排序算法总结

目录

一. 前言

二. 交换排序

1. 冒泡排序(平均复杂度n^2)

2. 快速排序(平均复杂度nlogn)

三. 插入排序

1. 直接插入排序(平均复杂度n^2)

2. 折半插入排序(时间复杂度n^2)

3. 表插入排序

四. 选择排序

1. 简单选择排序(平均复杂度n^2)

2. 堆排序算法(平均复杂度nlogn)

五. 归并排序

六. 基数排序

七. 排序总结


一. 前言

        常见的共九大排序算法,可分为内部排序(小文件排序)和外部排序(大文件排序)两大类,本文将重点介绍内部排序。

        注意:后文中排序算法的稳定性指的是能保证排序前2个相等的数,其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。再简单形式化一下即如果Ai = Aj且Ai原来在位置前,则排序后Ai还是要在Aj位置前。

二. 交换排序

1. 冒泡排序(平均复杂度n^2)

(1)基本版

执行趟数:n-1 ;  比较次数:n*(n-1)/2

交换次数:min:有序0 ; max:逆序 n*(n-1)/2

稳定性:稳定

        算法特点是双层循环,外层循环循环比较趟数,内层循环比较起点。每轮循环必定会把剩余部分最大的沉到最后或者把最小的浮到最上面而且其他元素的相对位置不改变。

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 7;
int Data[maxn],n;
int main()
{
    scanf("%d",&n);
    for(int i = 0;i<n;i++)scanf("%d",&Data[i]);
    for(int i = 0;i<n;i++){
        if(i==0)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");
    for(int i = 0;i<n;i++){
        for(int j = n-1;j>i;j--){
            if(Data[j] < Data[j-1])swap(Data[j],Data[j-1]);
        }
    }
    for(int i = 0;i<n;i++){
        if(i==0)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");
    return 0;
}

(2)优化版

执行趟数:min:有序 1 ;max:逆序:n-1

比较次数:min:有序n-1次 ;max:逆序 n*(n-1)/2

交换次数: min:有序0 ; max:逆序 n*(n-1)/2

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 7;
int Data[maxn],n;
int main()
{
    scanf("%d",&n);
    for(int i = 0;i<n;i++)scanf("%d",&Data[i]);
    for(int i = 0;i<n;i++){
        if(i==0)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");
    int i = n-1;//末位置
    while(i){
        int pos = 0;//记录最后一次交换的位置
        for(int j = 0;j<i;j++){
            if(Data[j] > Data[j+1]){
                swap(Data[j],Data[j+1]);
                pos = j;//发生交换
            }
        }
        i = pos;//记录位置
    }
    for(int i = 0;i<n;i++){
        if(i==0)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");
    return 0;
}

2. 快速排序(平均复杂度nlogn)

        其思想是每次排序选取一个基准,小于基准的都放在左边,大于基准的都放在右边,然后递归排序左右区间。其算法特点是每次排序都把序列划分为独立的两部分,一部分记录的关键字均比另一部分的小。

稳定性:不稳定

举例:初始  {2, 12, 16, 88, 5, 10, 34}

以最右边为基准进行快速排序:

第一趟快排后: 2 , 12 , 16 , 10 , 5 , 34 , 88

第二趟快排后:2, 5, 10, 12, 16, 34, 88

注意:当初始记录有序或者基本有序时,快速排序退化为冒泡排序,复杂度O(n^2)  

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 7;
int Data[maxn],n;
int Parttion(int low,int high){
    int temp = Data[low];//选取基准
    while(low < high){
        while(low < high&&Data[high]>=temp)high--;//从右往左找第一个小于基准的
        Data[low] = Data[high];//放在左边
        while(low < high&&Data[low]<=temp)low++;//从左往右找第一个大于基准的
        Data[high] = Data[low];//放在右边
    }
    Data[low] = temp;//最后更新基准位置
    return low;
}
void Qsort(int low,int high){
    if(low >= high)return;
    int pos = Parttion(low,high);//基准最终位置
    Qsort(low,pos-1);//递归左区间
    Qsort(pos+1,high);//递归右区间
}
int main()
{
    scanf("%d",&n);
    for(int i = 0;i<n;i++){
        scanf("%d",&Data[i]);
    }
    for(int i = 0;i<n;i++){
        if(i==0)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");

    Qsort(0,n-1);

    for(int i = 0;i<n;i++){
        if(i==0)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");
    return 0;
}

三. 插入排序

1. 直接插入排序(平均复杂度n^2)

执行趟数:n-1

比较次数:min:有序 n-1 ;max:逆序 (n+2)*(n-1)/2

移动次数:min:有序 0;max:逆序 (n+4)*(n-1)/2

最好:顺序 O(n)

最差:逆序 O(n^2)

稳定性:稳定

        其算法思想是假设前i项已经排好序,插入第i项,找到插入位置插入,后面元素后移。其算法特点是第i次循环结束,前i个元素必定有序,但不一定是最终结果。且其他元素相对位置不变。

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 7;
int Data[maxn],n,j;
int main()
{
    scanf("%d",&n);
    for(int i = 1;i<=n;i++)scanf("%d",&Data[i]);
    for(int i = 1;i<=n;i++){
        if(i==1)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");
    for(int i = 2;i<=n;i++){
        if(Data[i] < Data[i-1]){//升序排序,找到插入元素
            Data[0] = Data[i];//Data[0]保存插入元素
            Data[i] = Data[i-1];
            for(j = i-2;Data[0] < Data[j]&&j>=0;j--){//寻找位置并且后移元素
                Data[j+1] = Data[j];
            }
            Data[j+1] = Data[0];//更新
        }
    }
    for(int i = 1;i<=n;i++){
        if(i==1)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");
    return 0;
}

2. 折半插入排序(时间复杂度n^2)

        其算法思想是因为前i个数有序,所以二分插入位置,然后移动元素即可。相比直接插入,折半插入减少了比较次数,移动次数不变

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 7;
int Data[maxn],n;
int main()
{
    scanf("%d",&n);
    for(int i = 0;i<n;i++){
        scanf("%d",&Data[i]);
    }
    for(int i = 0;i<n;i++){
        if(i==0)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");
    for(int i = 1;i<n;i++){
        int l = 0,r = i-1;
        int temp = Data[i];
        while(l<=r){//寻找插入位置
            int mid = (l+r)>>1;
            if(Data[mid] > temp)r = mid-1;
            else l = mid+1;
        }
        for(int j = i;j>=r+1;j--)Data[j] = Data[j-1];//后移元素
        Data[r+1] = temp;//更新
    }
    for(int i = 0;i<n;i++){
        if(i==0)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");
    return 0;
}

3. 表插入排序

(1)普通表插入排序(复杂度n^2)

思想:为了减少插入排序的移动次数,用改变指针来代替移动

缺点:只能顺序操作,因为为链表结构,无法随机存取元素

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
const int maxn = 1000 + 7;
typedef struct{
    int data;
    int next;
}SLNode;
typedef struct{
    SLNode node[maxn];
    int len;
}SLinkList;
int n;
void TISort(SLinkList &L){
    L.node[0].data = INF;
    L.node[0].next = 1;//初始化循环链表,0为头节点
    L.node[1].next = 0;
    for(int i = 2;i<=L.len;i++){
        int j = 0;//寻找插入位置
        while(j<i&&L.node[L.node[j].next].data <= L.node[i].data)j = L.node[j].next;
        L.node[i].next = L.node[j].next;//改变指针指向
        L.node[j].next = i;
    }
}
int main()
{
    SLinkList List;
    scanf("%d",&n);
    List.len = n;
    for(int i = 1;i<=n;i++){
        scanf("%d",&List.node[i].data);
        List.node[i].next = -1;
    }

    TISort(List);

    bool flag = false;
    for(int i = List.node[0].next;i;i = List.node[i].next){//只能顺序输出
        if(!flag){
            printf("%d",List.node[i].data);
            flag = true;
        }
        else printf(" %d",List.node[i].data);
    }
    printf("\n");


    return 0;
}

(2)改进表插入排序

       其算法思想是根据指针调节位置。重排链表为顺序表,则能够执行二分操作。

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
const int maxn = 1000 + 7;
typedef struct{
    int data;
    int next;
}SLNode;
typedef struct{
    SLNode node[maxn];
    int len;
}SLinkList;
int n;
void TISort(SLinkList &L){
    L.node[0].data = INF;
    L.node[0].next = 1;
    L.node[1].next = 0;
    for(int i = 2;i<=L.len;i++){
        int j = 0;
        while(j<i&&L.node[L.node[j].next].data <= L.node[i].data)j = L.node[j].next;
        L.node[i].next = L.node[j].next;
        L.node[j].next = i;
    }
}
void Arrange(SLinkList&L){//重排函数
    int p = L.node[0].next;
    for(int i = 1;i<=L.len;i++){//拍好第i个元素(前i个元素已经有序)
        while(p<i)p = L.node[p].next;//在有序区域,要找到真正该排序的元素
        int q = L.node[p].next;//获得下一个该插入的元素
        if(p!=i){
            swap(L.node[p],L.node[i]);//交换
            L.node[i].next = p;//防止下次再前i区域内查找
        }
        p  = q;//下次查找做准备
    }
}
int main()
{
    SLinkList List;
    scanf("%d",&n);
    List.len = n;
    for(int i = 1;i<=n;i++){
        scanf("%d",&List.node[i].data);
        List.node[i].next = -1;
    }

    TISort(List);

    bool flag = false;
    for(int i = List.node[0].next;i;i = List.node[i].next){
        if(!flag){
            printf("%d",List.node[i].data);
            flag = true;
        }
        else printf(" %d",List.node[i].data);
    }
    printf("\n");

    Arrange(List);//重排

    for(int i = 1;i<=List.len;i++){//下表输出
        if(i==1)printf("%d",List.node[i].data);
        else printf(" %d",List.node[i].data);
    }
    printf("\n");
    return 0;
}

(3)希尔排序(复杂度n^1.3)

最优:正序O(n)

最差:逆序O(n^2)

稳定性:不稳定

        其算法思想是利用分治法思想划分插入排序的区间,先插入排序小区间,在逐步合并的插入排序大区间,依据原理:直接插入排序的优点是:当序列有序时,插入排序复杂度为线性On。其算法特点是某轮排序,每隔一定间隔元素全部有序;算法时间复杂度与选取怎么步长有关。

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100000 + 7;
int Data[maxn],n;
void ShellSort(int a[],int len){
     for(int gap = len/2;gap > 0;gap/=2){//步长
        for(int j = gap;j<n;j++){//排序该步长下每个子区间
            if(a[j] > a[j-gap])continue;
            int temp = a[j];
            for(int k = j-gap;k>=0&&a[k] > temp;k-=gap){//每个子区间插入排序
                a[k+gap] = a[k];
            }
            a[k+gap] = temp;
        }
     }
}
int main()
{
    scanf("%d",&n);
    for(int i = 0;i<n;i++){
        scanf("%d",&Data[i]);
    }
    for(int i = 0;i<n;i++){
        if(i==0)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");

    ShellSort(Data,n);//希尔排序

    for(int i = 0;i<n;i++){
        if(i==0)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");
    return 0;
}

四. 选择排序

1. 简单选择排序(平均复杂度n^2)

操作趟数:n-1

比较次数:n*(n-1)/2

移动次数:min:有序0 ; max:逆序3*(n-1)

稳定性: 不稳定

        其算法特点是第i轮选择结束,前i个元素一定为最终结果中前i个元素。

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 10000 + 7;
int num[maxn],n;
int PosMin(int l,int r){
   int pos = l;
   int minn = num[l];
   for(int i = l;i<r;i++){
      if(minn > num[i]){
          minn = num[i];
          pos = i;
      }
   }
   return pos;
}
int main()
{
   scanf("%d",&n);
   for(int i = 0;i<n;i++){
     scanf("%d",&num[i]);
   }
   for(int i = 0;i<n;i++){
       int j = PosMin(i,n);//在[i,n)内选择最小的一个,排好前i个
       if(i!=j)swap(num[i],num[j]);
   }
   for(int i = 0;i<n;i++){
      if(i==0)printf("%d",num[i]);
      else printf(" %d",num[i]);
   }
   printf("\n");
   return 0;
}

2. 堆排序算法(平均复杂度nlogn)

优点:最好最坏情况下,堆排序均为nlogn复杂度

大顶堆:对于一个序列 , ai >= a2i && ai >= a(2i+1) 恒成立,可看作一个完全二叉树

小顶堆:对于一个序列 , ai <= a2i && ai <= a(2i+1) 恒成立,可看作一个完全二叉树

由小到大排序:选择大顶堆

由大到小排序:选择小顶堆

稳定性:不稳定

        堆排序算法是利用堆的性质,不断输出堆顶元素,然后调节堆结构使剩下的仍满足堆,如此反复得到的序列,则为堆排序。在实现时需要思考以下问题:

(1)问题一:怎么把一个无序序列建成一个大/小顶堆?

答:自下而上,先建成小部分的堆,后往上合并堆。

(2)问题二:怎么把一个堆输出堆顶后,调整剩余部分仍为堆?

答:自上而下,选择较大的元素交换至上。

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 10000 + 7;
int Data[maxn],n;
void HeadAdjust(int l,int r){//假设除了堆顶以外,其他都成大顶堆
   int temp = Data[l];
   int s = l;
   for(int j = 2*s;j<=r;j*=2){
      if(j+1<=r&&Data[j] < Data[j+1])j++;//j+1 <= r !!!!  不然已经排好序的叶子会被再拿上来
      if(temp >= Data[j])break;
      Data[s] = Data[j];
      s = j;
   }
   Data[s] = temp;
}
void HeapSort(){
   for(int i = n/2;i>=1;i--){//从最后一个非叶子结点(叶子结点肯定自为堆),往上由小到大建堆
       HeadAdjust(i,n);
   }
   for(int i = n;i>1;i--){//排序一个堆
       swap(Data[i],Data[1]);
       HeadAdjust(1,i-1);//调整剩余部分仍为堆
   }
}
int main()
{
    scanf("%d",&n);
    for(int i = 1;i<=n;i++){
        scanf("%d",&Data[i]);
    }
    HeapSort();
    for(int i = 1;i<=n;i++){
        if(i==1)printf("%d",Data[i]);
        else printf(" %d",Data[i]);
    }
    printf("\n");
}
//10
//-1 100 99 0 66 14 -20 0 8 54

五. 归并排序

复杂度:归并排序的最好、最坏、平均时间复杂度均为O(nlogn)

缺点:需要辅助空间O(n)

稳定性:稳定

        归并排序的算法思想是分治,即分而治之。其算法特点是每一部分都独立的呈现有序。

举例:初始序列 {1,0,12,66,40,14,9}

第一趟排序后: [0,1 ],[ 12 , 66],[ 14 , 40 ],[ 9 ]

第二趟排序后: [ 0 , 1 , 12 , 66 ] , [ 9 , 14 , 40 ]

第三趟排序后:0 , 1,9,12,14,40,66

        2-路归并的实现代码如下:

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100000 + 7;
int num[maxn],temp[maxn],n;
void MergeArray(int *A,int l,int r,int mid){
     int p = l,q = mid+1;
     int len = l;
     while(p<=mid||q<=r){
        if(p>mid||(q<=r&&A[q] < A[p]))temp[len++] = A[q++];
        else temp[len++] = A[p++];
     }
     for(int i = l;i<=r;i++)A[i] = temp[i];
}
void Merge_Sort(int *A,int l,int r){
    if(l < r){
        int mid = (l + r)>>1;
        Merge_Sort(A,l,mid);
        Merge_Sort(A,mid+1,r);//分
        MergeArray(A,l,r,mid);//合
    }
}
int main()
{
    scanf("%d",&n);
    for(int i = 0;i<n;i++){
        scanf("%d",&num[i]);
    }

    Merge_Sort(num,0,n-1);

    for(int i = 0;i<n;i++){
        if(i==0)printf("%d",num[i]);
        else printf(" %d",num[i]);
    }
    printf("\n");
    return 0;
}

六. 基数排序

        多关键字排序,分为MSD(最高位优先排序) , 和LSD(最低位优先排序),这里放个图就明白啦!以LSD为例子:

        假设一个序列有n个记录,每个记录含有d个关键字(或者说每个记录长度为d,注意这里所有的记录都要统一长度;若为数字的话,长度不够前面补零), 每个关键字取值范围为rd,则基数排序中,每一次分配的复杂度为O(n),每次收集的复杂度为O(rd),整个排序要用d趟分配和收集。总的时间复杂度为O(n + rd) = O(kn)

七. 排序总结

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿阿阿安

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值