五、堆排序
1、算法描述
堆排序是一种树形选择排序,是对直接选择排序的有效改进。堆的定义如下:具有n个元素的序列(h1,h2,...,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个堆,这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
2、堆排序过程
既然是堆排序,自然需要先建立一个堆,而建堆的核心内容是调整堆,使二叉树满足堆的定义(每个节点的值都不大于其父节点的值)。调堆的过程应该从最后一个非叶子节点开始,假设有数组A = {1, 3, 4, 5, 7, 2, 6, 8, 0}。那么调堆的过程如下图,数组下标从0开始,A[3] = 5开始。分别与左孩子和右孩子比较大小,如果A[3]最大,则不用调整,否则和孩子中的值最大的一个交换位置,在图1中是A[7] > A[3] > A[8],所以A[3]与A[7]对换,从图1.1转到图1.2。
所以建堆的过程是:
void BuildHeap(int A[], int hLen){
int i;
int begin = hLen/2 - 1;
//最后一个非叶子节点
for (i = begin; i >= 0; i--){
AdjustHeap(A, hLen, i);
}
}
调堆:如果初始数组是非降序排序,那么就不需要调堆,直接就满足堆的定义,此为最好情况,运行时间为Θ(1);如果初始数组是如图1.5,只有A[0] = 1不满足堆的定义,经过与子节点的比较调整到图1.6,但是图1.6仍然不满足堆的定义,所以要递归调整,一直到满足堆的定义或者到堆底为止。如果递归调堆到堆底才结束,那么是最坏情况,运行时间为O(h) (h为需要调整的节点的高度,堆底高度为0,堆顶高度为floor(logn) )。
建堆完成之后,堆如图1.7是个大根堆。将A[0] = 8 与 A[heapLen-1]交换,然后heapLen减一,如图2.1,然后AdjustHeap(A, heapLen-1, 0),如图2.2。如此交换堆的第一个元素和堆的最后一个元素,然后堆的大小heapLen减一,对堆的大小heapLen的堆进行调整,如此循环,直到heapLen==1为止,最后结果如图3所示
3、算法代码
#include<iostream>
using namespace std;
int LeftChild(int i);
int RightChild(int i);
void BuildHeap(int A[], int hLen);
void AdjustHeap(int A[], int hLen, int i) ;
void HeapSort(int A[], int aLen);
int n,*a;
void main(){
cout<<"请输入要排序的数字序列的长度:";
cin>>n;
cout<<"请输入需要排序的数字序列:";
a = new int[n];
for(int i = 0; i < n; i++){
cin>>a[i];
}
HeapSort(a,n);
cout<<"排序结果是:";
for(int i = 0; i < n; i++){
cout<<a[i]<<" ";
}
cout<<endl;
delete []a;
}
/* 输入:数组A,堆的大小hLen
功能:建堆 */
void BuildHeap(int A[], int hLen){
int i;
int begin = hLen/2 - 1;
//最后一个非叶子节点
for (i = begin; i >= 0; i--){
AdjustHeap(A, hLen, i);
}
}
/* 输入:数组A,堆的长度hLen,以及需要调整的节点i
功能:调堆 */
void AdjustHeap(int A[], int hLen, int i) {
int left = LeftChild(i); //节点i的左孩子
int right = RightChild(i); //节点i的右孩子节点
int largest = i;
int temp;
while(left < hLen || right < hLen) {
if (left < hLen && A[largest] < A[left]) {
largest = left;
}
if (right < hLen && A[largest] < A[right]){
largest = right;
}
//如果最大值不是父节点
if (i != largest) {
temp = A[largest];
//交换父节点和和拥有最大值的子节点交换
A[largest] = A[i];
A[i] = temp;
i = largest; //新的父节点,以备迭代调堆
left = LeftChild(i); //新的子节点
right = RightChild(i);
} else {
break;
}
}
}
void HeapSort(int A[], int aLen) {
int hLen = aLen;
int temp;
BuildHeap(A, hLen); //建堆
while (hLen > 1) {
temp = A[hLen-1]; //交换堆的第一个元素和堆的最后一个元素
A[hLen-1] = A[0];
A[0] = temp;
hLen--; //堆的大小减一
AdjustHeap(A, hLen, 0); //调堆
}
}
int LeftChild(int i){
return 2*i;
}
int RightChild(int i){
return 2*i+1;
}