Skip to content

commit task_02 #198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: 2021
Choose a base branch
from
48 changes: 48 additions & 0 deletions 2021-Autumn/Backend/task_02/lfy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 学习心得
### 一.时间复杂度:
1. 算法的时间复杂度反映了程序执行时间随输入规模增长而增长的量级,在很大程度上能很好地反映出算法的优劣与否;

2. 算法执行时间需要依据该算法编制的程序在计算机上执行运行时所消耗的时间来度量,度量方法有两种,事后统计方法和事前分析估算方法,因为事后统计方法更多的依赖计算机的硬件,软件等环境因素,有时容易掩盖算法本身的优劣。因此常常采用事前分析估算的方法;

3. 一个算法是由控制结构(顺序,分支,循环三种)和原操作(固有数据类型的操作)构成的,而算法时间取决于两者的综合效率;

4. 一个算法花费的时间与算法中语句的执行次数成正比,执行次数越多,花费的时间就越多。一个算法中的执行次数称为语句频度或时间频度。记为``T(n)``;

5. 在时间频度中,n称为问题的规模,当n不断变化时,它所呈现出来的规律,我们称之为时间复杂度(其实在这中间引入了一个辅助函数``f(n)``,但它与``t(n)``是同数量级函数,我们且先这样理解。)

6. 在各种算法中,若算法中的语句执行次数为一个常数,则时间复杂度为``o(1)``;同时,若不同算法的时间频度不一样,但他们的时间复杂度却可能是一样的,``eg:T(n)=n^2+2n+4`` 与 ``T(n)=4n^2+n+8``,他们的时间频度显然不一样,但他们的时间复杂度却是一样的,均为``O(n^2)``,时间复杂度只关注最高数量级,且与之系数也没有关系。

7. 求解算法的时间复杂度的具体步骤是:
* ⑴. 找出算法中的基本语句:
算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。
* ⑵. 计算基本语句的执行次数的数量级:
  只需计算基本语句执行次数的数量级,这就意味着只要保证基本语句执行次数的函数中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。这样能够简化算法分析,并且使注意力集中在最重要的一点上:增长率。
* ⑶. 用大Ο记号表示算法的时间性能:
  将基本语句执行次数的数量级放入大Ο记号中。
  如果算法中包含嵌套的循环,则基本语句通常是最内层的循环体,如果算法中包含并列的循环,则将并列循环的时间复杂度相加

### 二.空间复杂度:
1. 空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度;

2. 一个算法在计算机上占用的内存包括:程序代码所占用的空间,输入输出数据所占用的空间,辅助变量所占用的空间这三个方面,程序代码所占用的空间取决于算法本身的长短,输入输出数据所占用的空间取决于要解决的问题,是通过参数表调用函数传递而来,只有辅助变量是算法运行过程中临时占用的存储空间,与空间复杂度相关;

3. 通常来说,只要算法不涉及到动态分配的空间,以及递归、栈所需的空间,空间复杂度通常为``O(1)``;

4. 对于一个算法,其时间复杂度和空间复杂度往往是相互影响的。当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间;反之,求一个较好的空间复杂度时,可能会使时间复杂度的性能变差,即可能导致占用较长的运行时间。另外,算法的所有性能之间都存在着或多或少的相互影响。因此,当设计一个算法(特别是大型算法)时,要综合考虑算法的各项性能,算法的使用频率,算法处理的数据量的大小,算法描述语言的特性,算法运行的机器系统环境等各方面因素,才能够设计出比较好的算法。
5. 求解算法的时间复杂度:
我们在写代码时,完全可以用空间来换取时间,比如字典树,哈希表等都是这个原理。算法在运行过程中临时占用的存储空间随算法的不同而异,有的算法只需要占用少量的临时工作单元,而且「不随问题规模的大小而改变」,我们称这种算法是“就地"进行的,是节省存储的算法,空间复杂度为``O(1)``,注意这并不是说仅仅定义一个临时变量;有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元,例如将快速排序和归并排序算法就属于这种情况。
如果算法执行所需要的临时空间不随着某个变量n的大小而变化,即此算法空间复杂度为一个常量,可表示为`` O(1)``。
### 三.时间复杂度与空间复杂度的关系:
对于一个算法,其时间复杂度和空间复杂度往往是相互影响的。
当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间;反之,追求一个较好的空间复杂度时,可能会使时间复杂度的性能变差,即可能导致占用较长的运行时间。
另外,算法的所有性能之间都存在着或多或少的相互影响。
因此,当设计一个算法(特别是大型算法)时,要综合考虑算法的各项性能,算法的使用频率,算法处理的数据量的大小,算法描述语言的特性,算法运行的机器系统环境等各方面因素,才能够设计出比较好的算法。

