【问题标题】:Is it possible to implement binary heap without using additional data structure (e.g. array, linked list)是否可以在不使用额外数据结构(例如数组、链表)的情况下实现二进制堆
【发布时间】:2021-11-19 05:41:34
【问题描述】:

我可以只使用 TreeNode 接口来实现二叉堆吗(有孩子,或左/右,或/和父母......类似这样的东西)?

我不想依赖使用数组或链表。

如果我不使用数组或链表,则无法将下一个元素插入正确的位置并使其保持完整的二叉树(所有非叶节点都已满)。也有麻烦取出根和重新堆。

【问题讨论】:

  • 你不想使用链表,但是二叉树会有 links 到子节点...这就像是链表概念的扩展列表。

标签: data-structures heap


【解决方案1】:

一个关键的观察是:

一棵完全二叉树中从根到最后一个叶子的路径用树的大小(树中的节点数)的二进制表示来表示。

例如,这棵树有 9 个节点。

                  1
                /   \
               4     2
              / \   / \
             6   5 3   7
            / \
           9   8

9 在二进制中是 1001。跳过最重要的“1”,这可以从左到右读取为 0、0、1 或“左-左-右”。这确实描述了从根到值为 8 的叶节点的路径!

当您需要为新节点找到 插入 点时,同样的原则也适用。然后首先增加大小,因此在示例中变为 10。二进制表示为 1010。跳过第一个数字,这表示“左-右-左”。最后一个方向(“左”)提供有关必须添加的边缘的信息。事实上,“左-右”将我们带到值为 5 的节点,并且必须插入一个新节点作为该节点的左子节点!

要在插入后恢复堆属性,请跟踪指向新插入叶的路径(例如,从递归函数返回时),然后将路径返回,每次都验证堆属性,并且必要时交换值。

类似地,对于根值的提取:首先找到要删除的节点(见上文),删除该节点并将删除的值分配给根节点。然后筛选堆以恢复堆属性。

这是一个纯 JavaScript 的实现——应该很容易将它移植到任何其他语言:

class Node {
    constructor(value) {
        this.value = value;
        this.left = this.right = null;
    }
    swapValueWith(other) { // We don't swap nodes, just their values
        let temp = this.value;
        this.value = other.value;
        other.value = temp;
    }
}

class HeapTree {
    constructor() {
        this.root = null;
        this.size = 0;
    }
    insert(value) {
        this.size++;
        if (this.root == null) {
            this.root = new Node(value);
        } else { // Use the binary representation of the size to find insertion point
            this.insertRecursive(this.root, 1 << (Math.floor(Math.log2(this.size)) - 1), value);
        }
    }
    insertRecursive(node, bit, value) {
        let side = this.size & bit;
        let child;
        if (side > 0) {
            if (bit == 1) node.right = new Node(value);
            child = node.right;
        } else {
            if (bit == 1) node.left = new Node(value);
            child = node.left;
        }
        if (bit > 1) this.insertRecursive(child, bit>>1, value)
        if (node.value > child.value) node.swapValueWith(child); // sift up
    }
    extract() {
        if (this.root == null) return; // Nothing to extract
        let value = this.root.value;  // The value to return
        if (this.size == 1) {
            this.root = null;
        } else {
            // Use the binary representation of the size to find last leaf -- to be deleted
            this.root.value = this.deleteRecursive(this.root, 1 << (Math.floor(Math.log2(this.size)) - 1));
            // Sift down
            let node = this.root;
            while (true) {
                let minNode = node;
                if (node.left != null && node.left.value < minNode.value) minNode = node.left;
                if (node.right != null && node.right.value < minNode.value) minNode = node.right;
                if (minNode === node) break;
                node.swapValueWith(minNode);
                node = minNode;
            }
        }
        this.size--;
        return value;
    }
    deleteRecursive(node, bit) {
        let side = this.size & bit;
        let child;
        if (side > 0) {
            child = node.right;
            if (bit == 1) node.right = null;
        } else {
            child = node.left;
            if (bit == 1) node.left = null;
        }
        return bit == 1 ? child.value : this.deleteRecursive(child, bit>>1);
    }
}

// Demo
let heap = new HeapTree();

for (let value of [4,2,5,8,7,9,0,3,1,6]){
    heap.insert(value);
}
// Output the values in sorted order:
while (heap.root != null) {
    console.log(heap.extract());
}

【讨论】:

  • @curious_birdie,对此答案有何反馈?
猜你喜欢
  • 1970-01-01
  • 2015-02-27
  • 2014-11-29
  • 2012-05-01
  • 1970-01-01
  • 2022-01-12
  • 1970-01-01
  • 2013-02-22
  • 2019-09-03
相关资源
最近更新 更多