算法导论第六章 堆排序


原文连接诶:http://blog.csdn.net/v_JULY_v/article/details/6198644

基本算是把算法导论的内容原版照抄。

一、堆排序算法的基本特性
时间复杂度:O(nlgn)...
//等同于归并排序
最坏:O(nlgn)
空间复杂度:O(1).
不稳定。

二、堆与最大堆的建立
要介绍堆排序算法,咱们得先从介绍堆开始,然后到建立最大堆,最后才讲到堆排序算法。

2.1、堆的介绍
    如下图,

a),就是一个堆,它可以被视为一棵完全二叉树。
每个堆对应于一个数组b),假设一个堆的数组A,
我们用length[A]表述数组中的元素个数,heap-size[A]表示本身存放在A中的堆的元素个数。
当然,就有,heap-size[A]<=length[A]。

    树的根为A[1],i表示某一结点的下标,
则父结点为PARENT(i),左儿子LEFT[i],右儿子RIGHT[i]的关系如下:

PARENT(i)
   return |_i/2_|

LEFT(i)
   return 2i

RIGHT(i)
   return 2i + 1

    二叉堆根据根结点与其子结点的大小比较关系,分为最大堆和最小堆。
最大堆:
根以外的每个结点i都不大于其根结点,即根为最大元素,在顶端,有
     A[PARENT(i)] (根)≥ A[i] ,

最小堆:
根以外的每个结点i都不小于其根结点,即根为最小元素,在顶端,有
     A[PARENT(i)] (根)≤ A[i] .

在本节的堆排序算法中,我们采用的是最大堆;最小堆,通常在构造最小优先队列时使用。

    由前面,可知,堆可以看成一棵树,所以,堆的高度,即为树的高度,O(lgn)。
所以,一般的操作,运行时间都是为O(lgn)。

具体,如下:
The MAX-HEAPIFY:O(lgn)  这是保持最大堆的关键.
The BUILD-MAX-HEAP:线性时间。在无序输入数组基础上构造最大堆。
The HEAPSORT:O(nlgn) time, 堆排序算法是对一个数组原地进行排序.
The MAX-HEAP-INSERT, HEAP-EXTRACT-MAX, HEAP-INCREASE-KEY, HEAP-MAXIMUM:O(lgn)。
可以让堆作为最小优先队列使用。 

 

2.2.1、保持堆的性质(O(lgn))

     为了保持最大堆的性质,我们运用MAX-HEAPIFY操作,作调整递归调用此操作,使i为根的子树成为最大堆。

MAX-HEAPIFY算法,如下所示(核心):

MAX-HEAPIFY(A, i)
 1 l ← LEFT(i)
 2 r ← RIGHT(i)
 3 if l ≤ heap-size[A] and A[l] > A[i]
 4    then largest ← l
 5    else largest ← i
 6 if r ≤ heap-size[A] and A[r] > A[largest]
 7    then largest ← r
 8 if largest ≠ i
 9    then exchange A[i] <-> A[largest]
10         MAX-HEAPIFY(A, largest) 

     如上,首先第一步,在对应的数组元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一个,将其下标存储在largest中。如果A[i]已经就是最大的元素,则程序直接结束。否则,i的某个子结点为最大的元素,将其,即A[largest]与A[i]交换,从而使i及其子女都能满足最大堆性质。下标largest所指的元素变成了A[i]的值,会违反最大堆性质,所以对largest所指元素调用MAX-HEAPIFY。如下,是此MAX-HEAPIFY的演示过程下图是把4调整到最底层,一趟操作,但摸索的时间为LogN):

 

     由上图,我们很容易看出,初始构造出一最大堆之后,在元素A[i],即16,大于它的俩个子结点4、10,满足最大堆性质。所以,i下调指向着4,小于,左子14,所以,调用MAX-HEAPIFY,4与其子,14交换位置。但4处在了14原来的位置之后,4小于其右子8,又违反了最大堆的性质,所以再递归调用MAX-HEAPIFY,将4与8,交换位置。于是,满足了最大堆性质,程序结束。

2.2.2、MAX-HEAPIFY的运行时间
   MAX-HEAPIFY作用在一棵以结点i为根的、大小为n的子树上时,其运行时间为调整元素A[i]、A[LEFT(i)],A[RIGHT(i)]的关系时所用时间为O(1),再加上,对以i的某个子结点为根的子树调用MAX-HEAPIFY所需的时间,且i结点的子树大小至多为2n/3,所以,MAX-HEAPIFY的运行时间为
     T (n) ≤ T(2n/3) + Θ(1).

我们,可以证得此式子的递归解为T(n)=O(lgn)。具体证法,可参考算法导论第6章之6.2节,这里,略过。

 

2.3.1、建堆(O(N))

BUILD-MAX-HEAP(A)
1  heap-size[A] ← length[A]
2  for i ← |_length[A]/2_| downto 1
3       do MAX-HEAPIFY(A, i)    //建堆,怎么建列?原来就是不断的调用MAX-HEAPIFY(A, i)来建立最大堆。