### 四.问题与笔记:

1.大部分排序算法都学过,基本没遇到什么问题。
2.在分析希尔排序的时间复杂度时,第一次分析时分析错误。再次认真分析后才分析对。
3.堆排序类似于插入排序与归并排序结合,拥有两者的优点。
4.计数排序与桶排序有些相似,但计数排序应用数据较密集时,而桶排序应该是应用于数据较分散时。
5.不是特别能理解基数排序的原理。
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以试试这个算法可视化网站 VisuAlgo - 排序 ,看看能不能帮助你理解


306 changes: 306 additions & 0 deletions 2021-Autumn/Backend/task_02/lfy/task_02.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
### 1.bubble_sort(优化版):
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

使用```添加代码段时,可以在```后面加上对应的语言标识,比如```java,这样代码段会有高亮,方便阅读

package sort;
import java.util.Scanner;
public class bubble_sort
{
int[] a;
public bubble_sort()
{
int i=0;
int n;
Scanner input=new Scanner(System.in);
System.out.println("请输入数组总数:");
n=input.nextInt();
this.a=new int[n];
System.out.println("请输入数组a:");
for(i=0;i<n;i++)
{
this.a[i]=input.nextInt();
}
}

public static void sort(int [] arr){
int t;
boolean flag;//是否交换的标志
for(int i=0; i<arr.length-1; i++){ //表示趟数,一共 arr.length-1 次
flag = false; // 每次遍历标志位都要先置为false,才能判断后面的元素是否发生了交换
for(int j=arr.length-1; j>i; j--){ //选出该趟排序的最大值往后移动
if(arr[j] < arr[j-1]){
t = arr[j];
arr[j] = arr[j-1];
arr[j-1] = t; //进行交换
flag = true; //只要有发生了交换,flag就置为true
}
}
if(!flag) break; // 判断标志位是否为false,如果为false,说明后面的元素已经有序,就直接return
}
}

public void list(int [] arr)
{
for(int i=0;i<arr.length;i++)
{
System.out.print(arr[i]+" ");
}
System.out.println();
}
public static void main(String[] args) {
bubble_sort A=new bubble_sort();
System.out.println("输出原数组:");
A.list(A.a);
A.sort(A.a);
System.out.println("输出交换后数组:");
A.list(A.a);
}
}
```
**时间复杂度**: ``O(N ^ 2)``

**空间复杂度**: ``O(1)``
## 2.insert_sort:
```
public static void insert_sort(int array[],int lenth){
int t;
for(int i=0;i<lenth-1;i++){
for(int j=i+1;j>0;j--){
if(array[j] < array[j-1]){ //寻找该数在前面有序部分被插入的部分并插入
temp = array[j-1];
array[j-1] = array[j];
array[j] = temp;
}else{ //该数最大,不需要交换
break;
}
}
}
}
```
**平均时间复杂度**: ``O(N ^ 2)``

**空间复杂度**: ``O(1)``

## 3.select_sort:
```
public static void select_sort(int array[],int lenth){
for(int i=0;i<lenth-1;i++){
int minIndex = i;
for(int j=i+1;j<lenth;j++){ //找到从i+1到最后的数中的最小值的下标并记录在minIndex中
if(array[j]<array[minIndex]){
minIndex = j;
}
}
if(minIndex != i){ //将最小值与第i个数交换
int temp = array[i];
array[i] = array[minIndex];
array[minIndex] = temp;
}
}
}
```
**平均时间复杂度**: ``O(N ^ 2)``

**空间复杂度**: ``O(1)``

## 4.shell_sort:
```
public static void shell_sort(int array[],int lenth){
int temp = 0;
int incre = lenth;
while(true){
incre = incre/2;
for(int k = 0;k<incre;k++){ //根据增量分为若干子序列
for(int i=k+incre;i<lenth;i+=incre){
for(int j=i;j>k;j-=incre){
if(array[j]<array[j-incre]){ //将每个子序列排序
temp = array[j-incre];
array[j-incre] = array[j];
array[j] = temp;
}else{
break;
}
}
}
}
if(incre == 1){ //直到子序列步长为1时,数组有序退出循环
break;
}
}
}
```

**时间复杂度:**:

shell排序的时间复杂度是根据选中的 ``增量d`` 有关的,所以分析shell排序的时间复杂度是个比较麻烦的事;

在**最优**的情况下,**时间复杂度**为``:O(N*(logN)^2 )`` (元素已经排序好顺序)

