七大经典排序算法

经典排序算法简介

以内排序(排序整个过程中,待排序的所有记录全部被放置在内存中)的七大经典排序算法为例子,就时间性能、辅助空间、算法复杂性来具体阐述。

排序算法分类

按照算法复杂程度分类

  1. 简单排序:冒泡排序、简单选择排序、直接插入排序
  2. 复杂排序:希尔排序、堆排序、归并排序、快速排序

按照算法类别分类

  1. 交换类:冒泡排序、快速排序
  2. 选择类:简单选择排序、堆排序
  3. 插入类:直接插入排序、希尔排序
  4. 归并类:归并排序

排序准备

建立一个排序用的顺序表结构L,用于下面七种经典排序

1  #define MAXSIZE 10
2  typedef struct SqList
3  {
4      int r[MAXSIZE+1];
5      int length;
6  }SqList;
/*排序的交换函数*/
7  void swap(SqList *L,int i,int j)
8  {
9      int temp=L->r[i];
10     L->[i]=L->[j];
11     L->[j]=temp;
12 }

冒泡排序

冒泡排序(Bubble Sort)一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

  • 中心思想:两两比较,符合要求交换。冒泡排序示意图

算法

1  void BubbleSort(SqList *L)
2  {
3      int i,j;
4      for(int i=1;i<L->length;i++)
5      {
6          for(int j=L->length-1;j>=i;j--)
7              if(L->r[j+1]<L->[j])
8                 swap(L,j+1,j);
9      }
10 }

时间复杂度

  1. 平均运行时间:所有情况中最有意义的,因为它是期望的运行时间
  2. 最坏时间复杂度:一般在没有特殊说明的情况下,都是指最坏时间复杂度

冒泡排序最坏时间复杂度是逆序,需要比较下面这么多次
∑ i = 2 n ( i − 1 ) = 1 + 2 + 3 + ⋅ ⋅ ⋅ + ( n − 1 ) = n ( n − 1 ) 2 \sum_{i=2}^n{\left( i-1 \right) =1+2+3+\cdot \cdot \cdot +\left( n-1 \right) =\frac{n\left( n-1 \right)}{2}} i=2n(i1)=1+2+3++(n1)=2n(n1)
并做出同等数量级的交换,总的时间复杂度为 o ( n 2 ) o( n^2 ) o(n2)

简单选择排序

简单选择排序法(Simple Selection Sort)就是通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<i<n)个记录交换之。

  • 中心思想:先比较选择,后记录交换
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

算法

1  void SelectSort(SqList *L)
2  {
3      int i,j,m;
4      for(i=1;i<L->length;i++)
5      {
6          min=i;
7          for(j=i+1;j<=L->length;j++)
8              if(L->r[j]<L->r[min])
9                 min=j;
10         if(i!=min)/**需要判断i和min,减少交换次数*/
11            swap(L,i,min);
12     }
13 }

时间复杂度

选择排序最坏时间复杂度是主要是比较选择过程,需要比较下面这么多次
∑ i = 1 n − 1 ( n − i ) = n − 1 + n − 2 + ⋅ ⋅ ⋅ + 1 = n ( n − 1 ) 2 \sum_{i=1}^{n-1}{\left( n-i \right) =n-1+n-2+\cdot \cdot \cdot +1=\frac{n\left( n-1 \right)}{2}} i=1n1(ni)=n1+n2++1=2n(n1)
交换次数最多为 n − 1 n-1 n1,因此总的时间复杂度为 o ( n 2 ) o( n^2 ) o(n2)

直接插入排序

直接插入排序(Straight Insertion Sort)的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。

  • 中心思想:设置额外空间,增加序列移动

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法

1  void InsertSort(SqList *L)
2  {
3      int i,j;
4      for(i=2;i<=L->length;i++)
5      {
6          if(L->r[i]<L->r[i-1])
7          {
8              L->r[0]=L->r[i];/*设置哨兵*/
9              for(j=i-1;L->r[j]>L->r[0];j--)/*>符号的原则,相等不交换*/
10                 L->r[j+1]=L->r[j];
11             L->r[j+1]=L->r[0];
12         }
13     }
14 }

时间复杂度

