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);

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