在**最差**的情况下,**时间复杂度**为:``O(N ^ 2)``;


**空间复杂度**: ``O(1)``

## 5.quicksort:
```
public static void quicksort(int a[],int l,int r){
if(l>=r)
return;
int i = l; int j = r; int key = a[l];//选择第一个数为key
while(i<j){
while(i<j && a[j]>=key)//从右向左找第一个小于key的值
j--;
if(i<j){
a[i] = a[j];
i++;
}
while(i<j && a[i]<key)//从左向右找第一个大于key的值
i++;
if(i<j){
a[j] = a[i];
j--;
}
}
//i == j
a[i] = key;
quickSort(a, l, i-1);//递归调用
quickSort(a, i+1, r);//递归调用
}
```
**平均时间复杂度**: ``O(N*logN)``

**空间复杂度**: ``O(1)``

## 6.merge_sort:
```
public static void merge_sort(int a[],int first,int last,int temp[]){
if(first < last){
int middle = (first + last)/2;
merge_sort(a,first,middle,temp);//左半部分排好序
merge_sort(a,middle+1,last,temp);//右半部分排好序
mergeArray(a,first,middle,last,temp); //合并
}
}
```
```
//合并算法
public static void mergeArray(int a[],int first,int middle,int end,int temp[]){
int i = first;
int m = middle;
int j = middle+1;
int n = end;
int k = 0;
while(i<=m && j<=n){
if(a[i] <= a[j]){
temp[k] = a[i];
k++;
i++;
}else{
temp[k] = a[j];
k++;
j++;
}
}
while(i<=m){
temp[k] = a[i];
k++;
i++;
}
while(j<=n){
temp[k] = a[j];
k++;
j++;
}
for(int ii=0;ii<k;ii++){
a[first + ii] = temp[ii];
}
}
```
**平均时间复杂度**:``O(NlogN)``

归并排序的效率是比较高的,设数列长为``N``,将数列分开成小数列一共要``logN``步,每步都是一个合并有序数列的过程,时间复杂度可以记为``O(N)``,故一共为``O(N*logN)``。

**空间复杂度**: ``O(n)``

归并排序的空间复杂度就是那个临时数组和递归时压如栈的数据占用的空间:``n + logn``。
## 7.MinHeap_sort:
```
//从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2
public static void MinHeapFixdown(int a[],int i,int n){
int j = 2*i+1; //子节点
int temp = 0;
while(j<n){ //在左右子节点中寻找最小的
if(j+1<n && a[j+1]<a[j]){
j++;
}
if(a[i] <= a[j])
break;
//较大节点下移
temp = a[i];
a[i] = a[j];
a[j] = temp;
i = j;
j = 2*i+1;
}
}
```
```
//构建最小堆
public static void MakeMinHeap(int a[], int n){
for(int i=(n-1)/2 ; i>=0 ; i--){
MinHeapFixdown(a,i,n);
}
}
```
```
//堆排序算法
public static void MinHeap_sort(int a[],int n){
int temp = 0;
MakeMinHeap(a,n);
for(int i=n-1;i>0;i--){
temp = a[0];
a[0] = a[i];
a[i] = temp;
MinHeapFixdown(a,0,i);
}
}
```
**平均时间复杂度**:``O(NlogN)``

由于每次重新恢复堆的时间复杂度为``O(logN)``,共``N - 1``次重新恢复堆操作,再加上前面建立堆时``N / 2``次向下调整,每次调整时间复杂度也为``O(logN)``。二次操作时间相加还是``O(N * logN)``。

**空间复杂度**: ``O(1)``

## 8.bucket_sort:
```
private void bucket_sort(int[] arr, int bucketSize) throws Exception {
if (arr.length == 0) {
return arr;
}
int minValue = arr[0];
int maxValue = arr[0];
for (int value : arr) {
if (value < minValue) {
minValue = value;
} else if (value > maxValue) {
maxValue = value;
}
}
int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;
int[][] buckets = new int[bucketCount][0];
// 利用映射函数将数据分配到各个桶中
for (int i = 0; i < arr.length; i++) {
int index = (int) Math.floor((arr[i] - minValue) / bucketSize);
buckets[index] = arrAppend(buckets[index], arr[i]);
}
int arrIndex = 0;
for (int[] bucket : buckets) {
if (bucket.length <= 0) {
continue;
}
// 对每个桶进行排序,这里使用了插入排序
bucket = insertSort.sort(bucket);
for (int value : bucket) {
arr[arrIndex++] = value;
}
}
}
```