数据结构:堆

461 阅读2分钟

今天简单聊聊一个称为堆的数据结构

堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。--百度百科

堆结构的使用场景

  • 构建优先队列(堆顶总是最大(最小)元素)

  • 堆排序

  • 寻找最大值/最小值

分类

最大堆(大顶堆)

每个父节点的值大于其子节点

最小堆(小顶堆)

每个父节点的值小于其子节点

堆和查找二叉树的区别

节点顺序

查找二叉树的右节点的值比父节点大,左节点的值比父节点小。而堆只要求子节点的值都比父节点小(大)。

内存占用

二叉树的数据结构包含了父节点对子节点的引用关系(指针),内存占用较高。而堆使用数组来保存数据,不包含引用关系,内存占用小。

搜索

因为二叉树的父子节点的相对大小有规律(右节点>父节点>左节点),并且保存了父子节点的引用关系,所以搜索效率高。而堆只限定了父节点和子节点的相对大小,搜索效率比二叉树低。

堆的实现

根据定义我们是使用数组来保存堆的,但是堆的数据结构又像二叉树一般有父节点,左右子节点,那么它们在数组的位置有何规律呢?

parent(i) = Math.floor((i - 1)/2);
left(i) = 2i + 1;
right(i) = 2i + 2;

下面是根据堆元素的位置关系实现的堆(最大堆),主要包含添加元素和删除堆顶方法

class Heap {
    constructor() {
        // 堆数据结构为数组
        this.data = [];
    }
    
    // 添加元素
    insert(data) {
        // 添加到末尾
        this.data.push(data);

        let i = this.data.length - 1;

        // 调整元素位置
        this.shiftUp(i);
    }

    // 上滤(自下而上调整)
    shiftUp(i) {
        while(1) {
            let parent = Math.floor((i - 1) / 2);
            let cur = this.data[i];

            if (parent < 0 || this.data[parent] >= cur) {
                break;
            }

            this.data[i] = this.data[parent];
            this.data[parent] = cur;

            i = parent;
        }
    }

    // 删除堆顶元素
    removeRoot() {
        const root = this.data[0];

        if (this.data.length < 1) {
            return null;
        }

        if (this.data.length === 1) {
            this.data = [];
            return root;
        }
        
        // 末尾元素提前,同时删除堆顶
        this.data[0] = this.data[this.data.length - 1];

        this.data.pop();

        // 调整元素位置
        this.shiftDown(0);

        return root;
    }

    // 下滤(自上而下调整)
    shiftDown(i) {
        const len = this.data.length;

        while(1) {
            const leftIdx = 2 * i + 1;
            const rightIdx = 2 * i + 2;
            const left = this.data[2 * i + 1];
            const right = this.data[2 * i + 2];
            const cur = this.data[i];
            
            if (leftIdx >= len || (left < cur && right < cur)) {
                break;
            }

            if (left > right) {
                if (left > cur) {
                    this.data[i] = this.data[leftIdx];
                    this.data[leftIdx] = cur;
                    i = leftIdx;
                }
            } else if (right > left) {
                if (right > cur) {
                    this.data[i] = this.data[rightIdx];
                    this.data[rightIdx] = cur;
                    i = rightIdx;
                }
            } else {
                break;
            }
        }
    }
}

因为堆的作用主要就在于堆顶的运用,所以我们只实现堆顶的移除方法,这已经满足大部分我们使用到堆顶的场景。

参考


欢迎到前端学习打卡群一起学习~516913974