前言

优先队列是允许至少下列两种操作的数据结构:insert(插入)以及deleteMin(删除最小者),其中deleteMin的工作是找出、返回、并删除优先队列中最小的元素。insert操作等价于enqueue(入队),而deleteMin则相当于dequeue(出队)。

二叉堆的性质

二叉堆的使用对于优先队列的实现相当普遍。二叉堆具有结构性和堆序性:

结构性质

堆是一棵被完全填满的二叉树,有可能的例外是在底层叶子上,叶子上的元素从左到右填入。这样的树称为完全二叉树。

根据完全二叉树的性质,我们可以使用一个数组来表示而不需要使用链。该数组有一个位置0,可在进行堆的插入操作时避免多次的赋值(《数据结构forJava版》)。

对于数组中任一位置i上的元素,其左儿子在位置2i上,右儿子在左儿子后的节点(2i+1)上,它的父亲则在位置i/2上。因此,这里不需要链就可以很简单的遍历该树。

一个堆结构将由一个(Comparable对象的)数组和一个代表当前堆的大小的整数组成。

堆序性质

**在一个堆中,对于每一个节点X,X的父亲中的关键字小于或等于X中的关键字,根节点除外(它没有父亲)。**根据堆序性质我们可以很容易的得出最小的元素一定在根上,因此快速找出最小元将会是件非常容易的事,并且只需要花费常数时间。

基本的堆操作

在对堆的操作中,无外乎是需要往堆中插入元素以及删除最小元素,但是所有的操作都需要保证始终保持堆序性质。

insert插入

为了将一个元素X插入到堆中,我们需要在下一个可用位置创建一个空穴,否则该堆将不是完全树。如果X可以放在该空穴中而不破坏堆的序,那么插入完成。否则,我们把空穴的父节点上的元素移入该空穴中,这样,空穴就朝着根的方向上冒一步。继续该过程直到X能被放入空穴中为止。

根据下图,我们想要插入14,我们在堆的下一个可用位置创建一个空穴,由于将14插入空穴破坏了堆的序(空穴父亲的关键字31大于14),因此将31移入该空穴,空穴位置上移到原31位置,接着继续比较现在空穴与其父节点,直到找到置入14的正确位置。

堆的插入策略叫做上滤。新元素在堆中上滤直到找到正确的位置。代码也很容易实现插入。

/*** 插入操作,需要上滤元素* 通过不断比较空穴元素hole与其父元素的大小进行元素上滤*/public void insert(T item){if(currentSize == array.length - 1){enlargeArray(array.length * 2 + 1);}int hole = ++currentSize;for(;hole > 1 && item.compareTo(array[hole/2]) < 0;hole /= 2){array[hole] = array[hole/2];}array[hole] = item;}

当进行元素插入时,由于我们是使用数组作为堆的元素存放,因此必须考虑数组越界问题,其中currentSize为数组此刻的最后一个元素的序号,当它大于数组长度时需要进行扩容。

我们通过array[hole/2]获得节点的父节点,然后来更改堆的序,确保每一个结点的父亲的关键字都要小于或等于该节点。

正常的一次交换操作需要执行三条赋值语句,如果一个元素上滤d层,那么由于交换而执行的赋值次数就达到3d,而我们这里的方法却只用到d+1次赋值。

deleteMin删除最小元

deleteMin以类似插入的方式处理。找出最小元是容易的,困难之处是删除它。当删除一个最小元时,需要在根节点建立一个空穴。由于现在堆少了一个元素,因此堆中最后一个元素X必须移动到该堆的某个地方。如果X可以被放到空穴中,那么deleteMin完成,不过这一般不太可能,因此我们将空穴的两个儿子中较小者移入空穴,这样空穴向下推了一层。重复该步骤直到X可以被放入空穴中。

在下图中,我们删除了该堆的最小元13,因此13位置成了空穴,所有此时需要将堆的最后一个元素31移入该空穴,但是发现该空穴的左儿子14小于该元素,因此需要将该左儿子移入空穴,并且空穴下移,反复该操作,直到31被放入正确的位置。这种一般的策略叫做下滤

删除最小元的代码如下:

/*** 删除最小元* 将堆中最后一个元素放在根节点* 然后进行元素下滤* @return*/public T deleteMin(){if (isEmpty()){throw new NoSuchElementException();}T minItem = findMin();array[1] = array[currentSize];array[currentSize--] = null;percolateDown(1);return minItem;}/*** 元素下滤* 从根节点开始比较其左右儿子,找出最小的儿子与父亲进行比较* 直到两儿子的值都大于父亲则结束循环* 最后将根节点的值放入最小的儿子处* @param hole*/public void percolateDown(int hole){int child;T tmp = array[hole];for(;hole * 2 <= currentSize;hole = child){child = hole * 2;if(child != currentSize && array[child + 1].compareTo(array[child]) < 0){child++;}if(child != currentSize && array[child].compareTo(tmp) < 0){array[hole] = array[child];} else {break;}}array[hole] = tmp;}

由于我们必须保证节点不总存在两个儿子,因此在第30行我们需要对节点的儿子进行大小比较,确保下滤元素总是流向较小的一方。

完整代码

由于堆的插入和删除最小元的代码已经在上面给出,因此下面的代码段将省略这两个方法的代码。

/*** @author: zhangocean* @Date: 2018/10/19 13:19* Describe:*/
public class BinaryHeap<T extends Comparable<? super T>> {private static final int DEFAULT_CAPACITY = 10;private int currentSize;private T[] array;public BinaryHeap() {this(DEFAULT_CAPACITY);}@SuppressWarnings("unchecked")private BinaryHeap(int heapSize) {currentSize = 0;//不能使用泛型创建数组array = (T[]) new Comparable[heapSize + 1];}@SuppressWarnings("unchecked")public BinaryHeap(T[] items) {currentSize = items.length;array = (T[]) new Comparable[(currentSize + 2) * 11 / 10];int i = 1;for (T item : items) {array[i++] = item;}buildHeap();}/*** 建立堆序* 从叶子节点中最左边的父节点开始进行元素下滤*/private void buildHeap() {for (int i = currentSize / 2; i > 0; i--) {percolateDown(i);}}/*** 插入操作,需要上滤元素* 通过不断比较空穴元素hole与其父元素的大小进行元素上滤*/public void insert(T item) {///插入代码见上方}/*** 查找堆中最小的元素(即根上的元素)** @return*/public T findMin() {if (isEmpty()) {return null;}return array[1];}/*** 删除最小元* 将堆中最后一个元素放在根节点* 然后进行元素下滤** @return*/public T deleteMin() {///删除最小元代码见上方}/*** 元素下滤* 从根节点开始比较其左右儿子,找出最小的儿子与父亲进行比较* 直到两儿子的值都大于父亲则结束循环* 最后将根节点的值放入最小的儿子处** @param hole*/public void percolateDown(int hole) {///元素下滤代码见上方}public boolean isEmpty() {return currentSize == 0;}public void makeEmpty() {currentSize = 0;for (int i = 0; i < array.length; i++) {array[i] = null;}}/*** 数组扩容*/private void enlargeArray(int newArraySize) {T[] oldArray = array;array = (T[]) new Comparable[newArraySize];int i = 0;for (T item : oldArray) {array[i++] = item;}}public void printHeap() {for (T item : array) {System.out.print(item + " ");}System.out.println();}public static void main(String[] args) {BinaryHeap<Integer> heap = new BinaryHeap<Integer>();for (int i = 0; i < 20; i++) {heap.insert(i);}heap.printHeap();heap.deleteMin();heap.printHeap();heap.deleteMin();heap.deleteMin();heap.printHeap();}
}

输出结果如下所示:

null 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 null null
null 1 3 2 7 4 5 6 15 8 9 10 11 12 13 14 19 16 17 18 null null null
null 3 4 5 7 9 11 6 15 8 17 10 18 12 13 14 19 16 null null null null null

总结

  1. 二叉堆对于优先队列的实现相对较于普遍。
  2. 堆需要保持堆的序,即对于堆中每个节点X,X的父亲中关键字小于或等于X中的关键字,当然啦根节点除外(它木有父亲)。
  3. 二叉堆是一棵完全二叉树,根据它的结构性可以用数组的方式来实现,而避免了链的使用。
  4. 由于是用数组来实现一棵树结构,因此需要能够对树中节点进行比较,所以通过newComparable数组实现数组元素之间的比较。
  5. 堆的主要操作在于元素插入以及删除最小元,插入时先在最后一个节点的下一个位置建立一个空穴,然后试着将需要插入的元素放入,否则上滤元素。删除最小元则是移除根节点(不用说,它肯定最小)元素,根节点成为空穴,再将最后一个结点试着移入该空穴中,否则进行元素下滤操作。

更多文章请关注我的个人博客:www.zhyocean.cn

完全二叉树——二叉堆(BinaryHeap)相关推荐

  1. 《恋上数据结构第1季》二叉堆原理及实现、最小堆解决 TOP K 问题