直接插入排序最坏时间复杂度是逆序,需要比较下面这么多次
∑ i = 2 n i = 1 + 2 + 3 + ⋅ ⋅ ⋅ + n = ( n + 2 ) ( n − 1 ) 2 \sum_{i=2}^n{i=1+2+3+\cdot \cdot \cdot +n=\frac{\left( n+2 \right) \left( n-1 \right)}{2}} i=2ni=1+2+3++n=2(n+2)(n1)
而记录的移动次数同时最大下面这么多次
∑ i = 2 n i + 1 = 2 + 3 + 4 + ⋅ ⋅ ⋅ + n + 1 = ( n + 4 ) ( n − 1 ) 2 \sum_{i=2}^n{i+1=2+3+4+\cdot \cdot \cdot +n+1=\frac{\left( n+4 \right) \left( n-1 \right)}{2}} i=2ni+1=2+3+4++n+1=2(n+4)(n1)
因此,我们得出直接插入排序法的时间复杂度为 0 ( n 2 ) 0(n^2) 0(n2)

希尔排序

在直接插入排序的基础上,我们需要采取跳跃分割的策略:
增加increment,increment=increment/3+1

  • 中心思想跳跃分割,直接插入

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法

1  void ShellSort(SqList *L)
2  {
3      int i,j;
4      int increment=L->length;
5      do
6      {
7          increment=increment/3+1;
8          for(i=increment+1;i<=L->length;i++)
9          {
10             if(L->r[i]<L->r[i-increment])
11             {
12                 L->r[0]=L->r[i];/*设置哨兵*/
13                 for(j=i-increment;i>0&&L->r[j]>L->[0];j-=increment)
14                     L->r[j+increment]=L->r[j];
15                 L->r[j+increment]=L->r[0];
16             }
17         }
18     } while (increment>1); 
19 }

时间复杂度

希尔排序的时间复杂度由于跳跃式的移动,研究表明当 d l t a [ k ] = 2 t − k + 1 − 1 ( 0 ⩽ k ⩽ t ⩽ ⌊ log ⁡ 2 ( n + 1 ) ⌋ ) dlta\left[ k \right] =2^{t-k+1}-1\left( 0\leqslant k\leqslant t\leqslant \lfloor \log _2\left( n+1 \right) \rfloor \right) dlta[k]=2tk+11(0ktlog2(n+1))
其空间复杂度 o ( n 3/ 2 ) o\left( n^{\text{3/}2} \right) o(n3/2),要好于直接排序的 o ( n 2 ) o(n^2) o(n2)

堆排序

堆排序(Heap Sort)就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。

  • 中心思想:不断的构成大顶堆的过程(二叉树)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法

1  void HeapSort(SqList *L)
2  {
3      int i;
4      for(i=L->r/2;i>0;i--)
5          HeapAdjust(L,i,L->length);/*把L中的r构建成一个大顶堆*/
6      for(i=L->length;i>1;i--)
7      {
8          swap(L,1,i);
9          HeapAdjust(L,1,i-1);/*将L->r[1..1-1]重新调整为大顶堆*/
10     }
11 }
/*本函数调整L->r[s]的关键字,使L->r[s..m]成为一个大顶堆*/
12 void HeapAdjust(SqList *L,int s,int m)
13 {
14     int j,temp;
15     temp=L->r[s];
16     for(j=2*s;j<=m;j*=2)
17     {
18         if(j<m&&L->r[j]<L->r[j+1])/*j<m是判断是否有右孩子*/
19            j++;
20         if(temp>=L->r[j])
21            break;
22         L->r[s]=L->r[j];
23         s=j;
24     }
25     L->r[s]=temp;/*s为全局变量,temp包含两个意思一个是直接break另外一个是L->r[j]*/
26 }

时间复杂度

堆排序时间复杂度包含构建堆正式排序

  1. 构建堆的时间复杂度 o ( n ) o(n) o(n)
  2. 正式排序时,第i次取堆顶记录重建堆需要用 o ( l o g i ) o(logi) o(logi)的时间(完全二又树的某个结点到根结点的距离为 ⌊ log ⁡ 2 i ⌋ + 1 \lfloor \log _2i \rfloor +1 log2i+1,并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为 o ( n l o g n ) o(nlogn) o(nlogn)

总的来说算时间复杂度为 o ( n l o g n ) o(nlogn) o(nlogn)

归并排序

归并排序(Merging Sort)就是利用归并的思想实现的排序方法。它的原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到 ⌈ n / 2 ⌉ \lceil n/2 \rceil n/2( ⌈ x ⌉ \lceil x \rceil x)表示不小于x的最小整数)个长度为2或1的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。

  • 中心思想:先拆分合并,子序列合并排序
    在这里插入图片描述

算法

   /*递归调用,向外封装了一个函数*/
