算法—二叉堆

实现栈或是队列与实现优先队列的最大不同在于对性能的要求。对于栈和队列,我们的实现能够在常数时间内完成所有操作;而对于优先队列,插入元素和删除最大元素这两个操作之一在最坏情况下需要线性时间来完成。我们接下来要讨论的基于数据结构堆的实现能够保证这两种操作都能更快地执行。

1.堆的定义

数据结构二叉堆能够很好地实现优先队列的基本操作。在二叉堆的数组中,每个元素都要保证大于等于另两个特定位置的元素。相应地,这些位置的元素又至少要大于等于数组中的另两个元素,以此类推。如果我们将所有元素画成一棵二叉树,将每个较大元素和两个较小的元素用边连接就可以很容易看出这种结构。

定义:当一棵二叉树的每个结点都大于等于它的两个子结点时,它被称为堆有序。

相应地,在堆有序的二叉树中,每个结点都小于等于它的父结点(如果有的话)。从任意结点向上,我们都能得到一列非递减的元素;从任意结点向下,我们都能得到一列非递增的元素。

命题:根结点是堆有序的二叉树中的最大结点

证明:根据树的性质归纳可得

二叉堆表示法

如果我们用指针来表示堆有序的二叉树,那么每个元素都需要三个指针来找到它的上下结点(父结点和两个子结点各需要一个)。如下图所示。完全二叉树只用数组而不需要指针就可以表示。具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子结点的子结点则分别在位置4、5、6和7,以此类推。

定义:二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级储存(不使用数组的第一个位置)

简单起见,在下文中我们将二叉堆简称为堆。在一个堆中,位置k的结点的父结点的位置为k/2,而它的两个子结点的位置则分别为2k和2k+1。这样在不使用指针的情况下我们也可以通过计算数组的索引在树中上下移动:从a[k]向上一层就令k等于k/2,向下一层则令k等于2k或2k+1.

用数组(堆)实现的完全二叉树的结构是很严格的,但它的灵活性已经足以让我们高效地实现优先队列。用它们我们将能实现对数级别的插入元素和删除最大元素的操作。利用在数组中无需指针即可沿树上下移动的便利和以下性质,算法保证了对数复杂度的性能。

命题:一棵大小为N的完全二叉树的高度为lgN

证明:通过归纳很容易可以证明这一点,且当N达到2的幂时树的高度会加1

2.堆的算法

堆的操作会首先进行一些简单的改动,打破堆的状态,然后再遍历堆并按照要求将堆的状态恢复。我们称这个过程叫做堆的有序化。在有序化的过程中我们会遇到两种情况。当某个结点的优先级上升(或是在堆底加入一个新的元素)时,我们需要由下至上恢复堆的顺序。当某个结点的优先级下降(例如将根结点替换为一个较小的元素)时,我们需要由上至下恢复堆的顺序。首先,我们会学习如何实现这两种辅助操作,然后再用它们实现插入元素和删除最大元素的操作。

由下至上的堆有序化(上浮)

private void swim(int k) {
        while (k > 1 && less(k/2, k)) {
            exch(k, k/2);
            k = k/2;
        }
    }

如果堆的有序状态因为某个结点变得比它的父结点更大而被打破,那么我们就需要通过交换它和它的父结点来修复堆。交换后,这个结点比它的两个子结点都大(一个是曾经的父结点,另一个比它更小,因为它是曾经父结点的子结点),但这个结点仍然可能比它现在的父结点更大。我们可以一遍遍地用同样的办法恢复秩序,将这个结点不断向上移动直到我们遇到了一个更大的父结点。只要记住位置k的结点的父结点的位置是k/2,这个过程实现起来很简单。swim()方法中的循环可以保证只有位置k上的结点大于它的父结点时堆的有序状态才会被打破。因此只要该结点不再大于它的父结点,堆的有序状态就恢复了。如下图

由上至下的堆有序化(下沉)

private void sink(int k) {
        while (2*k <= N) {
            int j = 2*k;
            if (j < N && less(j, j+1)) j++;
            if (!less(k, j)) break;
            exch(k, j);
            k = j;
        }
    }

如果堆的有序状态因为某个结点变得比它的两个子结点或是其中之一更小了而被打破了,那么我们可以通过将它和它的两个子结点中的较大者交换来恢复堆。交换可能会在子结点处继续打破堆的有序状态,因此我们需要不断地用相同的方式将其修复,将结点向下移动直到它的子结点都比它更小或是到达了堆的底部。由位置为k的结点的子结点位于2k和2k+1可以直接得到对应的代码。

sink()和swim()方法是高效实现优先队列API的基础,原因如下。

插入元素。我们将新元素加到数组末尾,增加堆的大小并让这个新元素上浮到合适的位置(如下图左半部分所示)。

删除最大元素。我们从数组顶端删去最大的元素并将数组的最后一个元素放到顶端,减小堆的大小并让这个元素下沉到合适的位置(如下图右半部分所示)。

基于堆的优先队列算法解决了我们在开始时提出的一个基本问题:它对优先队列API的实现能够保证插入元素和删除最大元素这两个操作的用时和队列的大小仅成对数关系。

源码下载

时间: 2024-10-12 22:45:03

算法—二叉堆的相关文章

堆排序:什么是堆?什么是最大堆?二叉堆是什么?堆排序算法是怎么样的?PHP如何实现堆排序?

