`
frank-liu
  • 浏览: 1665359 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

堆排序中两种建堆方法的比较

 
阅读更多

前言

    在我的前一篇文章里已经对堆排序有了一个详细的介绍。以最大堆为例,我们实现的buildMaxHeap的方法是在将所有元素放置到一个数组中再按照maxHeapify的子流程进行调整。这里有一个前提条件就是所有的数据已经就位了。在另外一些情况下,如果数据没有完全就位,比如说要从外部的数据源读,一次读一个数据进来,我们可以采用另外一种方式来建堆,也就是maxHeapInsert方法。在这里,我们对两种建堆的方法进行详细的分析和比较。

 

buildMaxHeap方法

     buildMaxHeap方法的流程简单概括起来就是一句话,从A.length / 2一直到根结点进行maxHeapify调整。在前面的文章中对该流程有过详细的解读,这里就只是贴出来一个详细的实现:

public static void maxHeapify(int[] a, int i, int length)
{
	int l = left(i);
	int r = right(i);
	int largest = i;
	while(true)
	{
		if(l < length && a[l] > a[i])
				largest = l;
		if(r < length && a[r] > a[largest])
				largest = r;
		if(i != largest)
			swap(a, i, largest);
		else
			break;
		i = largest;
		l = left(largest);
		r = right(largest);
	}
}

public static void buildMaxHeap(int[] a)
{
	for(int i = a.length / 2; i >= 0; i--)
		maxHeapify(a, i, a.length);
}

 

运行时间分析

    粗粗来看前面buildmaxheap的时间复杂度,每次maxHeapify调整需要的时间为lg(n), 总共要遍历的元素有N/2个,所以大致的运行时间复杂度为O(nlgn).

    如果我们更进一步分析,会发现它的实际情况会更理想一点。首先一个,我们从第a.length/2个元素开始执行maxHeapify,最开始这一层的元素只有一个子结点,也就是说,就算要调整,顶多一次就搞定了,不需要走lgn这么多步。

    要做进一步的分析,我们可以先思考一下我们要建的这个完全二叉树堆的几个特性。以如下图为例:

    我们看这棵二叉树,它必须保证每一层填满之后才能去填充下一层。而且,如果从根结点开始计数,往下第i层的元素如果不是最后一层的话,这一层的元素数量为2**i(2的i次方)。这样,对于一棵高为h的二叉树,它的所有结点数目就等于前面完全填满的层元素加上最下面一层的元素。

    为什么要把他们分开来计数呢?是因为最下面一层的元素有一个变动的范围,作为一棵高度为h的树,最下面一层的元素最少可以是1,最大可以是把整层填充满,也就是2**(h+1)。这样,他们求和的结果就是最少为2**h,最大为2**(h+1)。

所以假设堆的元素数量为n的话,我们就可以推出:

 

 结合这一步分析,我们可以得到: h <= lgn < h + 1。

结论1:

我们可以发现一个n个元素的树,它的高度相当于logn(向下取整)。

 

我们再来看我们分析的第二个结论。对应树每一个高度的一层,该有多少个元素呢?假设高度为1的那一层他们的元素个数为k,那么他们的访问时间复杂度为O(k)。根据前面的分析,我们还发现一个情况,就是如果从根结点开始计数,往下第i层的元素如果不是最后一层的话,这一层的元素数量为2**i(2的i次方)。正好这个第i层就相当于树的总高度减去当前层的结点的高度。假设第i层的高度为h,那么也就是i = lgn - h。

结论2:

这一层的元素数量为:

 那么他们所有元素的运算时间总和为如下:

 根据如下公式:

 

则有:

 

 

 现在,我们发现,buildMaxHeap方法的时间复杂度实际上为O(n).

maxHeapInsert方法

     maxHeapInsert方法和前面的办法不一样。它可以假定我们事先不知道有多少个元素,通过不断往堆里面插入元素进行调整来构建堆。

它的大致步骤如下:

1. 首先增加堆的长度,在最末尾的地方加入最新插入的元素。

2. 比较当前元素和它的父结点值,如果比父结点值大,则交换两个元素,否则返回。

3. 重复步骤2.

这个过程对应的代码实现如下:

public void heapIncreaseKey(int i, int key) throws Exception
{
	if(key < a[i])
		throw new Exception("new key is small than current key");
	a[i] = key;
	while(i > 0 && a[parent(i)] < a[i])
	{
		swap(i, parent(i));
		i = parent(i);
	}
}

public void maxHeapInsert(int key) throws Exception
{
	heapSize++;
	a[heapSize - 1] = Integer.MIN_VALUE;
	heapIncreaseKey(heapSize - 1, key);
}

    这里的parent()方法是用来求当前结点的父结点。详细的实现可以参考后面附件里的代码。

    这里,我们也可以分析一下插入建堆的时间复杂度。我们先看最理想的情况,假设每次插入的元素都是严格递减的,那么每个元素只需要和它的父结点比较一次。那么其最优情况就是n。

   对于最坏的情况下,每次新增加一个元素都需要调整到它的根结点。而这个长度为lgn。那么它的时间复杂度为如下公式:

 这样,插入建堆的时间复杂度为nlgn。

 总结

    常用的建堆方法主要用于堆元素已经确定好的情况,而插入建堆的过程主要用于动态的增加元素来建堆。插入建堆的过程也常用于建立优先队列的应用。这些可以根据具体的时间情况来选取。

 

参考资料

Introduction to algorithms

  • 大小: 15.2 KB
  • 大小: 3.2 KB
  • 大小: 3.5 KB
  • 大小: 8.5 KB
  • 大小: 5.4 KB
  • 大小: 9.3 KB
  • 大小: 19.6 KB
分享到:
评论

相关推荐

    堆排序详解升序和降序Java版本

    堆排序的思路主要就是建堆和排序两部分组成。堆排序是基于⼆叉树的,那么我们⾸先得知道⼆叉树得基本特性。我们在堆排序中定义这样⼀种完整⼆叉树,其中每个结点的值都⼤于等于它的孩⼦,那么我们就称之为最⼤堆,...

    几种常见排序算法实现

    实现时:建堆:置换堆顶元素和最后一个元素,堆大小减少,保持新的堆为最大堆; 保持最大堆: 从底向上依次保持最大堆,从第一个父节点到根部。 1.6 MergeSort:拆分数组,递归实现排序,二路归并。用哨兵来阻止...

    算法导论(part1)

    6.3 建堆 6.4 堆排序算法 6.5 优先级队列 第7章 快速排序 7.1 快速排序的描述 7.2 快速排序的性能 7.3 快速排序的随机化版本 7.4 快速排序分析 7.4.1 最坏情况分析 7.4.2 期望的运行时间 第8章...

    计算机二级C语言考试题预测

    建堆排序法 (43) 在深度为5的满二叉树中,叶子结点的个数为(C) A. 32 B. 31 C. 16 D. 15 (44) 对长度为N的线性表进行顺序查找,在最坏情况下所需要的比较次数为(B) 注:要牢记 A. N+1 B. N C. (N+1)/2 D. N/2 (45) ...

    数据结构与算法.xmind

    堆排序 思想 使用大顶堆的思想来排序,每次建堆后交换 做法 总体:建堆-替换 建堆 只要左子树或右子树大于当前根节点,则替换 替换后会导致下面的子树发生了...

    二级C语言公共基础知识

    (37) 在最坏情况下,堆排序需要比较的次数为______。 答:O(nlog2n) (38) 若串s="Program",则其子串的数目是______。 答:29 (39) 一个项目具有一个项目主管,一个项目主管可管理多个项目,则实体"项目主管"与...

    算法导论(part2)

    6.3 建堆 6.4 堆排序算法 6.5 优先级队列 第7章 快速排序 7.1 快速排序的描述 7.2 快速排序的性能 7.3 快速排序的随机化版本 7.4 快速排序分析 7.4.1 最坏情况分析 7.4.2 期望的运行时间 第8章...

    Java 语言基础 —— 非常符合中国人习惯的Java基础教程手册

    对象中,所以,访问对象中的数据只有一种途径,那就是利用一个公开的接口。 实际上,封装在程序和数据之间设置了一道栅栏,它可以阻止一部分的设计错误,不至 于涉足应用程序其他部分的数据。 2.2.3 消息 ...

    c语言数据结构算法演示(Windows版)

     堆排序(HeapSort)  快速排序(QuickSort)  锦标赛排序(Tournament) (3)其他  快速地址排序(QkAddrst)  基数排序(RadixSort) 13. 外部排序 (1)多路平衡归并排序(K-Merge) (2)置换-选择排序...

    用c描述的数据结构演示软件

     堆排序(HeapSort)  快速排序(QuickSort)  锦标赛排序(Tournament) (3)其他  快速地址排序(QkAddrst)  基数排序(RadixSort) 13. 外部排序 (1)多路平衡归并排序(K-Merge) (2)置换-选择排序...

    数据结构演示软件

     堆排序(HeapSort)  快速排序(QuickSort)  锦标赛排序(Tournament) (3)其他  快速地址排序(QkAddrst)  基数排序(RadixSort) 13. 外部排序 (1)多路平衡归并排序(K-Merge) (2)置换-选择...

    C#微软培训资料

    11.2 方法中的参数.125 11.3 静态和非静态的方法.129 11.4 方法的重载.130 11.5 操作符重载.134 11.6 小 结.137 第十二章 域 和 属 性 .139 12.1 域 .139 12.2 属 性 .143 12.3 小 结 .146 第十三...

    你必须知道的495个C语言问题

    *2.5 在C语言中是否有模拟继承等面向对象程序设计特性的好方法? 2.6 为什么声明externf(structx*p);给我报了一个晦涩难懂的警告信息? 2.7 我遇到这样声明结构的代码:structname{intnamelen;charnamestr[1];}...

    《你必须知道的495个C语言问题》

    *2.5 在C语言中是否有模拟继承等面向对象程序设计特性的好方法? 22 2.6 为什么声明extern f(struct x *p); 给我报了一个晦涩难懂的警告信息? 23 2.7 我遇到这样声明结构的代码:struct name {int namelen; ...

Global site tag (gtag.js) - Google Analytics