一步一步學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)和算法 (四) 索引堆

索引堆

之前建立堆的過程中所存在的問題

將一個數(shù)組進(jìn)行 heapify 之后, 數(shù)組元素的位置發(fā)生了變化, 有兩個缺點:

  1. 移動元素位置可能會造成大量的性能消耗.
  2. 在有些情況下, 元素位置有其他意義, 不能隨意改變元素位置.

建立索引堆

image

針對每一個元素, 添加一個 index 結(jié)構(gòu), 用來完成堆的建立. 建立完成后, 原數(shù)組元素位置不變, index 的值表示在堆中對應(yīng)位置的元素位置. 例如: 位置為 1 的 index 值為 10, arr[10] 位于堆的根節(jié)點, 其左孩子 index 值為 9, 表示 arr[9] 為根節(jié)點的左孩子.

在元素比較時, 比較的是 data 數(shù)據(jù), 在做元素交換時交換的是 index.

索引數(shù)組的含義, 是從堆的角度, 正向的理解, 即 indexes[i] 表示, 堆中第 i 個元素對應(yīng)的數(shù)據(jù)為 data[indexes[i]] (其索引為 indexes[i]).

代碼實現(xiàn)

相比較于之前最大堆的實現(xiàn), 改造為索引堆是很容易的, 需要做的是以下幾件事:

  1. 添加索引數(shù)組, 其大小與數(shù)據(jù)大小相同.
  2. 在用戶插入元素時, 需要給定該元素的索引, 該元素在數(shù)組的位置.
  3. 在比較元素大小時, 我們獲取到的索引為堆中的索引 k, 需要通過 indexes[k] 的方式轉(zhuǎn)換為數(shù)據(jù)的索引.
  4. 在交換元素時, 只需要交換其在 indexes 數(shù)組中的位置 swap(indexes[k], indexes[k / 2]).

完整的索引堆實現(xiàn)如下:

template<typename Item>
class IndexMaxHeap {
private:
    Item *data;
    int *indexes;
    int count;
    int capacity{};

    // 將 k 這個元素向上移動, 以滿足大根堆定義
    void shiftUp(int k) {
        while (k > 1 && data[indexes[k / 2]] < data[indexes[k]]) {
            swap(indexes[k], indexes[k / 2]);
            k /= 2;
        }
    }

    void shiftDown(int k) {
        while (2 * k <= count) {
            int j = 2 * k;
            if (j + 1 <= count && data[indexes[j + 1]] > data[indexes[j]]) {
                j += 1;
            }
            if (data[indexes[k]] >= data[indexes[j]]) {
                break;
            }
            swap(indexes[k], indexes[j]);
            k = j;
        }
    }
    
    public:
    IndexMaxHeap(int capacity) {
        data = new Item[capacity + 1];
        indexes = new int[capacity + 1];
        this->capacity = capacity;
        count = 0;
    }

    IndexMaxHeap(Item arr[], int n) {
        data = new Item[n + 1];
        indexes = new int[capacity + 1];
        this->capacity = n;
        count = n;
        for (int i = 0; i < n; i++) {
            data[i + 1] = arr[i];
        }
        for (int i = count / 2; i >= 1; i--) {
            shiftDown(i);
        }
    }

    ~IndexMaxHeap() {
        delete[] data;
        delete[] indexes;
    }

    int size() {
        return count;
    }

    bool isEmpty() {
        return count == 0;
    }

    // 傳入的 i 對于用戶來說, 是從 0 開始索引的
    void insert(int i, Item item) {
        assert(count + 1 <= capacity);
        assert(i + 1 >= 1 && i + 1 <= capacity);
        i += 1;
        data[++count] = item;
        indexes[++count] = i;
        shiftUp(count);
    }

    Item extractMax() {
        assert(count > 0);
        Item ret = data[indexes[1]];
        indexes[1] = indexes[count--];
        shiftDown(1);
        return ret;
    }

    // 返回最大元素的的索引
    int extractMaxIndex() {
        assert(count > 0);
        // 對于外部用戶來說, 索引減一
        int ret = indexes[1] - 1;
        indexes[1] = indexes[count--];
        shiftDown(1);
        return ret;
    }

    Item getItem(int i) {
        return data[i + 1];
    }

    void change(int i, Item newItem) {
        i += 1;
        data[i] = newItem;

        // 找到 indexes[j] = i, j 表示 data[i] 在堆中的位置.
        for (int j = 1; j <= count; j++) {
            if (indexes[j] == i) {
                shiftUp(j);
                shiftDown(j);
                return;
            }
        }
    }
};

