首先我们先来看一个由普通数组构建的普通堆。

然后我们通过前面的方法对它进行堆化(heapify),将其构建为最大堆。

结果是这样的:

对于我们所关心的这个数组而言,数组中的元素位置发生了改变。正是因为这些元素的位置发生了改变,我们才能将其构建为最大堆。

可是由于数组中元素位置的改变,我们将面临着几个局限性。

1.如果我们的元素是十分复杂的话,比如像每个位置上存的是一篇10万字的文章。那么交换它们之间的位置将产生大量的时间消耗。(不过这可以通过技术手段解决)

2.由于我们的数组元素的位置在构建成堆之后发生了改变,那么我们之后就很难索引到它,很难去改变它。例如我们在构建成堆后,想去改变一个原来元素的优先级(值),将会变得非常困难。

可能我们在每一个元素上再加上一个属性来表示原来位置可以解决,但是这样的话,我们必须将这个数组遍历一下才能解决。(性能低效)


针对以上问题,我们就需要引入索引堆(Index Heap)的概念。

对于索引堆来说,我们将数据和索引这两部分分开存储。真正表征堆的这个数组是由索引这个数组构建成的。(像下图中那样,每个结点的位置写的是索引号)

而在构建堆(以最大索引堆为例)的时候,比较的是data中的值(即原来数组中对应索引所存的值),构建成堆的却是index域

而构建完之后,data域并没有发生改变,位置改变的是index域。

那么现在这个最大堆该怎么解读呢?

例如,堆顶元素为Index=10代表的就是索引为10的data域的值,即62。

这时我们来看,构建堆的过程就是简单地索引之间的交换,索引就是简单的int型。效率很高。

现在如果我们想对这个数组进行一些改变,比如我们想将索引为7的元素值改为100,那我们需要做的就是将索引7所对应data域的28改为100。时间复杂度为O(1)。

当然改完之后,我们还需要进行一些操作来维持最大堆的性质。不过调整的过程改变的依旧是index域的内容。

