堆排序原理及c语言实现

很多同学在开始学排序算法的时候都会觉得堆排序是比较难的,其实对于堆排序,只要理解了两点,应该就可以掌握堆排序了,下面说说要理解哪两点以及c语言的实现。

第一点:堆的特性。

下面的堆的特性定义来自算法导论:

最大堆特性是指除了跟以外的每个结点i,有A[PARENT[i]]>=A[i],即每个结点的值至多是和其父结点的值一样大。

最小堆特性是指出了跟以外的每个结点i,有A[PARENT[i]]<=A[i],即每个结点的值至少是和其父结点的值一样大。

也就是说,最大堆的父结点都大于等于其子结点,最小堆的父结点都小于等于其子结点。

第二点:数组下标和堆结点的关系。

对于有n个元素的数组a[n],其下标和堆结点的关系为:

父结点a[parent]的左右子结点分别是a[parent*2+1],a[parent*2+2], 0<= parent <= n/2-1;

数组下标从大到小看,下标为n/2-1的结点为堆的第一个非叶子结点。

假设我们现在有一个数组a[10] = {4,1,3,2,16,9,10,14,8,7},对应的二叉树为:


树结点外的数字就是该结点对应的数组下标。

跟结点4的下标为0,其左子结点1的下标为1,1 = 0 * 2 + 1;

其右子结点3的下标为2,2 = 0 * 2 + 2;

1的左子结点2的下标,3 = 1 * 2  + 1;右子结点16的下标 4 = 1 * 2 + 2;

3的左子结点9的下标,5 = 2 * 2 + 1; 右子结点10的下标 6 = 2 * 2 + 2;

2的左子结点14的下标,7 = 3 * 2 + 1;右子结点8的下标 8 = 3 * 2 + 2;

16的左子结点7的下标,9 = 4 * 2 + 1;

以最大堆为例,堆排序的过程就是将数组建立成一个最大堆,每次把跟结点取出来,就是最大的一个数,所以遍历完整个数组后,其算法复杂度就是O(n)*O(logn),其中O(n)是遍历数组的算法复杂度,O(logn)是调整堆的算法复杂度。

下面是c语言的实现:

void swap(int *a, int *b)
{
	int temp;
	temp = *a;
	*a = *b;
	*b = temp;
}


void buildMaxHeap(int a[], int parentIndex, int lastIndex)
{
	int left;
	int right;
	int max;
	left = parentIndex * 2 + 1;	/* 左子结点下标 */
	right = parentIndex * 2 + 2;	/* 右子结点下标 */
	while (left <= lastIndex)
	{
		max = left;		/* 先假设左子结点为最大 */


		/* 如果存在右子结点 */
		if (right <= lastIndex)
		{
			if (a[left] < a[right])
			{
				max = right;	/* 右子结点比左子结点要大 */
			}
		}


		/* 如果父结点比子结点要小,那么子结点上升为父结点,
		*  原先的父结点下降为子结点,继续重复调整 
		*/
		if (a[parentIndex] < a[max])
		{
			swap(&a[parentIndex], &a[max]);
			parentIndex = max;
		}
		else
		{
			return;	/* 父结点比子结点都大,符合最大堆的特性,返回 */
		}


		/* 继续往下调整 */
		left = parentIndex * 2 + 1;
		right = parentIndex * 2 + 2;
	}
}


/* n是数组元素的个数 */
void heapSort(int a[], int n)
{
	int i;
	int unsort;
	/* 先将数组建立成一个最大堆*/
	for (i = n / 2 - 1; i >= 0; i--)
	{
		/* 从最后一个非叶子结点开始调整 */
		buildMaxHeap(a, i, n - 1);
	}
	swap(&a[0], &a[n - 1]);
	
	/* 遍历数组,每次取一个最大的,然后调整堆 */
	for (unsort = n - 1; unsort > 0; unsort--)
	{
		buildMaxHeap(a, 0, unsort-1);
		swap(&a[0], &a[unsort-1]);
	}
}


int main()
{
	int i;
	int a[] = {4,1,3,2,16,9,10,14,8,7};
	heapSort(a, 10);
	for (i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");


	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值