change 操作

在上面的實現(xiàn)中, 添加了一個新的操作 create(), 用于修改指定數(shù)據(jù)位置的數(shù)據(jù)內(nèi)容. 在實現(xiàn)中, 我們需要找到該數(shù)據(jù)對應(yīng)于堆中的位置, 即找到一個 j, 使得 indexes[j] = i.

void change(int i, Item newItem) {
    i += 1;
    data[i] = newItem;
    // 找到 indexes[j] = i, j 表示 data[i] 在堆中的位置.
    for (int j = 1; j <= count; j++) {
        if (indexes[j] == i) {
            shiftUp(j);
            shiftDown(j);
            return;
        }
    }
}

在上面的實現(xiàn)中, 我們通過遍歷整個 indexes 數(shù)組, 找到和 i 相匹配的元素. 這一步操作的時間復(fù)雜度是 O(n), 因為后續(xù) shiftUp()shiftDown() 操作復(fù)雜度都為 logn, 因此總體的時間復(fù)雜度可以看做 O(n). 如果同時對 n 個堆進(jìn)行操作, 時間復(fù)雜度就達(dá)到了 O(n^2).

進(jìn)一步改進(jìn)

為了降低時間復(fù)雜度, 這里一個通用的方法就是建立反向索引數(shù)組, 如下圖所示:

image

數(shù)組 rev 表示反向索引, 其含義為, 站在數(shù)據(jù)的角度來看, 這個數(shù)據(jù)所對應(yīng)于堆中的位置是多少. 例如, 位于 10 處的數(shù)據(jù), 對應(yīng)于堆中的位置為 1, 即其位于根節(jié)點.

  • reverse[i] 表示索引 iindexes (堆) 中的位置

根據(jù)其定義, 有下面兩個等式成立

  • indexes[reverse[i]] = i
  • reverse[indexes[i]] = i

在進(jìn)行位置變更時, 需要同時變更:

  • indexes[i] = j
  • reverse[j] = i

完整的實現(xiàn)如下:

template<typename Item>
class IndexMaxHeap {
private:
    Item *data;
    int *indexes;
    int *reverse;
    int count;
    int capacity{};

    // 將 k 這個元素向上移動, 以滿足大根堆定義
    void shiftUp(int k) {
        while (k > 1 && data[indexes[k / 2]] < data[indexes[k]]) {
            swap(indexes[k], indexes[k / 2]);
            reverse[indexes[k / 2]] = k / 2;
            reverse[indexes[k]] = k;
            k /= 2;
        }
    }

    void shiftDown(int k) {
        while (2 * k <= count) {
            int j = 2 * k;
            if (j + 1 <= count && data[indexes[j + 1]] > data[indexes[j]]) {
                j += 1;
            }
            if (data[indexes[k]] >= data[indexes[j]]) {
                break;
            }
            swap(indexes[k], indexes[j]);
            reverse[indexes[k]] = k;
            reverse[indexes[j]] = j;
            k = j;
        }
    }

public:
    IndexMaxHeap(int capacity) {
        data = new Item[capacity + 1];
        indexes = new int[capacity + 1];
        reverse = new int[capacity + 1];
        for (int i = 0; i <= capacity; i++) {
            // reverse[i] = 0 表示數(shù)據(jù) i 在堆中不存在.
            reverse[i] = 0;
        }
        this->capacity = capacity;
        count = 0;
    }

    IndexMaxHeap(Item arr[], int n) {
        data = new Item[n + 1];
        indexes = new int[capacity + 1];
        reverse = new int[capacity + 1];
        reverse = new int[capacity + 1];
        for (int i = 0; i <= capacity; i++) {
            // reverse[i] = 0 表示數(shù)據(jù) i 在堆中不存在.
            reverse[i] = 0;
        }
        this->capacity = n;
        count = n;
        for (int i = 0; i < n; i++) {
            data[i + 1] = arr[i];
        }
        for (int i = count / 2; i >= 1; i--) {
            shiftDown(i);
        }
    }

    ~IndexMaxHeap() {
        delete[] data;
        delete[] indexes;
    }

    int size() {
        return count;
    }

    bool isEmpty() {
        return count == 0;
    }