BUILD-MAX-HEAP通过对每一个其它结点,都调用一次MAX-HEAPIFY,
来建立一个与数组A[1...n]相对应的最大堆。A[(|_n/2_|+1) ‥ n]中的元素都是树中的叶子。
因此,自然而然,每个结点,都可以看作一个只含一个元素的堆。

关于此过程BUILD-MAX-HEAP(A)的正确性,可参考算法导论 第6章之6.3节。
下图,是一个此过程的例子下图是不断的调用MAX-HEAPIFY操作,把所有的违反堆性质的数都要调整,共N趟操作,然,摸索时间最终精确为O(N)):

 

2.3.2、BUILD-MAX-HEAP的运行时间
       因为每次调用MAX-HEAPPIFY的时间为O(lgn),而共有O(n)次调用,所以BUILD-MAX-HEAP的简单上界为O(nlgn)。算法导论一书提到,尽管这个时间界是对的,但从渐进意义上,还不够精确。

       那么,更精确的时间界,是多少列?
由于,MAX-HEAPIFY在树中不同高度的结点处运行的时间不同,且大部分结点的高度都比较小,
而我们知道,一n个元素的堆的高度为|_lgn_|(向下取整),且在任意高度h上,至多有|-n/2^h+1-|(向上取整)个结点。

因此,MAX-HEAPIFY作用在高度为h的结点上的时间为O(h),所以,BUILD-MAX-HEAP的上界为:O(n)。具体推导过程,略。
 

三、堆排序算法

     所谓的堆排序,就是调用上述俩个过程:一个建堆的操作、BUILD-MAX-HEAP,一个保持最大堆的操作、MAX-HEAPIFY。详细算法如下:

HEAPSORT(A)    //n-1次调用MAX-HEAPIFY,所以,O(n*lgn)
BUILD-MAX-HEAP(A)      //建最大堆,O(n)
2 for i ← length[A] downto 2
3    do exchange A[1] <-> A[i]
4       heap-size[A] ← heap-size[A] - 1
5       MAX-HEAPIFY(A, 1)    //保持堆的性质,O(lgn) 

     如上,即是堆排序算法的完整表述。下面,再贴一下上述堆排序算法中的俩个建堆、与保持最大堆操作:
BUILD-MAX-HEAP(A)  //建堆
1  heap-size[A] ← length[A]
2  for i ← |_length[A]/2_| downto 1
3       do MAX-HEAPIFY(A, i)


MAX-HEAPIFY(A, i)     //保持最大堆
 1 l ← LEFT(i)
 2 r ← RIGHT(i)
 3 if l ≤ heap-size[A] and A[l] > A[i]
 4    then largest ← l
 5    else largest ← i
 6 if r ≤ heap-size[A] and A[r] > A[largest]
 7    then largest ← r
 8 if largest ≠ i
 9    then exchange A[i] <-> A[largest]
10         MAX-HEAPIFY(A, largest) 

 

 以下是,堆排序算法的演示过程(通过,顶端最大的元素与最后一个元素不断的交换,交换后又不断的调用MAX-HEAPIFY重新维持最大堆的性质,最后,一个一个的,从大到小的,把堆中的所有元素都清理掉,也就形成了一个有序的序列。这就是堆排序的全部过程。):

 

 

上图中,a->b,b->c,....之间,都有一个顶端最大元素与最小元素交换后,调用MAX-HEAPIFY的过程,我们知道,此MAX-HEAPIFY的运行时间为O(lgn),而要完成整个堆排序的过程,共要经过O(n)次这样的MAX-HEAPIFY操作。所以,才有堆排序算法的运行时间为O(n*lgn)。

添加一份源码实现:


#include <iostream>
using namespace std;
 
// 输出当前堆的排序状况
void PrintArray(int data[], int size)
{
    for (int i=1; i<=size; ++i)
        cout <<data[i]<<"  ";
    cout<<endl;
}
 
// 堆化,保持堆的性质
// MaxHeapify让a[i]在最大堆中"下降",
// 使以i为根的子树成为最大堆
void MaxHeapify(int *a, int i, int size)
{
	int lt = 2*i, rt = 2*i+1;
	int largest;
	if(lt <= size && a[lt] > a[i])
		largest = lt;
	else
		largest = i;
	if(rt <= size && a[rt] > a[largest])
		largest = rt;
	if(largest != i)
	{
		int temp = a[i];
		a[i] = a[largest];
		a[largest] = temp;
		MaxHeapify(a, largest, size);
	}
}
 
// 建堆
// 自底而上地调用MaxHeapify来将一个数组a[1..size]变成一个最大堆
//
void BuildMaxHeap(int *a, int size)
{
	for(int i=size/2; i>=1; --i)
		MaxHeapify(a, i, size);
}
 
