排序是计算机内经常进行的一种操作,其目的是将一组无序的数据元素调整为有序的数据元素
排序的概念
排序数学定义:假设含n个数据元素的序列为{ R1, R2, ···, Rn}
其相应的关键字序列为{ K1, K2, ···, Kn}这些关键字相互之间可以进行比较,即在它们之间存在着这样一个关系 :Kp1 ≤ Kp2 ≤ ··· ≤ Kpn
按此固有关系将上式记录序列重新排列为{ Rp1, Rp2, ···,Rpn}的操作称作排序
排序的稳定性
如果在序列中有两个数据元素r[i]和r[j],它们的关键字k[i] == k [j],且在排序之前,对象
r[i]排在r[j]前面。如果在排序之后,对象r[i]仍在r[j]前面,则称这个排序方法是稳定的;
否则称这个排序方法是不稳定的
多关键字排序
排序时需要比较的关键字多余一个
排序结果首先按关键字1进行排序
当关键字1相同时按关键字2进行排序
当关键字n-1相同时按关键字n进行排序
对于多关键字排序,只需要在比较操作时同时考虑多个关键字即可
排序中的关键操作
- 比较
任意两个数据元素通过比较操作确定先后次序 - 交换
数据元素之间需要交换才能得到预期结果
内排序和外排序
- 内排序
整个排序过程不需要访问外存便能完成 - 外排序
待排序的数据元素数量很大,整个序列的排序过程不可能在内存中完成
排序的审判
- 时间性能
关键性能差异体现在比较和交换的数量 - 辅助存储空间
为完成排序操作需要的额外的存储空间
必要时可以“空间换时间” - 算法的实现复杂性
过于复杂的排序法会影响代码的可读性和可维护性,也可能影响排序的性能
排序总结
- 排序是数据元素从无序到有序的过程
- 排序具有稳定性,是选择排序算法的因素之一
- 比较和交换是排序的基本操作
- 多关键字排序与单关键字排序无本质区别
- 排序的时间性能是区分排序算法好坏的主要因素
选择排序
基本思想
每一趟 (例如第 i 趟,i = 0, 1, …,n-2)在后面 n-i个待排的数据元素中选出关键字
最小的元素, 作为有序元素序列的第 i 个元素。
排序过程
- 首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换
- 再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换
- 重复上述操作,共进行n-1趟排序后,排序结束
示意图
示例代码
1 | void SelectionSort(int array[], int len) // O(n*n) |
插入排序
基本思想:
元素只有一个元素
排序过程
整个排序过程为n-1趟插入,即先将序列中第1个记录看成是一个有序子序列,然后从第2个记录开始,逐个进行插入,直至整个序列有序
示意图
示例代码
1 | void InertionSort(int array[], int len) // O(n*n) |
实质
对线性表执行n-1次插入操作,只是先要找到插入位置
V[0], V[1], ···, V[i-1]已经排好序。这时已经排好序。这时,用V[i]的关键字与V[i-1], V[i-2], ···的关键字进行比较, 找到插入位置即将V[i]]插入, 原来位置上的对象向后顺移
插入排序关键点:
- 拿出一个元素,留出位置
- 符合条件的元素后移
冒泡排序
排序过程
- 将第一个记录的关键字与第二个记录的关键字进行比较,若为逆序r[1].key > r[2].key,则交换;然后比较第二个记录与第三个记录;以此类推,直至n-1个记录和第n个记录比较为止–第一冒泡排序,结果关键字最大的记录被安置在最后一个记录上
- 对前n-1个记录进行第二趟冒泡排序,结果使关键字次大的记录被安置在第n-1个记录位置
- 重复上述步骤,直到在一趟排序中没有进行过交换记录的操作为止
示例代码
1 | void BubbleSort(int array[], int len) // O(n*n) |
排序改进
在冒泡排序的过程中,可以记录下需要交换的位置,对比交换位置的值,得到一个最值,最后再一次性做交换
希尔排序
希尔排序可以看作插入算法的普遍情况
排序过程
先取一个正整数d1<n,把所有相隔d1的记录放一组,组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止
O(n-1.3)
Q(nlogn)
希尔排序是不稳定的
示意图
示例代码
1 | void ShellSort(int array[], int len) |
快速排序
思想
快速排序是对冒泡排序的一种改进。它的基本思想是:
通过一躺排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一不部分的所有数据都要小,基准数据排在这两个子序列的中间;
然后再按次方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
步骤描述
对r[s···]中记录进行一趟快速排序,附设两个指针i和j,设枢轴记录rp=r[s],x=rp.key
初始时令i=s,j=t
- 首先从j所指的位置向前走索第一个关键字小于x的记录,并和rp交换
- 再从i所指位置起向后搜索,找到第一个关键字大于x的记录,和rp交换
- 重复上述步骤,直至i==j
- 再分别对两个子序列进行快速排序,直到每个子序列只含有一个记录为止
示意图
示例代码
1 | //划分过程 第一个元素当枢轴,分成2个有效子序列 |
归并排序
排序思想
一个元素,可以看做有序的,是稳定的算法
归并思想
将两个或两个以上的有序序列合并成一个新的有序序列
有序序列v[1]···v[m]和v[m+1]···v[n] -> v[1]···v[n]
只有两路的归并称之为2路归并,还有3路,多路归并
归并步骤
检测两个有序序列A和B,C为归并后的新的有序序列
对一个数组分成两路,mid中间
- 设置i,j和p三个指针,其初始值分别指向这三个记录区的起始位置
- 合并时依次比较R[i]和R[j]的关键字,取关键字较小的记录复制到R1[p]中
- 然后将复制记录的指针i或j加1,以及指向复制位置的指针p加1
- 重复这一过程,直至两个输入的子文件中有一个已全部复制完毕,此时将另一个文件中剩余的记录依次复制到R1中即可
设两个有序的子文件(相当于输入堆)放在同一向量中相邻的位置上:R[low..m],R[m+1..high],先将它们合并到一个局部的暂存向量R1(相当于输出堆)中,待合并完成后将R1复制回R[low..high]中
示例代码
1 | void Merge(int src[], int des[], int low, int mid, int high) |