插入排序 | Insertion Sort

插入排序将数列划分为“已排序的”和“未排序的”两部分,每次从“未排序的”元素中选择一个插入到“已排序的”元素中的正确位置。

  1. for (int i = 1; i < N; i++) {
  2. int temp = a[i];
  3. for (int j = i; j > 0 && a[j-1] > temp; j--)
  4. a[j] = a[j-1]; //overwrite
  5. a[j] = temp;
  6. }
  1. 插入排序的最坏情况时间复杂度和平均情况时间复杂度都为 ![](https://cdn.nlark.com/yuque/__latex/8e9c5fee65a4f32abccd0e83ff203e39.svg#card=math&code=O%28N%5E2%29&height=23&id=GszKC),但其在数列几乎有序时效率很高,在数列是完全有序的情况下时间复杂度是 ![](https://cdn.nlark.com/yuque/__latex/33697ce7dfa48ba80980d298c8089378.svg#card=math&code=O%28N%29&height=20&id=IfiOB) 的。一般地,复杂度精确的为![](https://cdn.nlark.com/yuque/__latex/067b19dd03055f9cfc4789225e34236f.svg#card=math&code=%5CTheta%28N%2BI%29&id=EShL9),![](https://cdn.nlark.com/yuque/__latex/dd7536794b63bf90eccfd37f9b147d7f.svg#card=math&code=I&id=Sv6wS)为序列中逆序对的数目。<br />**逆序对的定义:**![](https://cdn.nlark.com/yuque/__latex/8f6e883e8492b49897937ab055b8dde5.svg#card=math&code=i%3Cj%2Ca%5Bi%5D%3Ea%5Bj%5D&id=RsMEg)的一对。
  • 内排序 | Internal Sorting - 图1个互异数的平均逆序数是内排序 | Internal Sorting - 图2,完全反序是内排序 | Internal Sorting - 图3
  • 通过交换相邻元素来进行排序的任何算法平均需要内排序 | Internal Sorting - 图4时间。

希尔排序 | Shell Sort

希尔排序可以看做插入排序的一般化,定义增量序列内排序 | Internal Sorting - 图5,在用增量内排序 | Internal Sorting - 图6排序后,对所有内排序 | Internal Sorting - 图7,均有内排序 | Internal Sorting - 图8,称为内排序 | Internal Sorting - 图9。其重要的性质也是算法的基础是,内排序 | Internal Sorting - 图10后的序列将保持内排序 | Internal Sorting - 图11排序的特性不改变。
对于增量序列的选取直接影响算法的复杂度,我们以内排序 | Internal Sorting - 图12为例。

  1. int i, j, increment, temp;
  2. for (increment = N / 2; increment > 0; increment /= 2){
  3. for (i = increment; i < N; i++){
  4. temp = a[i];
  5. for (j = i; j >= increment; j -= increment){
  6. if( temp < a[j - increment]) a[j] = a[j - increment];
  7. else break;
  8. }
  9. a[j] = temp;
  10. }
  11. }

Hibbard增量序列

  • Hibbard增量序列的希尔排序上界为内排序 | Internal Sorting - 图13,平均运行时间为内排序 | Internal Sorting - 图14
  • Hibbard序列:内排序 | Internal Sorting - 图15

Sedgewick增量序列(已知最好的序列)

  • 上界为内排序 | Internal Sorting - 图16,平均为内排序 | Internal Sorting - 图17
  • Sedgewick序列:内排序 | Internal Sorting - 图18

堆排序 | Heap Sort

之前学堆的时候自己写过一个堆排序,但是空间的需求是双倍,因为要重新建堆

  1. void Heapsort( ElementType A[ ], int N ) {
  2. int i;
  3. for ( i = N / 2; i >= 0; i - - ) /* BuildHeap */
  4. PercDown( A, i, N );
  5. for ( i = N - 1; i > 0; i - - ) {
  6. Swap( &A[ 0 ], &A[ i ] ); /* DeleteMax */
  7. PercDown( A, 0, i );
  8. }
  1. 改删除为放置于队尾,同时堆的大小减小(即`PercDown()`的第三个参数),这样可在原数组的基础上进行堆排序,空间需求仍为![](https://cdn.nlark.com/yuque/__latex/33697ce7dfa48ba80980d298c8089378.svg#card=math&code=O%28N%29&id=ocEpR)。

归并排序 | Merge Sort

经典的分治算法例子,我们看这个图会很清楚:
内排序 | Internal Sorting - 图19

快速排序 | Quick Sort(undone)

桶排序、基数排序 | Bucket&Radix Sort(undone)

算法稳定性

稳定性是指具有相同值的元素在排序过程中的相对位置是否变化,若变化,不稳定,反之则稳定

  • 插入排序:稳定,因为一个个
  • 希尔排序:不稳定,因为交换
  • 快速排序:不稳定,因为排序后交换
  • 堆排序:不稳定,直接拉到最上面交换
  • 归并排序:稳定,指针一个个移动
  • 基数排序:稳定