07.索引堆

索引堆

對于一個算法扛点,如果僅僅是實現(xiàn)出來透葛,而不能很好的用語言進行表達,說明對于此算法的理解還比較模糊镀裤,如果能夠清晰的表述出來竞阐,那么對于此算法的理解就足夠清晰了!


原數組array的位置不變暑劝,用一個新的數組indexArr存儲原數組的索引骆莹,在用一個數組reverseArr存儲array中元素對應的索引在indexArr數組中的位置,他們的關系如下担猛,利用這些特性可以讓索引堆的平均性能更優(yōu)幕垦。


索引的索引.png
import java.util.ArrayList;
import java.util.Comparator;

/** @索引堆
 */

public class IndexHeap<E> {
    private ArrayList<E> array; // 原始數組
    private ArrayList<Integer> indexArr; // 索引堆
    private ArrayList<Integer> reverseArr; // 原始數組元素的索引在索引堆中的位置
    private Comparator<E> compare;

    // 構造函數
    public IndexHeap(Comparator<E> compare){
        array = new ArrayList<>();
        indexArr = new ArrayList<>();
        reverseArr = new ArrayList<>();
        this.compare = compare;
    }

    public int size() {
        return array.size();
    }

    public boolean isEmpty() {
        return this.size() == 0;
    }

    /** @swap 交換函數,交換索引堆,同時維護一下索引堆指向的數據
     *  索引堆 與 索引指向 之間的關系
     *      @索引堆  : indexArr[i] = j
     *      @索引指向: reverseArr[j] = i
     *                reverseArr[indexArr[i]] = i;
     *                indexArr[reverseArr[j]] = j;
     *
     * @注意: 維護索引指向丢氢,不能像下面這樣耍,可能會造成索引指向重復
     * reverseArr.set(indexArr.get(i), i);
     * reverseArr.set(indexArr.get(j), j);
     */
    private void swap(int i, int j) {

        // 維護索引堆
        Integer temp = indexArr.get(i);
        indexArr.set(i, indexArr.get(j));
        indexArr.set(j, temp);

        // 維護索引執(zhí)行
        int revI = indexArr.get(i);
        int revJ = indexArr.get(j);
        int exchange = reverseArr.get(revI);
        reverseArr.set(revI, reverseArr.get(revJ));
        reverseArr.set(revJ, exchange);
    }

    /** 計算父節(jié)點索引:parent(i) = (i - 1) / 2*/
    private int parent(int i) {
        return (i - 1) / 2;
    }

    /** 計算左孩子節(jié)點:left child(i) = i * 2 + 1*/
    private int leftChild(int i) {
        return i * 2 + 1;
    }

    /** 計算右孩子節(jié)點:right child(i) = i * 2 + 2*/
    private int rightChild(int i) {
        return i * 2 + 2;
    }

    /** @shiftUp先改,
     * 將優(yōu)先級大的往上冒
     * 注意:實際比較的是索引堆值指向源數組中的數據
     * swap交換是索引堆的值
     */
    private void shiftUp(int i) {
        int parentIndex = parent(i);
        while (i > 0 && compare.compare(array.get(indexArr.get(i)), array.get(indexArr.get(parentIndex))) >= 0) {
            swap(i, parentIndex);
            i = parentIndex;
            parentIndex = parent(parentIndex);
        }
    }

    /** @向堆中添加一個元素
     * 同步向索引堆和索引堆指向中添加數組
     */
    public void add(E e) {
        array.add(e);
        indexArr.add(array.size() - 1);
        reverseArr.add(indexArr.size() - 1);

        // 對索引數組進行排序
        shiftUp(indexArr.size() - 1);
    }

    private int prioritySonIndex(int i, int boundary) {
        int leftIndex = leftChild(i);
        int rightIndex = rightChild(i);

        if(leftIndex >= boundary)
            return -1;

        if(rightIndex >= boundary)
            return leftIndex;

        if(compare.compare(array.get(indexArr.get(leftIndex)), array.get(indexArr.get(rightIndex))) > 0)
            return leftIndex;
        else
            return rightIndex;
    }

    // Shift Down操作疚察,從根節(jié)點開始與子節(jié)點比較,按照規(guī)則進行交換仇奶!
    private void shiftDown(int i, int boundary) {
        int parent = i;
        int son = prioritySonIndex(i, boundary);
        while (son != -1) {
            if(compare.compare(array.get(indexArr.get(son)), array.get(indexArr.get(parent))) > 0) {
                swap(parent, son);
                parent = son;
                son = prioritySonIndex(parent, boundary);
            } else
                return;
        }
    }

    /** @pop 找到堆中的第一個元素所指向的原始數組的值貌嫡,也就是優(yōu)先級最大的元素。
     * 1. 如果原始數組中刪除一個元素该溯,此元素的索引【i】之后的所有元素都會前移動一位
     *    索引堆中對應的大于上面索引【i】的索引堆指向都向后多了一位的指向岛抄,需要將
     *    他們的值減去1,向前偏移一位狈茉!
     * 2. 移除原始數組array中的被返回的數組
     * 3. 將索引指向堆reverseArr中對應【indexArr堆中末尾】的指向
     *    賦值到對應【indexArr堆中首位】的位置夫椭;
     *    將索引堆中的最后一個元素賦值到首位置
     *    刪除兩個數組中被移動之前的位置的元素!
     * 4. shiftDown操作,維護indexArr與reverseArr
     * 5. 返回源數組被移除的元素
     */
    public E pop() {
        int popIndex = indexArr.get(0);
        E temp = array.get(popIndex);

        // 1. 刪除原始數組之后论皆,原始數組中的對應的元素
        // 向前移動了一個位置,所以對應的索引堆中的指向也得同步修改
        for (int i = popIndex + 1; i < indexArr.size(); i++) {
            int index = reverseArr.get(i); // 17
            indexArr.set(index, indexArr.get(index) - 1);
        }

        // 2. 移除原始數據中對應的元素
        array.remove(popIndex);

        // 3 維護一下索引指向數組reverseArr和索引堆indexArr
        reverseArr.set(indexArr.get(0), reverseArr.get(indexArr.get(indexArr.size() - 1)));
        indexArr.set(0, indexArr.get(indexArr.size() - 1));
        reverseArr.remove(popIndex);
        indexArr.remove(indexArr.size() - 1);

        // 4. shiftDown操作
        shiftDown(0, indexArr.size());

        // 5. 返回被移除的值
        return temp;
    }