代码:

  1. package com.heap;
  2.  
  3. public class IndexMaxHeap {
  4.  
  5. private int[] arr;
  6. private int[] index;
  7. private int count;
  8. private int capacity;
  9.  
  10. //构造方法
  11. public IndexMaxHeap(int capacity){
  12. this.capacity=capacity;
  13. this.count=0;//数量初始化为0
  14. arr=new int[capacity+1];//索引从0开始
  15. index=new int[capacity+1];
  16. }
  17.  
  18. //判断当前堆是否为空
  19. public Boolean isEmpty(){
  20. return count==0;
  21. }
  22.  
  23. //返回该最大堆的元素个数
  24. public int size(){
  25. return count;
  26. }
  27.  
  28. //插入元素到最大堆
  29. public void insertItem(int item){
  30. if(count+1>capacity)
  31. System.out.println("容量已满,插入失败");
  32. else
  33. {
  34. count++;
  35. arr[count]=item;
  36. index[count]=count;
  37.  
  38. //向上调整
  39. shiftUp(count);
  40. }
  41. }
  42.  
  43. //向上调整
  44. private void shiftUp(int k) {
  45. //比较的是arr数组
  46. //注意此时堆中存储的是index值,比较的是对应index值对应的arr[]数组的值
  47. if(k>1&&arr[index[k/2]]<arr[index[k]]){
  48. //交换的是index数组
  49. int temp=index[k/2];
  50. index[k/2]=index[k];
  51. index[k]=temp;
  52.  
  53. }else
  54. return;
  55.  
  56. k=k/2;
  57. shiftUp(k);
  58.  
  59. }
  60.  
  61. //从堆里取出堆顶元素
  62. public int extractMax(){
  63. if(count<1){
  64. System.out.println("该最大堆为空");
  65. return -1;
  66. }else
  67. {
  68. //这里取出来的是arr[]数组中的元素
  69. //这里调整的还是index
  70. int item=arr[index[1]];
  71. //将末尾元素放到堆顶
  72. index[1]=index[count];
  73. count--;//堆的元素个数减一
  74.  
  75. //向下调整元素
  76. shiftDown(1);
  77.  
  78. return item;
  79. }
  80.  
  81. }
  82.  
  83. //向下调整元素
  84. private void shiftDown(int k) {
  85. //如果这个结点有左孩子
  86. while(2*k<=count){
  87. int j=2*k;
  88. if(j+1<=count&&arr[index[j+1]]>arr[index[j]])
  89. j+=1;
  90. if(arr[index[j]]>arr[index[k]]){
  91. int temp=index[j];
  92. index[j]=index[k];
  93. index[k]=temp;
  94.  
  95. k=j;
  96. }else
  97. break;
  98. }
  99.  
  100. }
  101.  
  102. //取出最大元素的索引值
  103. public int getMaxIndex(){
  104. return index[1];
  105. }
  106.  
  107. //返回给定索引在堆中所处位置对应的数据值
  108. public int getItemByIndex(int i){
  109. return arr[index[i]];
  110. }
  111.  
  112. //改变给定索引对应的数据值
  113. //别忘了改变完数据值,再去调整一下整个堆的形态
  114. public void change(int i,int newValue){
  115. arr[i]=newValue;//修改指定索引对应的值
  116.  
  117. //要调整改变完值的堆,必须先找到当前这个指定索引所对应的数据在堆中的位置
  118. //我们知道在插入堆时,我们调整的是index域的位置变化,那么对应的index[j]的值就应该是i(即数组本来的索引)
  119. //我们遍历一下index域就能找到index[j]==i;j就表示arr[i]在堆中的位置
  120. for(int j=1;j<=count;j++){
  121. if(index[j]==i){
  122. //试着往上调一调,再试着往下调一调。就完成了堆的调整
  123. shiftUp(j);
  124. shiftDown(j);
  125.  
  126. return;//跳出多余循环
  127. }
  128. }
  129. }
  130.  
  131. public static void main(String[] args) {
  132. IndexMaxHeap heap=new IndexMaxHeap(100);
  133. heap.insertItem(3);
  134. heap.insertItem(15);
  135. heap.insertItem(23);
  136. heap.insertItem(7);
  137. heap.insertItem(4);
  138. heap.insertItem(8);
  139. System.out.println("堆的大小"+heap.size());
  140. System.out.println("堆顶元素的索引值"+heap.getMaxIndex());
  141. System.out.println("返回索引2的值:"+heap.getItemByIndex(2));
  142. System.out.println("按堆的顺序输出元素:");
  143. for(int i=1;i<=heap.count;i++)
  144. System.out.print(heap.getItemByIndex(i)+" ");
  145. System.out.println();
  146. heap.change(3, 66);
  147. System.out.println("按堆的顺序输出元素:");
  148. for(int i=1;i<=heap.count;i++)
  149. System.out.print(heap.getItemByIndex(i)+" ");
  150. System.out.println();
  151. System.out.println("此时堆顶元素"+heap.extractMax());
  152. System.out.println("此时堆顶元素"+heap.extractMax());
  153. System.out.println("此时堆顶元素"+heap.extractMax());
  154. System.out.println("此时堆顶元素"+heap.extractMax());
  155. System.out.println("堆的大小"+heap.size());
  156.  
  157. }
  158.  
  159. }

和堆相关的问题


1)使用堆来实现优先队列

动态选择优先级最高的任务执行。

2)实现多路归并排序

将整个数组分成n个子数组,子数组排完序之后,将每个子数组中最小的元素取出,放到一个最小堆里面,每次从最小堆里取出最小值放到归并结束的数组中,被取走的元素属于哪个子数组,就从哪个子数组中再取出一个补充到最小堆里面,如此循环,直到所有子数组归并到一个数组中。

