☰
第5讲:排序
2026/10/20 2:23:06 网站建设 项目流程

【本节目标】

    1. 排序的概念及其运用
    1. 常见排序算法的实现
    1. 排序算法复杂度及稳定性分析

目录

  • 1. 排序的概念及其运用
  • 1.1 排序的概念
  • 1.2 排序运用
  • 1.3 常见的排序算法
  • 2. 常见排序算法的实现
  • 2.1 插入排序
  • 2.1.1 基本思想
  • 2.1.2 直接插入排序
  • 2.1.3 希尔排序(缩小增量排序)
  • 2.2 选择排序
  • 2.2.1 基本思想
  • 2.2.2 直接选择排序
  • 2.2.3 堆排序
  • 2.3 交换排序
  • 2.3.1 冒泡排序
  • 2.3.2 快速排序
  • 2.3.3 快速排序优化
  • 2.3.4 快速排序非递归
  • 2.4 归并排序
  • 2.5 非比较排序
  • 3. 排序算法复杂度及稳定性分析
  • 4. 选择题练习
  • 答案

1. 排序的概念及其运用

1.1 排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减地排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] = r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不断地在内外存之间移动数据的排序。

1.2 排序运用

1.3 常见的排序算法

查看各个排序算法动态演示效果:Comparison Sorting Visualization

// 排序实现的接口// 插入排序voidInsertSort(int*a,intn);// 希尔排序voidShellSort(int*a,intn);// 选择排序voidSelectSort(int*a,intn);// 堆排序voidAdjustDown(int*a,intn,introot);voidHeapSort(int*a,intn);// 冒泡排序voidBubbleSort(int*a,intn);// 快速排序递归实现// 快速排序 hoare 版本intPartSort1(int*a,intleft,intright);// 快速排序挖坑法intPartSort2(int*a,intleft,intright);// 快速排序前后指针法intPartSort3(int*a,intleft,intright);voidQuickSort(int*a,intleft,intright);// 快速排序非递归实现voidQuickSortNonR(int*a,intleft,intright);// 归并排序递归实现voidMergeSort(int*a,intn);// 归并排序非递归实现voidMergeSortNonR(int*a,intn);// 计数排序voidCountSort(int*a,intn);// 测试排序的性能对比voidTestOP(){srand(time(0));constintN=100000;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);for(inti=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];}intbegin1=clock();InsertSort(a1,N);intend1=clock();intbegin2=clock();ShellSort(a2,N);intend2=clock();intbegin3=clock();SelectSort(a3,N);intend3=clock();intbegin4=clock();HeapSort(a4,N);intend4=clock();intbegin5=clock();QuickSort(a5,0,N-1);intend5=clock();intbegin6=clock();MergeSort(a6,N);intend6=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);}

排序 OJ(可使用各种排序跑这个 OJ)。912. 排序数组 - 力扣(LeetCode)

2. 常见排序算法的实现

2.1 插入排序

2.1.1 基本思想

直接插入排序是一种简单的插入排序法,其基本思想是:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。

实际中我们玩扑克牌时,就用了插入排序的思想。

2.1.2 直接插入排序

当插入第i(i >= 1)个元素时,前面的array[0]、array[1]、…、array[i - 1]已经排好序,此时用array[i]的排序码与array[i - 1]、array[i - 2]、… 的排序码顺序进行比较,找到插入位置,即将array[i]插入,原来位置上的元素顺序后移。

直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高。
  2. 时间复杂度:O(N^2)。
  3. 空间复杂度:O(1),它是一种稳定的排序算法。
  4. 稳定性:稳定。
2.1.3 希尔排序(缩小增量排序)

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数gap,把待排序文件中所有记录分成gap组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,取新的gap,重复上述分组和排序的工作。当到达gap = 1时,所有记录在同一组内排好序。

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。

  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

  3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在不少书中给出的希尔排序的时间复杂度都不固定:

    • 《数据结构(C 语言版)》 - 严蔚敏

《数据结构-用面相对象方法与 C++ 描述》 - 殷人昆

因为咱们的gap是按照 Knuth 提出的方式取值的,而且 Knuth 进行了大量的试验统计,我们暂时就按照:O(n^1.25)到O(1.6 * n^1.25)来算。

  1. 稳定性:不稳定。

2.2 选择排序

2.2.1 基本思想

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

2.2.2 直接选择排序
  • 在元素集合array[i]--array[n - 1]中选择关键码最大(小)的数据元素。
  • 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换。
  • 在剩余的array[i]--array[n - 2](array[i + 1]--array[n - 1])集合中,重复上述步骤,直到集合剩余 1 个元素。

直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好,实际中很少使用。
  2. 时间复杂度:O(N^2)。
  3. 空间复杂度:O(1)。
  4. 稳定性:不稳定。
2.2.3 堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)。
  3. 空间复杂度:O(1)。
  4. 稳定性:不稳定。

2.3 交换排序

**基本思想:**所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置。交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

2.3.1 冒泡排序

冒泡排序的特性总结:

  1. 冒泡排序是一种非常容易理解的排序。
  2. 时间复杂度:O(N^2)。
  3. 空间复杂度:O(1)。
  4. 稳定性:稳定。
2.3.2 快速排序

快速排序是 Hoare 于 1962 年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某个元素作为基准值,按照该排序码将待排序集合分割成两个子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后对左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

// 假设按照升序对 array 数组中 [left, right) 区间中的元素进行排序voidQuickSort(intarray[],intleft,intright){if(right-left<=1)return;// 按照基准值对 array 数组的 [left, right) 区间中的元素进行划分intdiv=partion(array,left,right);// 划分成功后以 div 为边界形成了左右两部分 [left, div) 和 [div + 1, right)// 递归排 [left, div)QuickSort(array,left,div);// 递归排 [div + 1, right)QuickSort(array,div+1,right);}

上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像。同学们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。

将区间按照基准值划分为左右两半部分的常见方式有:

  1. Hoare 版本

  2. 挖坑法

  3. 前后指针版本

2.3.3 快速排序优化
  1. 三数取中法选key。
  2. 递归到小的子区间时,可以考虑使用插入排序。

2.3.4 快速排序非递归
voidQuickSortNonR(int*a,intleft,intright){Stack st;StackInit(&st);StackPush(&st,left);StackPush(&st,right);while(StackEmpty(&st)!=0){right=StackTop(&st);StackPop(&st);left=StackTop(&st);StackPop(&st);if(right-left<=1)continue;intdiv=PartSort1(a,left,right);// 以基准值为分割点,形成左右两部分:[left, div) 和 [div + 1, right)StackPush(&st,div+1);StackPush(&st,right);StackPush(&st,left);StackPush(&st,div);}StackDestroy(&st);}

快速排序的特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序。
  2. 时间复杂度:O(N*logN)。


3. 空间复杂度:O(logN)。
4. 稳定性:不稳定。

2.4 归并排序

基本思想:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

归并排序核心步骤:

归并排序的特性总结:

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)。
  3. 空间复杂度:O(N)。
  4. 稳定性:稳定。

2.5 非比较排序

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。

操作步骤:

  1. 统计相同元素出现次数。
  2. 根据统计的结果将序列回收到原来的序列中。

计数排序的特性总结:

  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(MAX(N, 范围))。
  3. 空间复杂度:O(范围)。
  4. 稳定性:稳定。

3. 排序算法复杂度及稳定性分析

4. 选择题练习

  1. 快速排序算法是基于( )的一个排序算法。

    • A. 分治法
    • B. 贪心法
    • C. 递归法
    • D. 动态规划法
  2. 对记录(54,38,96,23,15,72,60,45,83)进行从小到大的直接插入排序时,当把第 8 个记录 45 插入到有序表时,为找到插入位置需比较( )次?(采用从后往前比较)

    • A. 3
    • B. 4
    • C. 5
    • D. 6
  3. 以下排序方式中占用O(n)辅助存储空间的是:

    • A. 选择排序
    • B. 快速排序
    • C. 堆排序
    • D. 归并排序
  4. 下列排序算法中稳定且时间复杂度为O(n^2)的是( ):

    • A. 快速排序
    • B. 冒泡排序
    • C. 直接选择排序
    • D. 归并排序
  5. 关于排序,下面说法不正确的是:

    • A. 快排时间复杂度为O(N*logN),空间复杂度为O(logN)。
    • B. 归并排序是一种稳定的排序,堆排序和快排均不稳定。
    • C. 序列基本有序时,快排退化成冒泡排序,直接插入排序最快。
    • D. 归并排序空间复杂度为O(N),堆排序空间复杂度为O(logN)。
  6. 下列排序法中,最坏情况下时间复杂度最小的是( ):

    • A. 堆排序
    • B. 快速排序
    • C. 希尔排序
    • D. 冒泡排序
  7. 设一组初始记录关键字序列为(65,56,72,99,86,25,34,66),则以第一个关键字 65 为基准而得到的一趟快速排序结果是( ):

    • A. 34,56,25,65,86,99,72,66
    • B. 25,34,56,65,99,86,72,66
    • C. 34,56,25,65,66,99,86,72
    • D. 34,56,25,65,99,86,72,66

答案

  1. A
  2. C
  3. D
  4. B
  5. D
  6. A
  7. A

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询