    /** 隨意修改一個數據的優(yōu)先級猾漫,然后維護索引堆
     *  根據reverseArr可以快速定位當前索引在
     *  indexArr的位置点晴,然后將此位置與父與子比較
     *  優(yōu)先級,進而確定shiftUp操作或者shiftDown操作
     */
    public void setPriority(int i, E e) {
        array.set(i, e);

        // 找到i對應的索引堆的位置
        int index = reverseArr.get(i);

        // 找到對應的父節(jié)點
        int parent = parent(index);

        if(parent != index && compare.compare(array.get(indexArr.get(index)), array.get(indexArr.get(parent))) > 0) {
            shiftUp(index);
        } else {
            shiftDown(index, indexArr.size());
        }
    }

    /** 為一個數組快速建立一個索引堆
     *  找到二叉堆最后一個還有子節(jié)點的位置悯周,倒敘
     *  節(jié)點粒督,每個節(jié)點進行shiftDown操作!
     */

    public void heapify(ArrayList<E> arr){
        this.array = arr;
        this.indexArr = new ArrayList<>();
        this.reverseArr = new ArrayList<>();

        int n = 0;
        int m = 0;
        for (E e : this.array) {
            this.indexArr.add(n++);
            this.reverseArr.add(m++);
            // 這里的m之前寫的是n++和n,程序老是報錯禽翼,
            // 查了好久屠橄,居然犯這種低級錯誤!教訓叭虻病锐墙!
        }

        int last = parent(indexArr.size() - 1);
        for (int i = last; i >= 0; i--) {
            shiftDown(i, indexArr.size());
        }
    }

    @Override
    public String toString() {
        return array.toString();
    }
}

  • 測試類
import java.util.ArrayList;
import java.util.Random;

public class Main {
    public static void main(String[] args) {
        //addPopP();
        heapify();
    }

    public static void addPopP() {
        IndexHeap<Integer> myHeap = new IndexHeap<>((a, b) -> a - b);
        Random random = new Random();
        int n = 10000;
        for (int i = 0; i < n; i++) {
            myHeap.add(random.nextInt(10000));
        }

        for (int i = 0; i < 100; i++) {
            myHeap.setPriority(random.nextInt(n), random.nextInt(10000000));
        }

        Integer first = myHeap.pop();
        while (!myHeap.isEmpty()){
            int cur = myHeap.pop();
            if(cur > first)
                throw new IllegalArgumentException("很抱歉,出錯了!");
        }

        System.out.println("完美长酗!");
    }

    public static void heapify() {
        ArrayList<Integer> arrayList = new ArrayList<>();
        IndexHeap<Integer> myHeap = new IndexHeap<>((a, b) -> a - b);
        Random random = new Random();
        int n = 10000;

        for (int i = 0; i < n; i++) {
            arrayList.add(random.nextInt(100000000));
        }

        myHeap.heapify(arrayList);

        Integer first = myHeap.pop();
        while (!myHeap.isEmpty()){
            int cur = myHeap.pop();
            if(cur > first)
                throw new IllegalArgumentException("很抱歉溪北,出錯了!");
        }
        System.out.println("完美!");
    }
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末夺脾,一起剝皮案震驚了整個濱河市之拨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌咧叭,老刑警劉巖蚀乔,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異菲茬,居然都是意外死亡吉挣,警方通過查閱死者的電腦和手機派撕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來听想,“玉大人腥刹,你說我怎么就攤上這事『郝颍” “怎么了衔峰?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蛙粘。 經常有香客問我垫卤,道長,這世上最難降的妖魔是什么出牧? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任穴肘,我火速辦了婚禮,結果婚禮上舔痕,老公的妹妹穿的比我還像新娘评抚。我一直安慰自己,他們只是感情好伯复,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布慨代。 她就那樣靜靜地躺著,像睡著了一般啸如。 火紅的嫁衣襯著肌膚如雪侍匙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天叮雳,我揣著相機與錄音想暗,去河邊找鬼。 笑死帘不,一個胖子當著我的面吹牛说莫,可吹牛的內容都是我干的。 我是一名探鬼主播寞焙,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼唬滑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了棺弊?” 一聲冷哼從身側響起晶密,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎模她,沒想到半個月后稻艰,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡侈净,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年尊勿,在試婚紗的時候發(fā)現(xiàn)自己被綠了僧凤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡元扔,死狀恐怖躯保,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情澎语,我是刑警寧澤途事,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站擅羞,受9級特大地震影響尸变,放射性物質發(fā)生泄漏。R本人自食惡果不足惜减俏,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一召烂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧娃承,春花似錦奏夫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至漫谷,卻和暖如春仔雷,著一層夾襖步出監(jiān)牢的瞬間蹂析,已是汗流浹背舔示。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留电抚,地道東北人惕稻。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像蝙叛,于是被迫代替她去往敵國和親俺祠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內容