引言

二叉堆的实现见:Java中的优先队列——二叉堆

索引二叉堆又称为最小索引优先队列。它的特点是堆元素位置不变

保存堆元素的数组不变,即不交换数组中任意元素的位置。利用一个额外的指向堆元素下标(或索引)的索引数组(pq)来代替它进行交换,同时为了操作方便,再维护一个pq的翻转数组(pq中的值作为新数组的索引,索引作为新数组的值)。

文字看起来有点懵就对了。我们直接看它的结构。

数据结构核心原理与算法应用

结构


假设我们按{4,2,3,1,6,5,7}的顺序插入,并且一旦插入结束之后,不会交换元素的位置。我们又想进行取堆顶元素等操作,怎么办?
可以新增一个特殊的数组(pq)来维护keys数组的索引,根据keys数组中元素的大小移动pq数组的位置。使得可以通过pq[1]得到最小元素。

pq维护的是keys数组的索引。因此pq[1]的值一定是keys数组中最小值的索引,上图中最小值为1,其索引为3。因此pq[1] = 3
那么取堆顶元素可以:keys[pq[1]]

当构建完成索引二叉堆后,pq的结构如下:


pq相当是二叉堆对应的数组,按惯例,该索引从1开始。不过,这个数组中的元素不是堆元素的值,而是堆元素在keys数组中的下标。

pqkeys的关系如下:


其实很简单,pq的值指向keys中对应的下标即可。因此,我们就可以根据keys中相应值的大小调整pq数组来达到使keys数组满足堆序性的目的。

索引二叉堆中还有一个特殊的翻转数组。翻转的就是pq,我们命名这个翻转数组为reversed
pq中的值作为翻转数组的索引,索引作为翻转数组的值。根据上面的例子,其翻转数组如下:


就是将pq中下标和其对应的值翻转了一下得到的数组。

为什么要这个翻转数组呢,其实它只是一个辅助数组,比如我们要得到keys数组索引5(keys数组的索引称为关联索引)在pq中对应的索引。那么就可以通过reversed[5]得到。该值为6,而pq[6] = 5

总结一下,它们之间满足这样一个等式:pq[reversed[i]] = reversed[pq[i]] = i 其中,ikeys数组的索引。

理解了这个结构,再去理解它的实现代码就不难了。

代码