本文标签:  堆排序 php php算法 堆排序算法 二叉堆 数据结构 REST   服务器 什么是堆 这里的堆(二叉堆),指得不是堆栈的那个堆,而是一种数据结构. 堆可以视为一棵完全的二叉树,完全二叉树的一个"优秀"的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示,每一个结点对应数组中的一个元素. 数组与堆之间的关系 二叉堆一般分为两种:最大堆和最小堆. 什么是最大堆 堆中每个父节点的元素值都大于等于其孩子结点(如果存在),这样的堆就是一个最大堆 因此,最大堆中的

算法学习 - 优先队列的二叉堆实现

PriorityQuenue 优先队列就是作业调度类的ADT,这里用二叉堆来实现. 优先队列最少有两个操作:插入(Insert)和删除最小者(DeleteMin). 插入操作图解: 图片来源:www.educity.cn 删除操作图解: 图片来源:www.cfanz.cn 代码实现: // // main.cpp // binaryHeap // // Created by Alps on 14-8-17. // Copyright (c) 2014年 chen. All rights rese

普林斯顿公开课 算法4-2:二叉堆

二叉树 介绍二叉堆之前首先介绍二叉树.二叉树有一个根节点,节点下又有两个子节点.完全二叉树是指一个二叉树树除了最底层,其他层都是完全平衡的. 完全二叉树最基本的性质就是它的高度是 floor(lgN). 二叉堆 二叉堆是完全二叉树的一种,每个节点对应一个数值,而且这个数值都大于等于它子节点的数值. 下图是一个二叉堆. 二叉堆的储存 由于二叉堆是完全二叉树,所以它可以用一个数组进行储存.所以不需要创建节点对象,再建立节点之间的连接.这样节省了很多开销. 用数组a[]表示一个二叉堆有以下特性: a[

《Algorithms算法》笔记:优先队列(2)——二叉堆

二叉堆 1 二叉堆的定义 堆是一个完全二叉树结构(除了最底下一层,其他层全是完全平衡的),如果每个结点都大于它的两个孩子,那么这个堆是有序的. 二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级存储(不用数组的第一个位置) 2 二叉堆的性质 最大的元素在a[1] (root结点) 每个k的父亲在k/2 每个k的孩子在k*2和k*2+1 3 二叉堆的操作 3.1 上浮(孩子大于父亲)--对应插入操作 循环,每次比较自己和父亲,如果比父亲大就交换,直到root. 3.2 插入 先把元

EOJ 1848 你是ACM吗? 用二叉堆优化dijkstra + spfa算法的学习

Description  随着中国经济的腾飞,中国的物流产业迎来了发展的春天.特别是在上海这样一个拥有广阔国内腹地的国际化大都市,物流业以空前的速度膨胀. 当然是大蛋糕就会吸引许多馋嘴猫,馋嘴猫多了就会有残酷的竞争.当大量资金流入物流产业时,KOP 集团为了稳坐在国内物流业的第一把交椅,决定对现行的运输方案进行改良,以减少自己的成本同时使其它竞争者知难而退. 作为世界100强的KOP集团当然知道要找到最优运输方案,肯定得靠数学和算法很好的软件工程师,于是他们理所当然地找到华东师范大学软件学院.决

笔试算法题(46):简介 - 二叉堆 &amp; 二项树 &amp; 二项堆 &amp; 斐波那契堆

二叉堆(Binary Heap) 二叉堆是完全二叉树(或者近似完全二叉树):其满足堆的特性:父节点的值>=(<=)任何一个子节点的键值,并且每个左子树或者右子树都是一 个二叉堆(最小堆或者最大堆):一般使用数组构建二叉堆,对于array[i]而言,其左子节点为array[2*i],其右子节点为 array[2*i+1]:二叉堆支持插入,删除,查找最大(最小)键值的操作,但是合并二叉堆的复杂度较高,时间复杂度为O(N):但是二项堆或者斐波 那契堆则仅需要O(logN): 二项树(Binomial

学习算法 - 优先级队列二叉堆实现

PriorityQuenue 优先队列就是作业调度类的ADT,这里用二叉堆来实现. 优先队列最少有两个操作:插入(Insert)和删除最小者(DeleteMin). 插入操作图解: 图片来源:www.educity.cn 删除操作图解: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWxwczE5OTI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEas

二叉堆(binary heap)

堆(heap) 亦被称为:优先队列(priority queue),是计算机科学中一类特殊的数据结构的统称.堆通常是一个可以被看做一棵树的数组对象.在队列中,调度程序反复提取队列中第一个作业并运行,因而实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权.堆即为解决此类问题设计的一种数据结构. 本文地址:http://www.cnblogs.com/archimedes/p/binary-heap.html,转载请注明源地址. 逻辑定义 n个

在A*寻路中使用二叉堆

接上篇:A*寻路初探 GameDev.net 在A*寻路中使用二叉堆 作者:Patrick Lester(2003年4月11日更新) 译者:Panic 2005年3月28日 译者序:     这一篇文章,是"A* Pathfinding for Beginners.",也就是我翻译的另一篇文章<A*寻路初探>的补充,在这篇文章里,作者再一次展现了他阐述复杂话题的非凡能力,用通俗易懂的语句清晰的解释了容易让人迷惑的问题.还是那句话,如果你看了这篇文章仍然无法领会作者的意图,那