首先我們先來看一個由普通數組構建的普通堆。

然后我們通過前面的方法對它進行堆化(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域的內容。
代碼:
package com.heap; public class IndexMaxHeap { private int[] arr; private int[] index; private int count; private int capacity; //構造方法 public IndexMaxHeap(int capacity){ this.capacity=capacity; this.count=0;//數量初始化為0 arr=new int[capacity+1];//索引從0開始 index=new int[capacity+1]; } //判斷當前堆是否為空 public Boolean isEmpty(){ return count==0; } //返回該最大堆的元素個數 public int size(){ return count; } //插入元素到最大堆 public void insertItem(int item){ if(count+1>capacity) System.out.println("容量已滿,插入失敗"); else { count++; arr[count]=item; index[count]=count; //向上調整 shiftUp(count); } } //向上調整 private void shiftUp(int k) { //比較的是arr數組 //注意此時堆中存儲的是index值,比較的是對應index值對應的arr[]數組的值 if(k>1&&arr[index[k/2]]<arr[index[k]]){ //交換的是index數組 int temp=index[k/2]; index[k/2]=index[k]; index[k]=temp; }else return; k=k/2; shiftUp(k); } //從堆里取出堆頂元素 public int extractMax(){ if(count<1){ System.out.println("該最大堆為空"); return -1; }else { //這里取出來的是arr[]數組中的元素 //這里調整的還是index int item=arr[index[1]]; //將末尾元素放到堆頂 index[1]=index[count]; count--;//堆的元素個數減一 //向下調整元素 shiftDown(1); return item; } } //向下調整元素 private void shiftDown(int k) { //如果這個結點有左孩子 while(2*k<=count){ int j=2*k; if(j+1<=count&&arr[index[j+1]]>arr[index[j]]) j+=1; if(arr[index[j]]>arr[index[k]]){ int temp=index[j]; index[j]=index[k]; index[k]=temp; k=j; }else break; } } //取出最大元素的索引值 public int getMaxIndex(){ return index[1]; } //返回給定索引在堆中所處位置對應的數據值 public int getItemByIndex(int i){ return arr[index[i]]; } //改變給定索引對應的數據值 //別忘了改變完數據值,再去調整一下整個堆的形態 public void change(int i,int newValue){ arr[i]=newValue;//修改指定索引對應的值 //要調整改變完值的堆,必須先找到當前這個指定索引所對應的數據在堆中的位置 //我們知道在插入堆時,我們調整的是index域的位置變化,那么對應的index[j]的值就應該是i(即數組本來的索引) //我們遍歷一下index域就能找到index[j]==i;j就表示arr[i]在堆中的位置 for(int j=1;j<=count;j++){ if(index[j]==i){ //試着往上調一調,再試着往下調一調。就完成了堆的調整 shiftUp(j); shiftDown(j); return;//跳出多余循環 } } } public static void main(String[] args) { IndexMaxHeap heap=new IndexMaxHeap(100); heap.insertItem(3); heap.insertItem(15); heap.insertItem(23); heap.insertItem(7); heap.insertItem(4); heap.insertItem(8); System.out.println("堆的大小"+heap.size()); System.out.println("堆頂元素的索引值"+heap.getMaxIndex()); System.out.println("返回索引2的值:"+heap.getItemByIndex(2)); System.out.println("按堆的順序輸出元素:"); for(int i=1;i<=heap.count;i++) System.out.print(heap.getItemByIndex(i)+" "); System.out.println(); heap.change(3, 66); System.out.println("按堆的順序輸出元素:"); for(int i=1;i<=heap.count;i++) System.out.print(heap.getItemByIndex(i)+" "); System.out.println(); System.out.println("此時堆頂元素"+heap.extractMax()); System.out.println("此時堆頂元素"+heap.extractMax()); System.out.println("此時堆頂元素"+heap.extractMax()); System.out.println("此時堆頂元素"+heap.extractMax()); System.out.println("堆的大小"+heap.size()); } }
和堆相關的問題
1)使用堆來實現優先隊列
動態選擇優先級最高的任務執行。
2)實現多路歸並排序
將整個數組分成n個子數組,子數組排完序之后,將每個子數組中最小的元素取出,放到一個最小堆里面,每次從最小堆里取出最小值放到歸並結束的數組中,被取走的元素屬於哪個子數組,就從哪個子數組中再取出一個補充到最小堆里面,如此循環,直到所有子數組歸並到一個數組中。
