zl程序教程

您现在的位置是:首页 >  其它

当前栏目

堆排序

堆排序
2023-09-27 14:23:52 时间

引子:

堆排序是第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("\r\n");
	//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 \r\n", StartTime.wMinute, StartTime.wSecond, StartTime.wMilliseconds);

	HeapSort(a, 0, N);

	GetLocalTime(&EndTime);
	printf("timeAfter %d:%d:%d \r\n", EndTime.wMinute, EndTime.wSecond, EndTime.wMilliseconds);
	printf("times %d \r\n", O);
	return 0;
}