八大排序算法sum up,存干货,[ 建议收藏!!! ]

目录

⚠️注意:以下都是按照升序进行排列

一、插入排序

1.1 直接插入排序

在这里插入图片描述

核心思想:把一个数插入到有序区间,保持这个区间有序,当前第n+1个数插入到前面

前面的arr[0]到arr[n-1]已经排好序,此时用arr[n]与前面的arr[n-1], arr[n-2]...的值
进行比较找到合适的位置将arr[n]进行插入,原来位置上的元素顺序后移实现了插入

代码实现

//插入排序
void InsertSort(int* a, int n){
    //控制起始条件
    for(int i=0; i<n-1;i++){ //注意控制好终止条件,这里的end的位置是在倒数第二个位置,所以要-1
    
        //单趟插入
        int end = i;
        int temp = a[end + 1]; //有序区间的后面
        while(end>=0){ //end到-1就终止了
            if(a[end]>temp){
                a[end+1] = a[end];
                --end;
            }else{
                break;
            }
        }
        //两种情况:第一种在最右边,第二种在最左边,end为-1了,始终放在end后面
        a[end+1] = temp;    
    }
}

1.2 希尔排序

请添加图片描述

核心思想:插入排序的进阶版,首先进行预排序,让数组接近升序然后直接排序,这里的预排序
是按分组,选取一个gap值,对这gap组数据插入排序,预拍下gap为n时,有n组需要排序

gap越小,越接近有序,gap越大,越不接近有序
但是gap越小挪动越慢,gap越大挪动越快

时间复杂度:O(N^1.3),一般gap建议为3
void ShellSort(int* a, int n){
    int gap = n;
    
    while(gap>1){
//        gap = gap/2;
        gap = gap/3 + 1; //保证gap一定可以为1 , 如gap是2, 2/3 = 0,那么gap等于0就不满足了,因此要加1
        
        for(int i=0; i< n - gap; ++i){ //并排走
            int end = i;
            int tmp = a[end+gap]; //存储最后一个数
            while(end>=0){
                if(tmp < a[end]){
                    a[end+gap] = a[end];
                    end = end-gap;
                }else{
                    break;
                }
            }
            a[end+gap] = tmp;
            
        }
    }
        
}

二、选择排序

2.1 选择排序

在这里插入图片描述

核心思想:选择最大的,和最小的这里要注意,当maxi最大正好在开始的地方,需要把maxi移到正确的位置

时间复杂度:O(N^2),最坏的排序
void SelectSort(int* a, int n){
    int begin=0, end=n-1;
    while(begin < end){
        
        //选出当前区间[begin, end]中最大和最小值
        int maxi = begin, mini = begin;
        for(int i=begin; i<=end; ++i){
            if(a[i]>a[maxi]){
                maxi = i;
            }
            if(a[i]<a[mini]){
                mini = i;
            }
        }
        Swap(&a[mini], &a[begin]);
        
        //如果换完之后,begin正好是max
        if(begin == maxi){
            maxi = mini;
        }
        
        Swap(&a[maxi], &a[end]);
        begin++;
        end--;
    }
}

2.2 堆排序

我们在写堆排序前需要了解下列知识点

堆定义:用数组存储表示的完全二叉树

小堆:树中所有的父节点都小于等于孩子
请添加图片描述
大堆:树中所有的父节点都大于等于孩子
请添加图片描述
父子关系公式:
leftChild = parent * 2 + 1
rightChild = parent * 2 + 2
parent = (child - 1) / 2

在堆排序前要知道堆向下调整算法,当满足下面图的情况我们就可以进一步排序

条件:根左右子树都是小堆,就可以向下做调整
1. 从根开始,不断往下调整
2. 选出左右孩子中小的,跟父亲比较,如果选出的小的孩子比父亲还要小,跟父亲交换,以小的孩子位置继续往下调整,最坏调到叶子终止
3. 如果比父亲大则停止

