引子:
堆排序是第3个比较有意思的排序(相对于 归并排序,快速排序),效率很高,且不用额外的空间。
堆的定义是:它用一个数组来存储一个完全二叉树(叶子节点只出现在最下层的树)。
例如如下的数组的10个数据:
int a[] = {3,4,9,7,5,6,10,8,2,1};
其实是被看作为:
不难观察,规律是从 a[0] 到 a[9] 按在树中层次依次排列。
3的子节点是4和9,即a[0]的子节点是a[1]和a[2]。
4的子节点是7和5,即a[1]的子节点是a[3]和a[4]。
9的子节点是6和10,即a[2]的子节点是a[5]和a[6]。
7的子节点是8和2,即a[3]的子节点是a[7]和a[8]。
5的子节点是1,即a[4]的子节点是a[9]。
以此类推,可以得出
a[n]的子节点是a[2n+1]和a[2n+2] (如果有子节点的话)。
排序思路:
堆分两种:
- 最大堆,即堆顶是数组中的最大数。
- 最小堆,即堆顶是数组中的最小数。
大顶堆有一个重要的性质就是父子点永远大于或等于两个子节点。小顶堆则相反。
假设排序的目标是从小排到大。
堆排序的第一步是建立最大堆,代码如下:
for (int i = nEnd/2 -1; i >= 0 ; i--) { HeapAdjust( ua, i, nEnd); }
主要目的就是让堆中的最大值跑到堆顶去。
void HeapAdjust(int* ua, int nStart, int nEnd)
{
int nMaxIndex = 0;
while ( ( 2*nStart + 1) < nEnd )
{
nMaxIndex = 2*nStart + 1;
if ( ( 2*nStart + 2) < nEnd)
{
//比较左子树和右子树,记录较大值的Index
if (ua[2*nStart + 1] < ua[2*nStart + 2])
{
nMaxIndex++;
}
}
//如果父结点大于子节点,则退出,否则交换
if (ua[nStart] > ua[nMaxIndex])
{
break;
}
else
{
swap( ua[nStart], ua[nMaxIndex] );
//堆被破坏,继续递归调整(注意,这里是递归的关键)
nStart = nMaxIndex;
}
}
}
这样数组经过调整后就变成了:
之前:
{3,4,9,7,5,6,10,8,2,1};
{10,8,9,7,5,6,3,4,2,1};
这样最大值10就跑到堆顶了。
堆排序的第二步是:每次把堆顶的数与堆的最后一个数交换,再让最后一个数脱离堆,然后继续建最大堆。然后如此循环,直到只剩下堆顶的数。
比如第一轮把堆顶的10与1交换,此时1在堆顶,让10从堆中脱离,让10放在数组的末尾。此时堆还有9个数,最后1个数10留在末尾。
第二轮先建堆,让9在堆顶,然后让9和2交换,此时2在堆顶,让9从堆中脱离,主9放在数组的倒数第2个位置,此时堆还有8个数,最后两个数是9,10。
如此不堆的递归,直到堆越来越小,数组越来越大,并且是排序的状态。最后堆就没有了。
代码如下:
for (int i = nEnd-1; i > 0; i--) { swap(ua[0], ua[i]); HeapAdjust(ua, 0, i); }
完整的代码如下:
#include "stdafx.h" #include "windows.h" #include "time.h" const int N = 10; int O = 0;
int* GenRandom() { srand( (unsigned)time( NULL ) ); int* a = new int[N]; for (int i = 0; i < N; i++) { a[i] = rand() ; } return a; } void swap(int& a, int& b) { int temp = 0; temp = a; a = b; b = temp; }
void HeapAdjust(int* ua, int nStart, int nEnd) { int nMaxIndex = 0; while ( ( 2*nStart + 1) < nEnd ) { nMaxIndex = 2*nStart + 1; if ( ( 2*nStart + 2) < nEnd) { //比较左子树和右子树,记录较大值的Index if (ua[2*nStart + 1] < ua[2*nStart + 2]) { nMaxIndex++; } } //如果父结点大于子节点,则退出,否则交换 if (ua[nStart] > ua[nMaxIndex]) { break; } else { swap( ua[nStart], ua[nMaxIndex] ); //堆被破坏,继续递归调整 nStart = nMaxIndex; } } for (int i = 0; i < N; i++) { printf("%d ",ua[i]); } printf(" "); //printf("%d ", O++); } void HeapSort(int* ua, int nStart, int nEnd) { for (int i = nEnd/2 -1; i >= 0 ; i--) { HeapAdjust( ua, i, nEnd); } for (int i = nEnd-1; i > 0; i--) { swap(ua[0], ua[i]); HeapAdjust(ua, 0, i); } }
SYSTEMTIME StartTime = {0}; FILETIME StartFileTime = {0}; SYSTEMTIME EndTime= {0}; FILETIME SEndFileTime= {0}; int _tmain(int argc, _TCHAR* argv[]) { //int* a = GenRandom(); int a[] = {3,4,9,7,5,6,10,8,2,1}; GetLocalTime(&StartTime); printf("timeBefore %d:%d:%d ", StartTime.wMinute, StartTime.wSecond, StartTime.wMilliseconds); HeapSort(a, 0, N); GetLocalTime(&EndTime); printf("timeAfter %d:%d:%d ", EndTime.wMinute, EndTime.wSecond, EndTime.wMilliseconds); printf("times %d ", O); return 0; }