js版數據結構_09 二叉堆

ps:再來實現一個非常重要的數據結構 ——二叉堆

知道導航:

  • 什麼是二叉堆
  • 二叉堆的具體實現(以最小堆爲主)

那什麼是二叉堆呢?

首先二叉堆它是一種特殊的二叉樹,它有兩個特性

  • 它是一棵完全二叉樹,表示樹的每一層都有左側和右側子節點(除了最後一層的葉節點),
    並且最後一層的葉節點儘可能都是左側子節點,這叫作結構特性。
  • 二叉堆不是最小堆就是最大堆。最小堆允許你快速導出樹的最小值,最大堆允許你快速
    導出樹的最大值。所有的節點都大於等於(最大堆)或小於等於(最小堆)每個它的子
    節點。這叫作堆特性

額。。說白了,二叉堆是一棵完全二叉樹,且它不是最小堆就是最大堆。

那麼什麼是最小堆呢?
即從樹頂到樹底,一層比一層的數值小
在這裏插入圖片描述
最大堆與它相反

來看二叉堆的實現吧

我們接下來我實現以下功能:

  • insert(val)像堆中插入新數據
  • extract()剔除最小值(最小堆),最大堆就剔除最大值
  • findMin() 返回最小者,最大堆中就findMax返回最大值唄

前提:
我們要知道儲存一棵二叉樹一般有兩種方式,一個是“指針”的形式即保存數據之間的聯繫。
第二種形式即使用數組,我們可以事先給樹編寫序號,利用數組的索引位置一一對應。如下圖
在這裏插入圖片描述
這時我們就得需要知道一些東西
在數組中給我們一個數據的位置,我們如何拿到它的父、子節點?
通過一些規律觀察我們可以有以下結論。
數組中如果給定一已存儲了節點元素個位置index,那麼

  • 它的左子節點位置是 2 * index + 1(如果位置可用);
  • 它的右子節點位置是 2 * index + 2(如果位置可用);
  • 它的父節點位置是index / 2(如果位置可用)。

二叉堆的數據存儲結構就是使用的數組。故即接下我們先要實現上面的三個輔助函數

 getLeftIndex(index) {
                return 2 * index + 1;
            }
            getRightIndex(index) {
                return 2 * index + 2;
            }
            getParentIndex(index) {
                if (index === 0) {
                    return undefined;
                }
                return Math.floor((index - 1) / 2);
            }

開始構建最小堆類 MinHeap

class MinHeap {
            constructor() {
                this.heap = [];
            }
            }

爲了書寫簡單我將輔助函數也寫在了類的裏面

class MinHeap {
            constructor() {
                this.heap = [];
            }

            //下面三個是工具函數
            getLeftIndex(index) {
                return 2 * index + 1;
            }
            getRightIndex(index) {
                return 2 * index + 2;
            }
            getParentIndex(index) {
                if (index === 0) {
                    return undefined;
                }
                return Math.floor((index - 1) / 2);
            }
            }

來實現insert方法吧

實現insert方法的核心就是它裏面的上移操作。
什麼上移及怎麼上移呢?
看圖
在這裏插入圖片描述
新來一個節點2,要加入堆中,開始加入堆底。即放到節點3的右孩子位置上
在這裏插入圖片描述
這是因爲二叉堆的特性,最小堆父節點數據是要小於它每個子節點的。現在不符合這一特性了,即節點2要進行上移。怎麼上移呢,它先和自己的父節點進行數據的比較。若比其父節點數值小。則進行位置交換
在這裏插入圖片描述
交換完成
在這裏插入圖片描述
然後再再此位置上與它現在的父節點進行數據的比較。還是若小於其父節點交換位置。不小於則固定其現有位置

代碼實現:

            insert(value) {
                if (value != null) {
                    this.heap.push(value);
                    this.upHead(this.heap.length - 1); //上移操作
                    return true;
                }
                return false;
            }
                      //上移
            upHead(index) {
                    //拿到父節點
                    let parent = this.getParentIndex(index);
                    while (index > 0 && (this.heap[index] < this.heap[parent])) {

                        //進行父子節點交換
                        this.swap(this.heap, parent, index);
                        index = parent;
                        parent = this.getParentIndex(index);
                    }

                }
                 //交換
            swap(arr, a, b) {
                [arr[b], arr[a]] = [arr[a], arr[b]];
            }

再來實現extract方法吧

extract方法的作用是剔除堆頂。
extract方法的核心是堆化,先來看看堆化的思路吧。