请添加图片描述
但是在上述排序动作前,需要先建堆,排升序建大堆,选出最大的数将其放到最后面,然后满足大小堆后即可做向下调整动作即可

核心思想:第一步建堆搞成(大/小堆)
向下调整算法:满足 根 下面的左右子树都是堆
//堆排序
void AdjustDown(int* a, int n, int parent){
    int child = parent*2 + 1;
    while(child < n){
        if(child+1<n && a[child+1] > a[child]){
            ++child;
        }
        if(a[child]>a[parent]){
            Swap(&a[child], &a[parent]);
            parent = child;
            child = parent*2+1;
        }else{
            break;
        }
    }
    
}
void HeapSort(int* a, int n){
    //排升序建大堆 O(N)
    for(int i=(n-1-1)/2; i>=0; i--){
        AdjustDown(a, n, i);
    }
    
    //O(N*logN)
    int end = n - 1;
    while(end > 0){
        Swap(&a[0], &a[end]);
        AdjustDown(a, end, 0);
        end--;
    }
}

三、交换排序

3.1 冒泡排序

在这里插入图片描述

核心思想:先看单趟,两两比较交换,依次循环往复
如果发现flag一趟排下来没有进行交换动作就直接结束排序
void BubbleSort(int* a, int n){
    
    for(int j=0; j<n; j++){
        int flag = 0;
        for(int i=1; i<n-j; ++i){
            if(a[i] < a[i-1] ){
                Swap(&a[i], &a[i-1]);
                flag = 1;
            }
        }
        if(flag == 0){
            break;
        }
    }
}

3.2 快速排序

3.2.1 Hoare

在这里插入图片描述

上图说明:选一个关键key,一般都是选择头,或者尾
单趟:key放在他正确的位置上,key的左边值比key小,key右边值比key大(这是key一趟下来排完后最终要放的位置)
单趟拍完,再想办法让左边区间有序,key的右边区间有序

时间复杂度为N*log(N), 高度是logN像二叉树,每一次单趟排都是N
如果是有序的,最坏是O(N^2)

那么还有优化解决方案:
第一种是取随机值做下标
第二种是获取这三个数中不是最大,也不是最小的那个值的下标,这种情况下不会有最坏情况,因为有三数组取中
//三数取中
int midIndex(int* a, int left, int right){
    int mid = left + (right - left)/2;
    if(a[left]<a[mid]){
        if(a[mid]<a[right]){
            return mid;
        }else if(a[left]<a[right]){
            return right;
        }else{
            return left;
        }
    }else{ // a[left] > a[mid]
        if(a[mid]>a[right]){
            return mid;
        }else if(a[left] < a[right]){
            return left;
        }else{
            return right;
        }
    }
}

int PartSort(int* a, int left, int right){
    
    int midI = midIndex(a, left, right);
    Swap(&a[left], &a[midI]);
    
    int keyi = left; //左边的做key,为下标,最右边的先出发
    //没有相遇就继续
    while(left<right){
        //如果左边做key,右边先走找比key小的
        while(left<right && a[right] >= a[keyi]){
            --right;
        }
        //左边再走,找大
        while(left < right && a[left] <= a[keyi]){
            ++left;
        }
        //交换,把大的还换右边,小的换左边
        Swap(&a[left], &a[right]);
    }
    Swap(&a[left], &a[keyi]); //这里的left和rightn用哪个都行
    
    return left; //返回key的位置
}

3.2.2 前后指针法

在这里插入图片描述

pre和key在同一位置,cur在后一个位置

核心思想:
1. cur往前走,找到比key小的数据
2. 找到后停下来,++pre
3. 交换pre和cur指向位置的值
4. 知道cur走到数组的结尾
5. 最后把pre和key指向的值做交换,使得key的做小比key小,key右边比key大