    二叉堆 BinaryHeap 堆(Heap) 堆的出现 堆简介 二叉堆(Binary Heap) 获取最大值 最大堆 - 添加 最大堆 - 添加优化 最大堆 - 删除 replace 最大堆 - 批量 ...

  2. 【数据结构】二叉堆、TOP K 问题

    二叉堆.TOP K 问题 堆(Heap) 堆的出现,思考? 堆简介 二叉堆(Binary Heap) 获取最大值 最大堆 - 添加 最大堆 - 添加优化 最大堆 - 删除 replace 最大堆 – ...

  3. 二叉堆(TopK问题,优先级队列)

    目录 实现一个大根堆 优先级队列 Comparable和Compator区别 compareTo方法 TopK问题 TopK问题常见题型为求最大(最小)的K个值. 我们一般拿堆来解决. 堆:二叉堆首先 ...

  4. 数据结构之优先队列--二叉堆(Java实现)

    前言 数据结构队列的学习中,我们知道队列是先进先出的.任务被提交到队列中,按照先进先出的原则 对各个任务进行处理.不过在现实的情况下,任务通常有着优先级的概念,例如短任务.管理员的操作 应该优先执行. ...

  5. 二叉堆与二叉堆的构建

    什么是二叉堆? 二叉堆本质上是一种完全二叉树,它分为两个类型: 最大堆:任何一个父节点的值,都大于或等于它左.右孩子节点的值. 最小堆:任何一个父节点的值,都小于或等于它左.右孩子节点的值. 二叉堆的 ...

  6. 二叉堆时间复杂度 php,二叉堆(Binary Heap)

    二叉堆这个数据结构有点意思,自己做了个总结,内容结构如下: 二叉堆性质 二叉堆操作 应用 二叉堆性质: 堆(Heap)是一个可以被看成近似完全二叉树的结构,具有完全二叉树的特性: 缺少的叶子节点总是位 ...

  7. python优先队列的库,python优先队列及二叉堆的实现

    python优先队列及二叉堆的实现 发布于 2015-12-18 06:55:17 | 117 次阅读 | 评论: 0 | 来源: PHPERZ Python编程语言Python 是一种面向对象.解释 ...

  8. 二叉堆的基本概念与实现

    基本概念 二叉堆又名堆,或者优先队列.一般实现在堆顶的元素总是最小的. 二叉堆是一颗用数组实现的完全二叉树. 要实现二叉堆必须满足以下条件: 1堆有序,二叉树中每一个子树的父节点不大于(大根堆)两个子 ...

  9. python最大堆_二叉堆 及 大根堆的python实现

    Python 二叉堆(binary heap) 二叉堆是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树.二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子 ...

最新文章

  1. c# 字典按ascii 排序_利用工作表函数,对字典键进行排序并给出对应重复个数
  2. NOIP模拟题——dun
  3. 计算机等级考试初级网络工程师,计算机等级网络工程师考试内容
  4. Java PriorityQueue
  5. 垂直柱状图(洛谷-P1598 )
  6. “阿里味” PUA 编程语言火上GitHub热榜,标星1.9K!
  7. python批量创建txt文件
  8. 转载: Fisher精确检验概述
  9. HTML5写的app打开白屏,苹果App Store白屏的五种解决办法
  10. 如何在国外做好自然科学研究-2
  11. C语言扫雷游戏代码以及基本原理教学(一看就会)
  12. cout和cin后面跟指针的问题
  13. linux 内核源码下载网址
  14. 浅谈自然辩证在现代科学领域的作用
  15. LeetCode 417. 太平洋大西洋水流问题 JAVA dfs
  16. 最为一个程序猿,怎么能不懂行内黑话。
  17. 英语系高手的整理!不想过四六级都难啊
  18. php 获取移动端设备号,getDeviceId()获取设备号IMEI、MEID、ESN
  19. Windows10下编译Nginx64位并增加Ipv6模块
  20. 如何使用报表工具制作超级链接报表

热门文章

  1. eclipse怎样显示行数
  2. You can also run `php --ini` inside terminal to see which files are used by PH P in CLI mode
  3. 电脑开机出现红色三角标志,怎么办
  4. Excel公式:将度分秒格式转换成度的格式
  5. ResourceQuota 和 LimitRange 实践
  6. ubuntu下top命令源码位置及分析
  7. mysql查询除开id不同的重复数据
  8. 程序员如何提高生产效率?
  9. 免费pdf转换器软件
  10. 参加科学教师与计算机培训总结报告,信息技术校本培训总结