package com.algorithms.heap;import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;/*** 索引二叉堆<p/>* 具有堆元素位置不变的性质* <p>* 保存堆元素的数组不变,即不交换数组中任意元素的位置。利用一个额外的指向堆元素下标的索引数组(pq)来代替它进行交换,* 同时为了操作方便,再维护一个pq的翻转数组(pq中的值作为新数组的索引,索引作为新数组的值)** @author yjw* @date 2019/6/3/003*/
@SuppressWarnings("unchecked")
public class IndexMinPQ<Key extends Comparable<? super Key>> implements Iterable<Integer> {/*** 堆中最大元素数量*/private final int capacity;/*** 堆中元素数量*/private int size = 0;/*** 指向堆元素引用的数组* 数组中的元素指向堆元素(keys)的索引* 数组里面只是索引,具体参与比较的还是对应的堆元素* <p>* 该数组满足堆性质,很容易知道最小元素为keys[pq[1]]* <p>* 索引范围: (0,size]*/private int[] pq;/*** <strong>保存的是关联索引i -> pq中对应的索引,其下标就是关联索引</strong>* <p>* 以pq中值作为reversed的索引,pq值对应的索引作为reversed的值。* 相当于将pq数组值和下标翻转过来得到的数组。* <p>* 满足: reversed[pq[i]] = pq[reversed[i]] = i** 索引范围: [0,size - 1)*/private int[] reversed;/*** 堆元素数组,该数组不会进行交换操作* <p>* 索引范围: [0,size - 1)*/private Key[] keys;public IndexMinPQ(int capacity) {if (capacity < 0) {throw new IllegalStateException();}this.capacity = capacity;keys = (Key[]) new Comparable[capacity + 1];pq = new int[capacity + 1];reversed = new int[capacity + 1];Arrays.fill(reversed, -1);//-1表示没有关联任何pq的索引}private void rangeCheck(int i) {if (i < 0 || i >= capacity) {throw new IndexOutOfBoundsException();}}/*** @param i 索引* @return 该索引是否在堆中*/public boolean contains(int i) {rangeCheck(i);return reversed[i] != -1;}public int size() {return size;}/*** 后面所说的索引i(public 方法参数中的i)都指的是keys数组中的索引,也就是关联索引*//*** 关联元素e与索引i* <p>* 想查询i对应的元素,除了keys[i],还可以通过keys[pq[reversed[i]]]** @param i* @param key*/public void insert(int i, Key key) {if (contains(i)) {throw new IllegalStateException("index is already in the heap");}size++;keys[i] = key;pq[size] = i;//将索引添加到pq中最后的位置,保存新元素e在keys中的索引reversed[i] = size;//翻转pqswim(size);//上滤}/*** 删除最小的元素并返回它的关联索引** @return*/public int deleteMin() {if (isEmpty()) {throw new NoSuchElementException();}//pq下标为1的元素即keys中最小元素的关联索引,int min = pq[1];swap(1, size--);//用最后一个元素替代最小元素的位置sink(1);//下滤reversed[min] = -1;        // 标记为已删除keys[min] = null;    // 防止内存泄漏!!pq[size + 1] = -1;        // 标记之前的最后一个元素为已删除return min;}/*** 删除关联索引i处的元素,注意删除的是keys[i]对应的元素** @param i*/public void delete(int i) {if (!contains(i)) {throw new NoSuchElementException();}/*** 得到pq数组中的索引index,然后删除keys[i]处的元素*/int index = reversed[i];/*** 同样用最后一个元素替代该元素*/swap(index, size--);/*** 这里需要进行上滤和下滤操作* 有可能堆中最后的元素小于index处的父节点*/swim(index);sink(index);keys[i] = null;//防止内存泄漏reversed[i] = -1;//表明关联索引i没有关联任何元素了}/*** 返回关联最小元素的索引** @return*/public int minIndex() {if (isEmpty()) {throw new NoSuchElementException();}return pq[1];}public Key minKey() {return keys[minIndex()];}/*** 返回与索引i关联的元素** @param i* @return*/public Key keyOf(int i) {if (!contains(i)) {throw new NoSuchElementException();}return keys[i];}public boolean isEmpty() {return size == 0;}private boolean less(int i, int j) {return keys[pq[i]].compareTo(keys[pq[j]]) < 0;}/*** 将i关联的元素值增加到 key** @param i* @param key 要满足大于i处的值*/public void increaseKey(int i, Key key) {if (!contains(i)) {throw new NoSuchElementException();}if (keys[i].compareTo(key) >= 0) {throw new IllegalArgumentException("Calling increaseKey() with given argument would " +"not strictly increase the key");}keys[i] = key;//因为是增大值,所以只需要下滤sink(reversed[i]);}public void decreaseKey(int i, Key key) {if (!contains(i)) {throw new NoSuchElementException();}if (keys[i].compareTo(key) <= 0) {throw new IllegalArgumentException("Calling decreaseKey() with given argument " +"would not strictly decrease the key");}keys[i] = key;//上滤swim(reversed[i]);}public void changeKey(int i, Key key) {if (!contains(i)) {throw new NoSuchElementException();}keys[i] = key;//因为不知道是增加了还是减少了,所以需要在两个方向进行交换swim(reversed[i]);sink(reversed[i]);}/*** 另一种写法* @param i* @param key*//*public void changeKey(int i, Key key) {if (!contains(i)) {throw new NoSuchElementException();}if (keys[i].compareTo(key) < 0) {increaseKey(i, key);} else if (keys[i].compareTo(key) > 0) {decreaseKey(i, key);}}*/// swim/swap/sink都是对pq和reversed数组进行的,keys数组只能进行置null操作!!!/*** 上滤** @param k*/private void swim(int k) {/*** 当k比它的父节点要小时,上滤* 直到k不小于它的父节点* 或k变成了堆顶(k=1)*/while (k > 1 && less(k, k / 2)) {swap(k, k / 2);k = k / 2;}}/*** 下滤** @param k*/private void sink(int k) {//如果有孩子,不停的下滤,直到满足堆的性质//k * 2 <= size 说明至少有左孩子while (k * 2 <= size) {int child = k * 2;//如果有右孩子(child +1),且右孩子更小if (child < size && less(child + 1, child)) {child = child + 1;}if (less(k, child)) {//如果小于孩子,就不用下滤了break;}swap(k, child);k = child;//更新k}}/*** 交换pq和reversed数组** @param i* @param j*/private void swap(int i, int j) {int tmp = pq[i];pq[i] = pq[j];pq[j] = tmp;reversed[pq[i]] = i;reversed[pq[j]] = j;}@Overridepublic Iterator<Integer> iterator() {return new HeapIterator();}@Overridepublic String toString() {StringBuilder sb = new StringBuilder();sb.append("capacity: ").append(capacity).append(",current size: ").append(size).append("\n");sb.append("keys:").append(Arrays.toString(keys)).append("\n");sb.append("pq:").append(Arrays.toString(pq)).append("\n");sb.append("reversed:").append(Arrays.toString(reversed));return sb.toString();}private class HeapIterator implements Iterator<Integer> {private IndexMinPQ<Key> copied;public HeapIterator() {copied = new IndexMinPQ<>(pq.length - 1);for (int i = 1; i <= size; i++) {//pq数组已经满足堆序性,因此没有元素会移动copied.insert(pq[i], keys[pq[i]]);}}@Overridepublic boolean hasNext() {return !copied.isEmpty();}@Overridepublic Integer next() {if (!hasNext()) {throw new NoSuchElementException();}return copied.deleteMin();}}public static void main(String[] args) {Integer[] ints = {4, 2, 3, 1, 6, 5, 7};//假设以这个顺序插入堆IndexMinPQ<Integer> pq = new IndexMinPQ<>(ints.length);for (int i = 0; i < ints.length; i++) {pq.insert(i, ints[i]);}System.out.println(pq);// print each key using the iterator/*for (int i : pq) {System.out.println(i + " " + ints[i]);}*/}
}

一文带你弄懂什么是索引二叉堆相关推荐