int PartSort2(int* a, int left, int right){
    int key = left;
    int pre = left;
    int cur = pre + 1;
    while(cur<=right){
        if(a[cur] <=  a[key] ){
            ++pre;
            Swap(&a[pre], &a[cur]);
        }
            //无论哪种情况,cur都要继续向后走
            ++cur;
        
    }
    Swap(&a[pre], &a[key]);
    return pre;
}
void QuickSort(int* a, int left, int right){
    if(left < right){
        int keyPos = PartSort3(a, left, right);
//        printArr(a+left, right-left+1);
        QuickSort(a, left, keyPos-1);
        QuickSort(a, keyPos+1, right);
    }
    
}

3.2.3 挖坑法

int PartSort3(int* a, int left, int right){
    int key = a[left]; //坑位的值进行保存
    int hole = left;
    
    while(left < right){
        //右边找小,填到左边的坑
        while(left < right && a[right] >=key ){
            --right;
        }
        
        //把右边找的小的,填到左边的坑,自己形成新的坑
        a[hole] = a[right];
        hole = right;
        
        //左边去找到,填到右边的坑里
        while(left < right && a[left] <= key ){
            ++left;
        }
        
        //左边找的大的,填到右边的坑,自己形成新的坑
        a[hole] = a[left];
        hole = left;
        
    }
    a[hole] = key;
    return hole;
}

四、归并排序

4.1递归版

Alt

核心思想:通过递归的方式把一个大数组进行拆分,然后依次进行归并。
此思想类似于后序遍历,先整体左边有序和右边整体有序后,再进行最后一次合并即可得到升序
void MergeSort(int* a, int left, int right, int* newArr){
     if(left>=right){
        return;
    }
    int mid = left + (right-left)/2;
    MergeSort(a, left, mid, newArr);
    MergeSort(a, mid+1, right, newArr);

    int index = left;
    int begin1 = left, end1 = mid;
    int begin2 = mid+1, end2 = right;
    while(begin1 <= end1 && begin2 <= end2){
        if(a[begin1] < a[begin2]){
            newArr[index++] = a[begin1++];
        }else{
            newArr[index++] = a[begin2++];
        }
    }

    while(begin1 <=end1){
        newArr[index++] = a[begin1++];
    }
    while(begin2 <=end2){
        newArr[index++] = a[begin2++];
    }

    for(int i=left; i<=right; ++i){
        a[i] = newArr[i];
    }
}

void Merge(int* a, int left, int right){
    int* newArr = (int*)malloc(sizeof(int)*right+1);
    MergeSort(a, left, right, newArr);
}

五、非递归排序

5.1 QuickSort非递归版

void MergeSortNoRecursion(int* a, int n ){
    int* tmp = (int*)malloc(sizeof(int) * n);
    int groupNum = 1;
    
    while(groupNum<n){
        //控制小组
        for(int i=0; i < n; i+=2*groupNum){
            int begin1 = i, end1 = i+groupNum-1; // 减去1代表的是下标
            int begin2 = i+groupNum, end2 = i+groupNum*2-1;
            
            int index = begin1;
            
            if(begin2>=n){
                begin2 = n+1;
                end2 = n;
            }
            
            if(end1 >=n){
                end1 = n-1;
            }
            
            if(end2>=n){
                end2 = n-1;
            }
            
            while(begin1 <= end1 && begin2 <= end2){
                if(begin1 < begin2){
                    tmp[index++] = a[begin1++];
                }else{
                    tmp[index++] = a[begin2++];
                }
                
            }
            
            while(begin1 <= end1){
                tmp[index++] = a[begin1++];
            }
            
            while(begin2 <= end2){
                tmp[index++] = a[begin2++];
            }
        }
        
        //拷贝会原数组
        for(int i=0; i<n; ++i){
            a[i] = tmp[i];
        }
        groupNum = groupNum*2;
    }
    
    
    free(tmp);
}

5.2 MergeSort非递归版