// 堆排序
// 初始调用BuildMaxHeap将a[1..size]变成最大堆
// 因为数组最大元素在a[1],则可以通过将a[1]与a[size]互换达到正确位置
// 现在新的根元素破坏了最大堆的性质,所以调用MaxHeapify调整,
// 使a[1..size-1]成为最大堆,a[1]又是a[1..size-1]中的最大元素,
// 将a[1]与a[size-1]互换达到正确位置。
// 反复调用Heapify,使整个数组成从小到大排序。
// 注意: 交换只是破坏了以a[1]为根的二叉树最大堆性质,它的左右子二叉树还是具备最大堆性质。
//        这也是为何在BuildMaxHeap时需要遍历size/2到1的结点才能构成最大堆,而这里只需要堆化a[1]即可。
void HeapSort(int *a, int size)
{
	BuildMaxHeap(a, size);
	PrintArray(a, size);
 
	int len = size;
	for(int i=size; i>=2; --i)
	{
		int temp = a[1];
		a[1] = a[i];
		a[i] = temp;
		len--;
		MaxHeapify(a, 1, len);
		cout << "中间过程:";
		PrintArray(a, size);
	}
 
}
 
int main()
{
	int size;
	int arr[100];
	cout << "Input the num of elements:\n";
	cin >> size;
	cout << "Input the elements:\n";
	for(int i=1; i<=size; ++i)
		cin >> arr[i];
	cout << endl;
    HeapSort(arr, size);
	cout << "最后结果:";
    PrintArray(arr, size);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
算法导论第三版中文版 pdf高清版 在有关算法的书中,有一些叙述非常严谨,但不够全面;另一些涉及了大量的题材,但又缺乏严谨性。算法导论第三版中文版将严谨性和全面性融为一体,深入讨论各类算法,并着力使这些算法的设计和分析能为各个层次的读者接受。全书各章自成体系,可以作为独立的学习单元;算法以英语和伪代码的形式描述,具备初步程序设计经验的人就能看懂;说明和解释力求浅显易懂,不失深度和数学严谨性。全书选材经典、内容丰富、结构合理、逻辑清晰,对本科生的数据结构课程和研究生的算法课程都是非常实用的教材,在IT专业人员的职业生涯中,算法导论第三版也是一本案头必备的参考书或工程实践手册。 第3版的主要变化 1、新增了van Emde Boas树和多线程算法,并且将矩阵基础移至附录。 2、修订了递归式(现在称为“分治策略”)那一章的内容,更广泛地覆盖分治法。 3、移除两章很少讲授的内容:二项堆和排序网络。 4、修订了动态规划和贪心算法相关内容。 5、流网络相关材料现在基于边上的全部流。 6、由于关于矩阵基础和Strassen算法的材料移到了其他章,矩阵运算这一章的内容所占篇幅更小。 7、修改了对Knuth-Morris-Pratt字符串匹配算法的讨论。 8、新增100道练习和28道思考题,还更新并补充了参考文献。
第一部分(Part I) 基础(Foundations) 第一章 计算中算法的角色(The Role of Algorithms in Computing) 第二章 开始(Getting Started) 第三章 函数的增长率(Growth of Functions) 第四章 递归(Recurrences) 第五章 概率分析与随机化算法(Probabilistic Analysis and Randomized Algorithms) 第二部分(Part II) 排序与顺序统计(Sorting and Order Statistics) 第六章 堆排序(Heapsort) 第七章快速排序(Quicksort) 第八章 线性时间中的排序(Sorting in Linear Time) 第九章 中值与顺序统计(Medians and Order Statistics) 第三部分(Part III) 数据结构(Data Structures) 第十章 基本的数据结构(Elementary Data Structures) 第十一章 散列表(Hash Tables) 第十二章 二叉查找树(Binary Search Trees) 第十三章 红-黑树(Red-Black Trees) 第十四章 扩充的数据结构(Augmenting Data Structures) 第四部分(Part IV) 高级的设计与分析技术(Advanced Design and Analysis Techniques) 第十五章 动态规划(Dynamic Programming) 第十六章 贪婪算法(Greedy Algorithms) 第十七章 分摊分析(Amortized Analysis) 第五部分(Part V) 高级的数据结构(Advanced Data Structures) 第十八章 B-树(B-Trees) 第十九章 二项式堆(Binomial Heaps) 第二十章 斐波纳契堆(Fibonacci Heaps) 第二十一章 不相交集的数据结构(Data Structures for Disjoint Sets) 第六部分(Part VI) 图算法(Graph Algorithms) 第二十二章 基本的图算法(Elementary Graph Algorithms) 第二十三章 最小生成树(Minimum Spanning Trees) 第二十四章单源最短路径(Single-Source Shortest Paths) 第二十五章 全对的最短路径(All-Pairs Shortest Paths) 第二十六章 最大流(Maximum Flow) 第七部分(Part VII) 精选的主题(Selected Topics) 第二十七章 排序网络(Sorting Networks) 第二十八章矩阵运算(Matrix Operations) 第二十九章 线性规划(Linear Programming) 第三十章 多项式与快速傅里叶变换(Polynomials and the FFT) 第三十一章 数论算法(Number-Theoretic Algorithms) 第三十二章 字符串匹配(String Matching) 第三十三章 计算几何学(Computational Geometry) 第三十四章 NP-完备性(NP-Completeness) 第三十五章 近似算法(Approximation Algorithms) 第八部分(Part VIII) 附录:数学背景(Mathematical Background) 索引(Index)

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值