前言

优先队列是允许至少下列两种操作的数据结构: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. mysql数据库主从配置
  2. python画饼图程序_python使用matplotlib画饼状图
  3. 【JS】call,apply,bind
  4. ucos任务调度函数 OSSched()函数分析 ,任务切换函数
  5. Android常用权限permission列表摘录
  6. python 计算订单_从问题到程序:用Python学编程和计算
  7. 32位寄存器用法介绍
  8. windows——JDK下载与安装及环境变量配置
  9. samba服务testparm时提示rlimit_max: increasing rlimit_max (1024) to minimum Windows limit (16384)处理办法
  10. 饮用水公司配送管理系统可行性报告
  11. ip自签名ssl证书
  12. 【STM32H7】第2章 ThreadX FileX文件系统介绍
  13. STM32入门之LCD1602驱动
  14. “error : unknown filesystem”的解决的方法
  15. 在北上广的程序员们,三十岁以后你们会回老家做什么
  16. php中false的作用,PHP中false 。==是什么意思
  17. Java IDE使用基础
  18. 【移动端h5常用的几款插件 】
  19. java 不兼容类型_JAVA不兼容的类型:无法将对象转换为我的类型
  20. 未来已来,光伏产业将走向何方?十大趋势待揭晓!

热门文章

  1. ERP : 反馈与校正行动
  2. 基于cpt的组网实验_无线自组网高精度网络时间同步算法
  3. [GO]mysql中支持表情emoji字符的几个修改点
  4. Excel怎么改变表格边框及背景颜色
  5. 随机数种子的作用域问题
  6. 熊猫TV构建差异化竞争壁垒 游戏直播现新突破
  7. 学历和能力对程序员哪个更重要
  8. html直播源码,HTML5中的websocket实现直播功能
  9. redhat linux 百度云,RedHat Enterprise Linux Server 5.11 6.7 7.1 7.2 百度云盘 下
  10. 评价的等级优良差_(提优必备)小学生期末评语-等级优良合格