思路
每一趟都从n-i+1(i=1,2,3….n-1)个记录中选择出最小的关键字,作为有序序列的第i个元素。
常用的选择排序
- 简单选择排序
- 堆排序
简单选择排序
思路 (参考:选择排序)
数组A,长度为:n,临时变量:i,初始化为1
- 从A[i]~A[n]这n-i+1个元素中,找出最小的关键字,并且记录其下标
- 如果该关键字不是A[i]~A[n]这个序列的第一个元素,则将该两个位置的元素替换
- 将i从1~n-1,递增循环执行上述两个步骤
代码实现
1 | #include<iostream> |
结果:
时间复杂度分析
我们通过分析可以知道,我们程序无论是什么情况,都要比较:n*(n-1)/2次。所以程序的时间复杂度为:O(n^2)。
是否稳定
稳定的定义就是:两个相同的数,在排完序后前后的相对顺序没有改变。对于简单选择排序,它是不稳定的,我可以简单的举个例子:{7,7,2},当我们进行简单选择排序后,第一个7会和2调换位置,从出现不稳定的情况
堆排序
简单的选择排序为了得出有序的序列,对于任意序列都要比较:n*(n-1)/2次。那么我们要优化这个算法的话,那么就要减少我们的比较的次数。所以从比较这里出发:我们为得出n个关键字中的最小的值,需要比较n-1次,而下一次要得出n-1个关键字中最小的值时,同样又要比较n-2次,如果我们可以利用之前的n-1次的比较结果,那么我们就可能在下一次寻找中,减少比较次数,从而就可以提高程序的效率了。堆就可以记录这些信息,所以我们就提出了另外一种优化的选择排序:堆排序
思路
用一个完全二叉树来表示堆,堆有两种:最大堆和最小堆。
- 最大堆:所有结点都比它们的左右孩子大。调整过程出现两个孩子结点,都大于父结点,选择大的那个替换。
- 最小堆:所有结点都比它们的左右孩子小。调整过程出现两个孩子结点,都小于父结点,选择小的那个替换。
堆排序的大体思路是(以最小堆为例):
- 首先建立初始堆
- 输出根结点,用最后一个结点替换根结点,删除最后一个结点,并且对堆进行调整,使其还是满足最小堆的性质。
- 重复,第二个过程,直到堆为空为止
实现过程
我们从思路分析,可以知道我们需要解决两个问题:
- 建立初始堆
- 输出后,对堆的调整
程序实现
1 | void HeapAdjust(Datatype * data, int length, int s) { |
在主函数中,调用上述的HeapSort函数,就可以进行堆排序了。
时间复杂度分析
这里的时间主要是花费在调用heapAdjust函数中,HeapAdjust函数最多做:⌈log2n⌉次调整,总共需要调用:(n/2)+(n-1)次HeapAdjust函数,所以该时间复杂度为:O(nlog2n)
是否稳定
堆排序是不稳定的排序算法,比如有无序的序列:{3,27,36,27},所以在输出3后,最后的那个27替换到了根节点,从这里就可以看出该算法是不稳定的。
总结
堆排序的时间复杂也是为:O(nlog2n),而且是最多就是:O(nlog2n)。对于时间复杂同样是:O(nlog2n)的排序算法:快速排序和归并排序来说,它只是在最坏情况下是占优势的。具体可以参考:
堆和快排、归并的比较