数据结构(堆 heap)

在JS中,我们知道数据类型分为

原始类型(number, string, boolean, null, undefined)
引用类型(object) => Array, function, data, RegExp
原始类型都是保存在栈当中,引用类型都是保存在堆当中

举个例子

var a = 123  // 是原始类型,会在栈底部,创建一个叫做a的房间,里面放入123
image.png
var a = 123 // 是原始类型,会在栈底部,创建一个叫做a的房间,里面放入123
var b = a  // 再创建一个房间b,将a房间的值copy一份放在b房间
image.png
var a = 123 // 是原始类型,会在栈底部,创建一个叫做a的房间,里面放入123
var b = a  // 再创建一个房间b,将a房间的值copy一份放在b房间
a = 124 // 再创建一个房间a,里面放入值124,将之前房间a名字删掉,但里面值不删
image.png

因为原始值是不可改变的,只会改房间编号,并不会删除房间内的值,所以放在栈中的数据会一直放在底部。就像相机一样,我们删除照片的时候,只是把照片名删掉了,实际内存中还是会存有数据,所以我们要是想销毁数据,就只能大量往内部存照片,覆盖之前的数据就可以了

下面再看堆
var arr = [1, 2] // 会在栈内命名一个arr的房间,但一看值是个引用类型,就会把值放在堆里面,同时arr房间里面存着[1, 2]的房间名
image.png
var arr1 = arr // 再在栈内创建一个arr1, 将arr拷贝一份放在arr1房间内,但是,拷贝的只是个引用地址,所以他们都是指向 堆内的[1, 2]
image.png

arr.push(3) // 这个时候再往arr push 数据的时候 就是在堆内添加数据,所以arr1也会随之改变

image.png
堆(Heap):堆可以看做是一颗用数组实现的二叉树,所以它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。

特点如下几点:

1.可以使用一维数组来表示。其中,第i个节点的父节点、子节点index如下:
  - parent(i) = (i - 1) / 2; (取整)
  - leftChild(i) = i * 2 + 1;
  - rightChild(i) = i * 2 + 2;
2.堆中某个节点的值总是不大于(最小堆)或不小于(最大堆)其父节点的值

上浮(shiftUp)和下沉(shiftDown)

  1. 上浮(shiftUp)(以构建最小堆为例)
    上浮操作就是,如果一个节点比它父节点小,则将它与父节点交换位置。这个节点在数组中的位置上升。
  2. 下沉(shiftDown)
    下沉操作就是,如果一个节点比它子节点大,那么它需要向下移动。称之为“下沉”。

堆的构建

给定一个一维数组[4,1,3,2,16,9,10,14,8,7],怎么把它构建成一个堆呢?使用自底向上的方法将数组转化为堆。
大体思路为:如果每一个节点都是一个堆(以这个节点为根),那么这个数组肯定是一个堆。自底向上的方法意思就是,自底向上依次调整每个节点,使得以该节点为根的树是一个堆。
(以最大堆为例)

首先将数组还原成一个完全二叉树
image.png
从倒数第一个非叶子节点开始(i = (n/2)-1,其中,n是数组的长度),依次对每个节点执行步骤3,将其调整成最大堆,直至第0个节点。

注:这里要说一下为啥从第一个非叶子节点开始,因为叶节点没有孩子节点,可以把它看成只有一个元素的堆。

将以第i个节点为根节点的子树调整为最大堆。假设根节点A[i]的左右子树的根节点index分别为left[i]和right[i],其中,left[i]和right[i]都是最大堆。采用逐级下降的方法进行调整,具体步骤如下:

(1) 从A[i]、A[left[i]]、A[right[i]]中找到最大的值,将其下标存到largest中。如果A[i]是最大的,那么以i为根节点的子树是最大堆;否则,交换A[i]和A[largest]的值。
(2) 对下标为largest为根的子树重复(1)操作,直至largest为叶子节点。

如图所示:

从 i = 4 开始遍历,此时,A[4] = 16,它是一个最大堆;
i = 3,A[3] = 2,与A[7]交换,因为i = 7是叶子节点,所以调整完毕。
i = 2,A[2] = 3,与A[6]交换,因为i = 6是叶子节点,所以调整完毕。此时,该树变成:


image.png

i = 1,A[1] = 1,与A[4]交换。因为i = 4不是叶子节点,所以要对以4为根的子树进行上述操作;此时,A[4] = 1,与A[9]交换,i = 9是叶子节点,调整完毕。如下图所示:


image.png

i = 0,A[0] = 4,与A[1]交换。因为i = 1不是叶子节点,对以1为根的子树进行上述操作;此时,A[1] = 4,与A[3]交换,i = 3不是叶子节点,对以3为根的子树重复进行操作;此时A[3] = 4,与A[8]交换,i = 8是叶子节点,调整完毕。最终得到最大堆:
image.png
堆的其它方法

1.插入
向数组最后插入一个数据,然后再向上调整。还以上述数组为例,插入一个11。

在数组最后插入11


image.png