索引堆(Index Heap)的更多相关文章

  1. 《高性能SQL调优精要与案例解析》一书谈主流关系库SQL调优(SQL TUNING或SQL优化)核心机制之——索引(index)

    继<高性能SQL调优精要与案例解析>一书谈SQL调优(SQL TUNING或SQL优化),我们今天就谈谈各主流关系库中,占据SQL调优技术和工作半壁江山的.最重要的核心机制之一——索引(i ...

  2. SQL Server 性能调优2 之索引(Index)的建立

    前言 索引是关系数据库中最重要的对象之中的一个,他能显著降低磁盘I/O及逻辑读取的消耗,并以此来提升 SELECT 语句的查找性能.但它是一把双刃剑.使用不当反而会影响性能:他须要额外的空间来存放这些 ...

  3. C++实现索引堆及完整测试代码

    首先贴一篇我看的博客,写的很清楚.作者:Emma_U 一些解释 索引堆首先是堆,但比堆肯定是更有用. 用处: 1.加速. 索引堆存储的是索引,并不直接存储值.在堆上浮下沉的元素交换的时候,交换索引可比 ...

  4. SQLServer性能调优3之索引(Index)的维护

    前言 前一篇的文章介绍了通过建立索引来提高数据库的查询性能,这其实只是个开始.后续如果缺少适当的维护,你先前建立的索引甚至会成为拖累,成为数据库性能的下降的帮凶. 查找碎片 消除碎片可能是索引维护最常 ...

  5. 堆和索引堆的实现(python)

    ''' 索引堆 ''' ''' 实现使用2个辅助数组来做.有点像dat.用哈希表来做修改不行,只是能找到这个索引,而需要change操作 还是需要自己手动写.所以只能用双数组实现. #引入索引堆的核心 ...

  6. [数据结构]——堆(Heap)、堆排序和TopK

    堆(heap),是一种特殊的数据结构.之所以特殊,因为堆的形象化是一个棵完全二叉树,并且满足任意节点始终不大于(或者不小于)左右子节点(有别于二叉搜索树Binary Search Tree).其中,前 ...

  7. MySQL索引的Index method中btree和hash的优缺点

    MySQL索引的Index method中btree和hash的区别 在MySQL中,大多数索引(如 PRIMARY KEY,UNIQUE,INDEX和FULLTEXT)都是在BTREE中存储,但使用 ...

  8. 聚合索引(clustered index) / 非聚合索引(nonclustered index)

    以下我面试经常问的2道题..尤其针对觉得自己SQL SERVER 还不错的同志.. 呵呵 很难有人答得好.. 各位在我收集每个人擅长的东西时,大部分都把SQL SERVER 标为Expert,看看是否 ...

  9. Java的堆(Heap)和栈(Stack)的区别

    Java中的堆(Heap)是一个运行时数据区,用来存放类的对象:栈(Stack)主要存放基本的数据类型(int.char.double等8种基本数据类型)和对象句柄. 例1 int a=5; int ...

随机推荐

  1. Python安装模块的几种方法

    一.方法1: 单文件模块 直接把文件拷贝到 $python_dir/Lib 二.方法2: 多文件模块,带setup.py 下载模块包,进行解压,进入模块文件夹,执行:python setup.py i ...

  2. shell脚本中if的“-e,-d,-f”

    文件表达式-e filename 如果 filename存在,则为真-d filename 如果 filename为目录,则为真 -f filename 如果 filename为常规文件,则为真-L ...

  3. DDD实战进阶第一波(十三):开发一般业务的大健康行业直销系统(订单上下文领域逻辑)

    前一篇文章主要讲了订单上下文的POCO模型,其中订单与订单项中有大量的值对象.这篇文章主要讲讲这些值对象以及订单项.订单相关的领域逻辑. 1.ProductSKUs值对象领域逻辑:ProductSKU ...

  4. webstorm基础使用

    因为需要用到sass自动编译,从sublime转到了webstorm.现在自我感觉sublime需要的插件太多,而webstorm很多功能都帮你及成,不需要你去找插件和安装插件. webstorm的好 ...

  5. VisualVM远程连接Tomcat

    最近项目已经要提测了,有时间来考虑一些性能上的事儿了.之前拜读过<深入理解java虚拟机>,只可惜当时功力尚浅,有些东西还是不太懂,而且应用场景也没有,所以借这次机会看看.当然了,这次并不 ...

  6. 利用maven/eclipse搭建ssm(spring+spring mvc+mybatis)

    前言 本文旨在利用maven搭建ssm环境,而关于maven的具体内容,大家可以去阅读<Maven 实战>.其实园内这方面文章已有不少,那么为什么我还要重复造轮子呢?我只是想记录自己的实践 ...

  7. 使用 Mutex 实现进程间同步

    我们知道 Mutex 互斥量是可以用在线程间同步的,线程之间共享进程的数据,mutex 就可以直接引用.而进程有自己独立的内存空间,要怎样将它应用在进程间同步呢?为了达到这一目的,可以在 pthrea ...

  8. for 循环 和 Array 数组对象

    博客地址:https://ainyi.com/12 for 循环 和 Array 数组对象方法 for for-in for-of forEach效率比较 - 四种循环,遍历长度为 1000000 的 ...

  9. [转]Node.js中koa使用redis数据库

    本文转自:https://blog.csdn.net/offbye/article/details/52452322 Redis是一个常用的Nosql数据库,一般用来代替Memcached做缓存服务, ...

  10. WPF系列(1)WPF和XAML基础

    终于下定决心开始更新WPF一个系列的文章,这里主要是出于两个目的,一是自己对所学的知识有一个系统的总结,二十希望能对其他人有些帮助,如果您觉得我写的不好,欢迎提意见. 那么既然我要开始写WPF,那我们 ...