    // 傳入的 i 對于用戶來說, 是從 0 開始索引的
    void insert(int i, Item item) {
        assert(count + 1 <= capacity);
        assert(i + 1 >= 1 && i + 1 <= capacity);
        i += 1;
        data[++count] = item;
        indexes[++count] = i;
        reverse[i] = count;
        shiftUp(count);
    }

    Item extractMax() {
        assert(count > 0);
        Item ret = data[indexes[1]];
        indexes[1] = indexes[count];
        reverse[indexes[1]] = 1;
        reverse[indexes[count]] = 0;
        count--;
        shiftDown(1);
        return ret;
    }

    // 返回最大元素的的索引
    int extractMaxIndex() {
        assert(count > 0);
        // 對于外部用戶來說, 索引減一
        int ret = indexes[1] - 1;
        indexes[1] = indexes[count];
        reverse[indexes[1]] = 1;
        reverse[indexes[count]] = 0;
        shiftDown(1);
        return ret;
    }

    // 判斷數(shù)組下標(biāo)為 i 的元素, 是否存在于堆中
    bool contain(int i) {
        assert(i + 1 >= 1 && i + 1 <= capacity);
        return reverse[i + 1] != 0;
    }

    Item getItem(int i) {
        assert(contain(i));
        return data[i + 1];
    }


    void change(int i, Item newItem) {
        assert(contain(i));
        i += 1;
        data[i] = newItem;

        // 找到 indexes[j] = i, j 表示 data[i] 在堆中的位置.
        int j = reverse[i];
        shiftUp(j);
        shiftDown(j);
        return;
    }
};
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蓝撇,一起剝皮案震驚了整個濱河市果复,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渤昌,老刑警劉巖虽抄,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異独柑,居然都是意外死亡迈窟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門忌栅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來车酣,“玉大人,你說我怎么就攤上這事索绪『保” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵瑞驱,是天一觀的道長娘摔。 經(jīng)常有香客問我,道長唤反,這世上最難降的妖魔是什么凳寺? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮彤侍,結(jié)果婚禮上肠缨,老公的妹妹穿的比我還像新娘。我一直安慰自己拥刻,他們只是感情好怜瞒,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著般哼,像睡著了一般吴汪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蒸眠,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天漾橙,我揣著相機(jī)與錄音,去河邊找鬼楞卡。 笑死霜运,一個胖子當(dāng)著我的面吹牛脾歇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播淘捡,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼藕各,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了焦除?” 一聲冷哼從身側(cè)響起激况,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎膘魄,沒想到半個月后乌逐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡创葡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年浙踢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灿渴。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡洛波,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逻杖,到底是詐尸還是另有隱情奋岁,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布荸百,位于F島的核電站闻伶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏够话。R本人自食惡果不足惜蓝翰,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望女嘲。 院中可真熱鬧畜份,春花似錦、人聲如沸欣尼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愕鼓。三九已至钙态,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間菇晃,已是汗流浹背册倒。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留磺送,地道東北人驻子。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓灿意,卻偏偏與公主長得像,于是被迫代替她去往敵國和親崇呵。 傳聞我的和親對象是個殘疾皇子缤剧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容

  • 索引堆是一個相對于普通堆更加高級的數(shù)據(jù)結(jié)構(gòu)。 為什么要引入索引堆這個數(shù)據(jù)結(jié)構(gòu)演熟? 在一些場景下鞭执,堆這個數(shù)據(jù)結(jié)構(gòu)不高效...
    李威威閱讀 800評論 0 1
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當(dāng)在唯一索引所對應(yīng)的列上鍵入重復(fù)值時,會觸發(fā)此異常芒粹。 O...
    我想起個好名字閱讀 5,259評論 0 9
  • 堆就是用數(shù)組實現(xiàn)的二叉樹,所以它沒有使用父指針或者子指針大溜。堆根據(jù)“堆屬性”來排序化漆,“堆屬性”決定了樹中節(jié)點的位置。...
    唐先僧閱讀 248,811評論 21 252
  • 文章圖片存儲在GitHub钦奋,網(wǎng)速不佳的朋友座云,請看《進(jìn)擊的堆:最大索引堆》 或者 來我的技術(shù)小站 godbmw.co...
    心譚閱讀 635評論 0 6
  • 云紅宇之鵬翔兮,吾心將融音神付材。 墨漆于之高崖兮朦拖,臥虎藏龍同人。 唇透光之天地兮厌衔,佳人如夢令書璧帝。 眼留點之化物兮,神...
    止兒徐子閱讀 593評論 9 8