首先来看一下简单选择排序:
思想:在未排序的序列中选出最小的元素和当前序列的第一个元素互换,然后在剩下的元素中选出一个与剩下元素的第一个位置互换,依次交换,最后就会形成一个从小到大的已排序序列。
void simpleSelectSort(int *a, int N){
int i,j,min;
for (int i=0; i<N; i++) {
//找到最小元素 然后和i交换位置
min = i;
for (int j=i+1; j<N; j++) {
if (a[min]>a[j]) {
min = j;
}
}//循环完一遍 已找到最小元素
//交换
a[i] = a[i]^a[min];
a[min] = a[min]^a[i];
a[i] = a[i]^a[min];
}
}
简单选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个 元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而更小的元素又出现在一个和当前元素相等的元素后面,那么 交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。
仔细观察可以发现,简单选择排序的瓶颈部分在于找到最小元素的过程和不停的交换过程。每次取最小的元素这一个步骤,我们能够联想到“最小堆”这个数据结构,使用这个数据结构可以帮我们找到最小元素。不知道什么是最小堆?看这里。
堆排序:
堆排序(HeapSort)是由1991年计算机先驱奖获得者、斯坦福大学计算机科学系教授 罗伯特·弗洛伊德(Robert W.Floyd) 和 威廉姆斯(J.Williams) 在1964年共同提出的。
堆排序是指利用堆这种数据结构所设计的一种排序算法,一般情况下堆排序是用数组的方式来实现。
思想:
利用最小堆(或最大堆)输出堆顶元素,即最小值(或最大值),将剩余的元素重新生成最小堆(或最大堆),继续输出堆顶元素,重复此过程,直到全部元素都已输出,得到的输出元素序列即为有序序列。
一种实现方法是:额外开辟一个数组,然后每次输出最小堆的堆顶元素保存到该数组中,然后重新调整为最小堆,然后输出堆顶元素,如此循环,直到最小堆无元素,最后把该数组元素复制回原来的数组。但是这种方式,需要额外的空间消耗。我们一般使用更巧妙一点的方式。
另一种:将待排序列调整成最大堆,然后每次将堆顶元素(最大值)和当前堆的最后一个元素交换,把除去堆末尾已经交换过的值以外的元素重新调整为最大堆,然后如此循环,最后就会形成从小到大的已排序序列。
需要注意的:因为这里的元素是从0开始存放的,所以和最大堆的代码不同,对于第i个结点,其左孩子的编号是2i+1,右孩子编号是2i+2,父亲结点的编号是|(i-1)/2|。
void percDownHeap(int *a, int p, int N){
int parent,child;
int top = a[p];
for (parent=p; (parent*2+1)<=N; parent=child) {
child=parent*2+1;
if (child!=N-1 && a[child]<a[child+1]) {
child++;
if (a[child]>top) {
a[parent] = a[child];
}else
break;
}
}
a[parent] = top;
}
void heap_sort(int *a, int N){
for (int i=N/2-1; i>0; i--) {//先排成最大堆(从最后一个父节点向上遍历)
percDownHeap(a, i, N);
}
for (int i=N-1; i>0; i--) {
swap(&a[0], &a[i]);//将当前堆最大值移动到数组最后
percDownHeap(a, 0, i);//剩下的元素调整为最大堆(代码同最大堆删除元素)
}
}
我们知道堆的结构是节点i的孩子为2*i和2*i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n /2-1, n/2-2, ...1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序是不稳定的排序算法。