算法 之 堆 - 创建堆

给出一个有n个元素的数组A[1...n],要创建一个包含这些元素的堆,可以这样进行:从空的堆开始,不断插入每一个元素,直到A完全被转移到堆中为止。因为插入第j个键值用时O(log j),因此用这种方法创建堆栈的时间复杂性是O(n log n)。

 

我们知道对应于堆H[1...n]的树的节点可以方便地以自顶向下、从左到右的方式从1到n编码。在这样编码之后,可以用下面的方法,把一棵n个节点的几乎完全的二叉树转换成堆H[1...n]。从最后一个节点开始(编码为n的那一个)到根节点(编码为1的节点),逐个扫描所有的节点,根据需要,每一次将以当前节点为根节点的子树转换成堆。

每一棵只有一片叶子的子树已经是一个堆,因此叶子被跳过。

当以第i个节点为根的子树不是堆时,我们就对i进行Sift-down运算一边把它转换成堆。如此进行下去,使整棵二叉树符合堆得性质。

 

上面说明了如何对树进行运算。直接对输入的数组执行同样的过程是相当容易的。

令A[1...n]是已知数组,T是对应于A的一棵几乎完全的二叉树,我们注意到 A[n/2+1],A[n/2+2],...,A[n] 它们对应于T的叶子,这样我们可以直接从A[n/2]开始调整数组,然后继续调整 A[n/2-1],A[n/2-2],...,A[1]。这样得到的数组就是我们需要的堆。

 

过程  MakeHeap

输入  n个元素的数组A[1...n]

输出  A[1...n]转换成堆

 

算法描述

for i ← n/2 downto 1

    Sift-down(A, i)

end for

 

注意这里算法描述的数组的索引都是1...n,而不是大家习惯的0...n-1。

 

下面我们来计算算法MakeHeap的运行时间

设T是对应于数组A[1...n]的一棵几乎完全的二叉树,那么由观察结论可知,T的高是k=⌊log n⌋。

令A[j]对应该树的第i层中第j个节点,当语句Sift-down(A, j)调用过程Sift-down时,重复执行的次数最多是k-i。

因为在第i层上正好有2i个节点,0≤i<k,所以每一层循环执行的总次数的上界是 (k-i)2i

总的循环执行的总次数的上界就是:

由于在过程Sift-down的每一个循环中,最多有两次元素的比较,因此元素比较的总次数是2×2n=4n。

但是在每次调用Sift-down时,都至少执行一次循环,因此元素比较的最小次数是2n/2≥n-1,这个就是元素比较的总次数的下界。

因此,MakeHeap算法需要Θ(n)时间来构造一个n元素的堆,似乎并不是之前我们认为的O(n log n)时间。

int* makeHeap(int* array, int arrayLength)
{
	if (array == NULL || arrayLength <= 0)
		return NULL;

	int heapLength = arrayLength;
	int* heap = (int*)malloc(sizeof(array) * (heapLength + 1));
	if (heap == NULL)
		return NULL;

	// heap[0]用来存储数组中堆数据的长度,堆数据heap[1]...heap[heapLength]  
	// 所以数组的实际长度是heapLength+1,我们只对从数组索引为1开始的heapLength个数据进行操作
	heap[0] = heapLength;
	for (int i = 0; i < arrayLength; i++)
	{
		heap[i + 1] = array[i];
	}

	for (int i = heapLength / 2; i >= 1; i--)
	{
		siftDown(heap, i);
	}

	return heap;
}

 

我使用的数组是这样定义的: 

const int ARRAY_LENGTH = 10;  
int array[ARRAY_LENGTH] = { 4, 3, 8, 10, 11, 13, 7, 30, 17, 26 };

// 如果用我上面写的方法创建堆,不要忘了在操作完成之后释放内存哦
int* heap = makeHeap(array, ARRAY_LENGTH);
// do something ...
free(heap);

 

更多内容请参考:

算法 之 堆 - 简介

算法 之 堆 - Sift-up和Sift-down

算法 之 堆 - 插入和删除

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值