1  void MergeSort(SqList *L)
2  {
3      MSort(L->r,L->r,1,L->length);
4  }
   /*将SR[s..t]归并排序为TR1[s..t]*/
5  void MSort(int SR[],int TR1[],int s,int t)
6  {
7      i--nt m;
8      int TR2[MAXSIZE+1];
9      if(s==t)
10        TR1[s]=SR[s];
11     else
12     {
13         m=(s+t)/2;
14         MSort(SR,TR2,s,m);
15         MSort(SR,TR2,m+1,t);
16         Merge(TR2,TR1,s,m,t);
17     }
18 }
   /*将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n]*/
19 void Merge(int SR[],int TR[],int i,int m,int n)
20 {
21     int j,k,l;
22     for(j=m+1,k=i;i<=m&&j<=n;k++)/*这里不用||代替&&原因是为了防止数组越界,由于i++和j++*/
23     {
24         if(SR[i]<SR[j])
25            TR[k]=SR[i++];
26         else
27            TR[k]=SR[j++];
28     }
29     if(i<=m)/*29-38不可省,主要为了防止数组越界,导致i和j在SR数组赋值到TR上出现混乱*/
30     {
31         for(l=0;l<=m-i;l++)/*这里是L不是数字1*/
32             TR[k+l]=SR[i+l];
33     }
34     if(j<=n)
35     {
36         for(l=0;l<=n-j;l++)
37             TR[k+l]=SR[j+l];
38     }
39 }

时间复杂度

归并排序时间复杂度包括遍历整体归并

  1. 遍历:排序序列中的所有记录扫描一遍,因此耗费 o ( n ) o(n) o(n)时间
  2. 整体归并:由完全二叉树的深度可知,整个归并排序需要进行 ⌈ log ⁡ 2 n ⌉ \lceil \log _2n \rceil log2n次,时间复杂度为 o ( n l o g n ) o(nlogn) o(nlogn)

因此归并排序总的时间复杂度要 o ( n l o g n ) o(nlogn) o(nlogn)

快速排序

快速排序(Quick Sort)的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。

  • 枢轴分小大两部分,递归调用记录枢轴

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法

   /*向外封装一个函数*/
1  void QuickSort(SqList *L)
2  {
3      QSort(L,1,L->length);
4  }
   /*对顺序表工中的子序列L->r[1ow..high]作快速排序*/
5  void QSort(SqList *L,int low,int high)
6  {
7      int pivot;/*设置枢轴*/
8      if(low<high)
9      {
10         pivot=Partition(L,low,high)
11         QSort(L,low,pivot-1);
12         QSort(L,pivot+1,high);
13     }
14 }
   /*使枢轴记录到位,并返回其所在位置*/
15 int Partition(SqList *L,int low,int high)
16 {
17     int pivotkey;
18     pivotkey=L->r[low];
19     while(low<high)
20     {
21         while(low<high&&L->r[high]>=pivotkey)/*>=原则相等不交换*/
22               high--;
23         swap(L,low,high);
24         while(low<high&&L->r[low]<=pivotkey)
25               low++;
26         swap(L,low,high);
27     }
28     return low;/*返回枢轴所在位置*/
29 }

时间复杂性

快速排序的时间复杂性最坏的情况为正序和逆序(即一棵斜树),包括递归调用和比较找枢轴

  1. 递归调用:n-1次的递归调用,时间复杂度为 o ( n ) o(n) o(n)
  2. 比较找枢轴:第i次划分需要经过n-i次关键字的比较才能找到第i个记录,也就是枢轴的位置,比较找的次数为 ∑ i = 1 n − 1 n − i = n − 1 + n − 2 + ⋅ ⋅ ⋅ + 1 = n ( n − 1 ) 2 \sum_{i=1}^{n-1}{n-i=n-1+n-2+\cdot \cdot \cdot +1=\frac{n\left( n-1 \right)}{2}} i=1n1ni=n1+n2++1=2n(n1),则时间复杂度为 o ( n 2 ) o(n^2) o(n2)

总的时间复杂度为 o ( n 2 ) o(n^2) o(n2)

总结

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 经过优化的快排是目前最好的排序算法,特别是应用于大数据(n>50)
  2. 小数据的时候简单排序更胜一筹(直接插入排序)
  3. 归并排序的稳定性最好

参考文献

[1] 程杰. 大话数据结构[M]. 清华大学出版社, 2011.
[2] 啊哈磊. 啊哈! 算法[M]. 人民邮电出版社, 2014.

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值