比较该节点与其父节点的大小,11 > 7,交换两者,进行上浮。重复该步骤,11 < 14,此时满足最大堆性质,插入完毕。


image.png

2.删除(指删除堆顶的数据)
交换堆顶和最后一个数据,删除最后一个数据,再对堆顶数据进行向下调整算法。

交换堆顶和最后一个数据,并删除最后一个数据。


image.png

堆根节点进行下沉操作:对比该节点和其左右子节点,将该节点与最大的节点进行交换。重复此操作,直至该节点为叶子节点。(因为这个节点原本就是叶子节点交换上去的,比上面所有层的节点都小,所以调整完毕后,这个节点一定仍是叶子节点)
1与14交换
1与8交换
1与4交换


image.png
堆排序

基本思路:

将输入的数组建成最大堆。此时,堆顶元素就是最大值。
交换堆顶元素和末尾元素。此时,末尾元素是最大值。
将剩下 n-1 个元素重新构造成一个堆。重复执行上述步骤。
举个简单的例子:[14, 8, 10, 4]

将该数组构建成最大堆


image.png

交换堆顶元素和末尾元素。


image.png

忽略末尾元素,将剩下的元素利用下沉法重新构造为一个最大堆。
image.png

重复以上步骤,直至剩下的元素只剩下一个。最终得到如下结果,排序完毕:


image.png

JavaScript实现堆类

js中并没有“堆”这个数据结构,只能手动实现,以下是源代码。

class MaxHeap {
    constructor(heap) {
        this.heap = heap;
        this.heapSize = heap.length;
        this.buildMaxHeap();
    }

    // 构建最大堆
    buildMaxHeap() {
        for (let i = Math.floor(this.heapSize / 2) - 1; i >= 0; i--) {
            this.maxHeapify(i);
        }
    }

    //将以i为根节点的子树调整为最大堆
    maxHeapify(index) {
        let left = 2 * index + 1;
        let right = 2 * index + 2;
        let largest = index;
        if (left < this.heapSize && this.heap[left] > this.heap[largest]) largest = left;
        if (right < this.heapSize && this.heap[right] > this.heap[largest]) largest = right;
        if (largest !== index) {
            this.swapNum(index, largest);
            this.maxHeapify(largest);
        }
    }

    //交换i,j的值
    swapNum(i, j) {
        let temp = this.heap[i];
        this.heap[i] = this.heap[j];
        this.heap[j] = temp;
    }

    //插入一个数
    insert(num) {
        this.heap.push(num);
        this.heap.heapSize = this.heap.length;
        let index = this.heap.heapSize - 1;
        while (index != -1) {
            index = this.shiftUp(index);
        }
        console.log(this.heap);
    }

    //删除堆顶元素
    pop() {
        this.swapNum(0, this.heapSize - 1);
        this.heap.pop();
        this.heapSize = this.heap.length;
        let index = 0;
        while (1) {
            let temp = this.shiftDown(index);
            if (temp === index) break;
            else index = temp;
        }
    }

    //堆排序
    heapSort() {
        while (this.heapSize > 1) {
            this.swapNum(0, this.heapSize - 1);
            this.heapSize -= 1;
            let index = 0;
            while (1) {
                let temp = this.shiftDown(index);
                if (temp === index) break;
                else index = temp;
            }
        }
        this.heapSize = this.heap.length;
    }

    //上浮操作 - 将当前节点与父节点进行比较,如果该节点值大于父节点值,则进行交换。
    shiftUp(index) {
        let parent = Math.ceil(index / 2) - 1;
        if (this.heap[index] > this.heap[parent] && parent >= 0) {
            this.swapNum(index, parent);
            return parent;
        }
        return -1;
    }

    // 下沉操作 - 将当前节点与左右子节点进行比较,如果该节点值不是最大,则进行交换
    shiftDown(index) {
        let left = Math.floor(index * 2) + 1;
        let right = left + 1;
        let largest = index;
        if (left < this.heapSize && this.heap[left] > this.heap[largest]) largest = left;
        if (right < this.heapSize && this.heap[right] > this.heap[largest]) largest = right;
        if (largest !== index) {
            this.swapNum(index, largest);
        }
        return largest;
    }

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容

  • 堆(Heap)可以看成近似完全二叉树的数组,树中每个节点对应数组中一个元素。除了最底层之外,该树是完全充满的,最底...
    BlowMan阅读 1,491评论 0 0
  • 堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。...
    唐先僧阅读 248,915评论 21 252
  • 堆,其实是一个完整的二叉树(除了叶节点外,其他节点都有左右子节点)。堆有以下两种形式: 最大堆:根部元素的值最大,...
    Lebron_James阅读 451评论 0 1
  • 什么是堆? 二叉堆本质上是一颗完全二叉树,它分为两个类型: 最大堆什么是最大堆?最大堆的任何一个父节点的值,都大于...
    花逝97阅读 191评论 0 1
  • 堆就是用数组实现的二叉树 所有它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。...
    mingweiqi阅读 238评论 0 0