递归程序的问题:
1. 递归的深度太深,那么导致栈溢出
2. 性能问题
因此任何一个递归程序,我们要掌握把他改成非递归
1. 循环
2. 栈+循环
void QuickSortNoRecusive(int* a, int left, int right){
    ST st;
    StackInit(&st);
    StackPush(&st, right);
    StackPush(&st, left);
    
    while(!StackEmpty(&st)){
        int start = StackTop(&st);
        StackPop(&st);
        
        int end = StackTop(&st);
        StackPop(&st);
        
        int keyi = PartSort3(a, start, end);
        
        if(keyi+1 < right){
            StackPush(&st, end);
            StackPush(&st, keyi+1);
        }
        
        if(start < keyi){
            StackPush(&st, keyi-1);
            StackPush(&st, start);
        }
    }
    
    StackDestory(&st);
}

六、计数排序

时间复杂度:O(max(N, Range))
空间复杂度:O(Range)
优点:此排序适合数据大小比较集中的数据
缺点: 只适合整数排序,浮点数/字符串就不行了
void CountSort(int* a, int n){
    int min = a[0], max = a[0];
    for(int i=1; i <n; i++){
        if(a[i]<min){
            min = a[i];
        }
        if(a[i]>max){
            max = a[i];
        }
    }
    
    int range = max - min + 1;
    
    int* count = (int*)calloc(range, sizeof(int));
    
    //统计次数
    for(int i=0; i<n; ++i){
        count[a[i] - min]++;
    }
    
    for(int i=0; i<n; ++i){
        printf("%d   ", count[i]);
    }
    
    //根据count数组排序
    int index=0;
    for(int j=0; j<range; ++j){
        while(count[j]--){
            a[index++] = j + min;
        }
    }
    free(count);
}

对各种排序进行性能测试评估

// 测试排序的性能对比
void TestOP()
{
srand(time(0));
const int N = 10000;
int* a1 = (int*)malloc(sizeof(int)*N);
int* a2 = (int*)malloc(sizeof(int)*N);
int* a3 = (int*)malloc(sizeof(int)*N);
int* a4 = (int*)malloc(sizeof(int)*N);
int* a5 = (int*)malloc(sizeof(int)*N);
int* a6 = (int*)malloc(sizeof(int)*N);
int* a7 = (int*)malloc(sizeof(int)*N);
    
for (int i = 0; i < N; ++i)
 {
    a1[i] = rand();
    a2[i] = a1[i];
    a3[i] = a1[i];
    a4[i] = a1[i];
    a5[i] = a1[i];
    a6[i] = a1[i];
    a7[i] = a1[i];
 }
 int begin1 = clock();
 InsertSort(a1, N);
 int end1 = clock();
    
 int begin2 = clock();
 ShellSort(a2, N);
 int end2 = clock();
    
 int begin3 = clock();
 SelectSort(a3, N);
 int end3 = clock();
    
 int begin4 = clock();
 HeapSort(a4, N);
 int end4 = clock();
    
 int begin5 = clock();
 QuickSort(a4, 0, N-1);
 int end5 = clock();
    
 int begin6 = clock();
 MergeSort(a6, N);
 int end6 = clock();
 printf("InsertSort:%d\n", end1 - begin1);
 printf("ShellSort:%d\n", end2 - begin2);
 printf("SelectSort:%d\n", end3 - begin3);
 printf("HeapSort:%d\n", end4 - begin4);
 printf("QuickSort:%d\n", end5 - begin5);
 printf("MergeSort:%d\n", end6 - begin6);
 free(a1);
 free(a2);
 free(a3);
 free(a4);
 free(a5);
 free(a6);
 free(a7);
 }

请添加图片描述

最后附上各种排序的时间与空间复杂度图一览
Alt

七、完整代码

Gitee链接🔗 🔗 🔗

👉 👉 👉 Click HERE to check some sort of different sorting 👈 👈 👈

创作不易,如果文章对你帮助的话,点赞三连哦:)

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值