  1. 一文带你弄懂Visual Studio:运行时库及MT/MTD、MD/MDD

    一文带你弄懂Visual Studio:运行时库及MT/MTD.MD/MDD 引子 什么是Runtime Library? Runtime Library和运行库 MT MTD MD MDD的关系 静 ...

  2. if __name__ == __main___一文带你弄懂python中if __name__ == #39;__main__#39;

    我们在python模块那章节的学习,有所接触到if __name__ == '__main__'这个概念.当时我们只是大概描述了一番,不少伙伴还是有所困惑,今天就让我们通过实际例子去讲解这条语句到底有 ...

  3. 一文带你弄懂普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法

    Prim算法 Prim算法用于构造最小生成树,且适用于稠密图. 基本思想 : 归并顶点 设连通网络 N = { V, E } 从某顶点 u0 出发, 选择与它关联的具有最小权值的边(u0, v), 将 ...

  4. 一文带你弄懂 Java 动态代理 | 原力计划

    作者 | mjzuo 责编 | 王晓曼 出品 | CSDN 博客 在说动态代理之前,先来简单看下代理模式. 代理是最基本的设计模式之一.它能够插入一个用来替代"实际"对象的&quo ...

  5. Vue的生命周期详解,一文带你弄懂Vue的生命周期

    大家好,我是Ned

  6. 一文带你弄懂C++中的ANSI、Unicode和UTF8三种字符编码及相互转换

    目录 1.概述 2.Visual Studio中的字符编码 3.ANSI窄字节编码 4.Unicode宽字节编码 5.UTF8编码

  7. 一文带你读懂HTTP协议的前世今生

    点击上方蓝字关注我们 HTTP,Hypertext Transfer Protocol,超文本协议,是在万维网上传输文件(如文本.图形图像.声音.视频和其他多媒体文件)的规则集.如果web用户打开他们 ...

  8. 三分钟带你弄懂slot插槽——vue进阶

    文章目录 三分钟带你弄懂slot插槽--vue进阶 一.概述 程序员之死 什么是 slot插槽? 2.6.0 版本中的 slot 二.具名插槽 例子 效果图 代码 三.小惊喜 三分钟带你弄懂slot插 ...

  9. # Odoo丨一文让你弄懂Odoo的用户、组与权限

    Odoo丨一文让你弄懂Odoo的用户.组与权限 在 Odoo 上的尝试.调研与分享 Odoo中的权限规则的使用 Odoo是基于Python语言编写的全球流行的开源管理套件,它的各个应用程序能完美集成在 ...

  10. DNN、RNN、CNN.…..一文带你读懂这些绕晕人的名词

    DNN.RNN.CNN.-..一文带你读懂这些绕晕人的名词 https://mp.weixin.qq.com/s/-A9UVk0O0oDMavywRGIKyQ 「撞脸」一直都是娱乐圈一大笑梗. 要是买 ...

最新文章

  1. (转)flash的Socket通讯沙箱和安全策略问题
  2. springboot_4 spring boot 使用servlet,filter,listener和interceptor
  3. CentOS yum安装MySQL5.7.20
  4. 20189320《网络攻防》第一周作业
  5. 索泰显卡超频软件测试要多少时间,索泰显卡专用超频软件_FireStorm显卡超频 V2.0.1 官方版...
  6. 命令行工具tshark使用小记
  7. indy10 UDP实例
  8. 38 SD配置-销售凭证设置-定义拒绝原因
  9. IT职场人生系列之十三:技术?管理?业务?
  10. typechoSQLIte转MySQL_Typecho SQLite与MySQL的数据库切换及解决MySQL连接打开缓慢问题
  11. nginx 访问控制之 document_uri
  12. matlab s函数模板,MATLAB第5章S函数.ppt
  13. 《老罗Android开发视频教程》
  14. excel查找出某一列的重复数据
  15. 重庆千年古镇摆千米长宴迎新年
  16. #2 – Rendering Tiers(WPF渲染级别)
  17. 工业加固三防平板主要适用于哪些环境
  18. Python实现对比两个Excel数据内容并标出不同
  19. Android 搭建Linux服务器
  20. 成都大数据语言培训:如何提高数据分析能力

热门文章

  1. Laser Reflections solutions
  2. java操作数据库,以页面显示学生信息为例
  3. hdu 3746 kmp的next数组理解
  4. [中等]寻找缺失的数
  5. IBM Lotus Domino V8.5 服务器管理入门手册
  6. AutoCompleteTextView输入汉字拼音首字母实现过滤提示(支持多音字,Filterable的使用)...
  7. 使用js来实现模拟无刷新文件上传。
  8. 一个简单的 SQLite 的示例
  9. Centos 03 基础命令
  10. js-Date()对象,get/setFullYear(),getDay()编程练习