在這裏插入圖片描述
如上圖我們要剔除堆頂,不能直接剔除就完事了吧。。。我們還要保持它的堆結構呢,怎麼保持呢?
在這裏插入圖片描述
首先讓這個堆頂節點1與它的子節點比較,小於它的子節點(廢話嘛)。交換兩者位置
在這裏插入圖片描述
交換完成之後,再與它的子節點比較。這時就出現了一點問題。如果是和節點7交換,那麼我們直接push掉即可,和6交換我們要怎麼辦呢?去heap裏面去找到嗎?這顯然不是我們不需要的。

來再換一種方法吧
在這裏插入圖片描述
首先我們就先把堆頂和堆底節點互換,然後把換完的新堆底扔出去。
在這裏插入圖片描述
這時新堆頂是7,它要下移。則有子節點比較。交換位置
在這裏插入圖片描述
接着比較,交換位置
在這裏插入圖片描述
大功告成

代碼實現:

           
            extract() {
                // 空堆
                if (this.heap.length === 0) {
                    return false;
                }
                // 堆中就一值
                if (this.heap.length === 1) {
                    return this.heap.shift();
                }

                //堆中多值
                let removeValue = this.heap[0];
                // 交換堆頂堆底
                [this.heap[0], this.heap[this.heap.length - 1]] = [this.heap[this.heap.length - 1], this.heap[0]];
                this.heap.pop();

                //進行堆化


                this.sinkDown(0);
                return removeValue;

            }
            //堆化
            sinkDown(index) {
                let element = index;
                const left = this.getLeftIndex(index);
                const right = this.getRightIndex(index);
                const size = this.size();
                if (left < size && (this.heap[element] > this.heap[left])) {
                    element = left;

                }
                if (right < size && this.heap[index] > this.heap[right]) {
                    element = right;
                }
                if (index !== element) {
                    this.swap(this.heap, index, element);


                    this.sinkDown(element);
                }

            }

findMin的實現就簡單多了,要找的就在堆頂也即數組頭部

            findMin() {
                return this.heap.length===0() ? undefined : this.heap[0];
            }

最大堆原理一樣,就是改一下小於大於號了這裏不做實現了,堆排序我接下來會把它放到幾個常見的排序總結中去

完整代碼

   <script>
        class MinHeap {
            constructor() {
                this.heap = [];
            }

            //下面三個是工具函數
            getLeftIndex(index) {
                return 2 * index + 1;
            }
            getRightIndex(index) {
                return 2 * index + 2;
            }
            getParentIndex(index) {
                if (index === 0) {
                    return undefined;
                }
                return Math.floor((index - 1) / 2);
            }

            insert(value) {
                if (value != null) {
                    this.heap.push(value);
                    this.upHead(this.heap.length - 1); //上移操作
                    return true;
                }
                return false;
            }

            //上移
            upHead(index) {
                    //拿到父節點
                    let parent = this.getParentIndex(index);
                    while (index > 0 && (this.heap[index] < this.heap[parent])) {

                        //進行父子節點交換
                        this.swap(this.heap, parent, index);
                        index = parent;
                        parent = this.getParentIndex(index);
                    }

                }
                //交換
            swap(arr, a, b) {
                [arr[b], arr[a]] = [arr[a], arr[b]];
            }


           
            extract() {
                // 空堆
                if (this.heap.length === 0) {
                    return false;
                }
                // 堆中就一值
                if (this.heap.length === 1) {
                    return this.heap.shift();
                }

                //堆中多值
                let removeValue = this.heap[0];
                // 交換堆頂堆底
                [this.heap[0], this.heap[this.heap.length - 1]] = [this.heap[this.heap.length - 1], this.heap[0]];
                this.heap.pop();

                //進行堆化


                this.sinkDown(0);
                return removeValue;

            }

            //堆化
            sinkDown(index) {
                let element = index;
                const left = this.getLeftIndex(index);
                const right = this.getRightIndex(index);
                const size = this.size();
                if (left < size && (this.heap[element] > this.heap[left])) {
                    element = left;

                }
                if (right < size && this.heap[index] > this.heap[right]) {
                    element = right;
                }
                if (index !== element) {
                    this.swap(this.heap, index, element);


                    this.sinkDown(element);
                }

            }


            size() {
                return this.heap.length;
            }
            isEmpty() {
                return this.size === 0;
            }
            findMin() {
                return this.heap.length===0() ? undefined : this.heap[0];
            }



        }
        let heap = new MinHeap();


        heap.insert(4);
        heap.insert(5);
        heap.insert(1);
        heap.insert(2);
        heap.insert(3);
        heap.extract();
        console.log(heap);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章