索引堆(Index Heap)


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

然后我們通過前面的方法對它進行堆化(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個子數組,子數組排完序之后,將每個子數組中最小的元素取出,放到一個最小堆里面,每次從最小堆里取出最小值放到歸並結束的數組中,被取走的元素屬於哪個子數組,就從哪個子數組中再取出一個補充到最小堆里面,如此循環,直到所有子數組歸並到一個數組中。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM