查找最大权重且大小不大于给定限制的节点

问题描述

我正在看这个:

建议一个数据结构来处理运输箱,其中每个箱子都有:特殊的 ID、重量和尺寸。

从所有最大尺寸为 v(即 size <= v)的盒子中找到 O(log(n)) 时间内最重的盒子,其中n 是保存的盒子总数。

你可以假设所有的权重都不同。

我想将这些框保存在 AVL 树中。

我知道解决方案是根据盒子的大小对盒子进行排序并在每个节点中保存一些额外的数据,但我不确定哪些数据会有所帮助。

每个节点保存的额外数据示例:

  • 右子树中最重框的 ID。

示例

考虑以下 AVL 树,我们在其中寻找大小小于 25 的最重框:

enter image description here

什么样的信息有助于决定从根开始向右还是向左?


* 树的构建使用:https://www.cs.usfca.edu/~galles/visualization/AVLtree.html*

解决方法

您将按大小为 AVL 树设置密钥,并存储(和维护)可以在节点的子树中找到的最大权重,同时考虑节点自身的权重。

这将允许:

  • 找到从根节点到可以插入v 大小的节点的位置的路径(实际上并没有这样做)。就大小而言,此路径中的最后一个节点将是该潜在未来节点的最接近的前任或后继节点。
  • 从该路径(从根到找到的节点),您可以过滤没有更大尺寸的节点,并查看存储在其左子节点中的最大权重以及路径本身上的节点。在这些候选中选择权重最大的节点或最大权重最大的子树。如果它是子树,则在该节点处沿着子树向下走以找到具有此最大化权重的实际节点。所有这些都包括一次向下走(以找到最大尺寸的节点)和一次向上走(沿路径)和另一次向下走(遵循相同最大权重的路径)。所以它代表对数时间复杂度。

需要扩展对 AVL 树的操作,以确保正确保持最大权重。例如,当一个节点被插入时,它的权重应该一直上升,只要这个权重决定了存储在祖先节点中的最大权重。应该更新删除和旋转的算法,以便以合理的方式维护这些额外信息。但在不影响相关时间复杂度的情况下,这一切都是可能的。

实施

此实现将只专注于找到所需的节点。它不实现 AVL 平衡,也不实现删除操作。所以它是一个普通的二叉树,只有插入和查找功能。

我在具有 Nodesizeweight 属性的基本 maxWeight 类上定义了以下方法:

  • pathToSize(size):这将找到可以插入具有给定大小的节点作为叶节点的插入点。 它返回一个祖先节点数组,这些祖先节点构成了从根到父节点的路径,可以在其下插入这样的节点。

  • findHeaviest():这将从当前节点获取 maxWeight 信息并返回该节点的子树中与该权重匹配的节点。 三种可能:

    • 当前节点的权重与其 maxWeight 匹配:返回它
    • 节点的左子节点具有相同的 maxWeight:使用递归查找该子树中的节点。
    • 节点的右子节点具有相同的 maxWeight:使用递归查找该子树中的节点。
  • findHeaviestWithMaxSize(size):这实现了实际的算法。它调用 pathToSize(size) 来获取路径。 然后它将在该路径上查找大小不大于给定大小的节点。否则:这些是 路径右转的节点(除非它是路径上的最后一个节点)。对于这些节点,我们需要检查两个 事情:

    • 该节点本身的权重是否比我们目前发现的要大
    • 该节点的左子树是否承载了一个比我们目前发现的权重更大的节点。我们还不需要搜索那个子树,因为我们可以只检查那个左孩子的 maxWeight

    在这次扫描之后,我们知道去哪里寻找:要么我们有节点本身,要么我们有一个子树,我们应该在那里找到一个带有 maxWeight 的节点。 对于后一种情况,我们可以使用上面的方法 findHeaviest

下面我提供了一个 JavaScript 片段,它允许您定义一个带有数据对输入的二叉树,或者只是获得一个具有 10 个节点的随机树。 然后,您可以更改 size 参数的值,并直观地(带有小箭头)查看哪个节点被选中,其大小不大于并最大化权重。

显然,该代码段包含一些用于用户界面的代码,但核心是上述 Node 类的方法。

class Node {
    constructor(size,weight) {
        this.size = size;
        this.weight = weight;
        this.left = null;
        this.right = null;
        this.maxWeight = weight;
    }
    pathToSize(size) {
        // Find a path to a node that would be the parent if 
        //   a new node were added with the given size.
        let child = size < this.size ? this.left : this.right;
        if (child === null) return [this];
        // Use recursion to add the rest of the path
        return [this,...child.pathToSize(size)];
    }
    findHeaviest() {
        // Find a decendant that has a weight equal to the maxWeight of this node
        if (this.weight === this.maxWeight) return this;
        // As it is not this node that has that weight,it must be found
        // in the left or the right subtree:
        if (this.left !== null && this.left.maxWeight === this.maxWeight) {
            return this.left.findHeaviest();
        } else if (this.right !== null && this.right.maxWeight === this.maxWeight) {
            return this.right.findHeaviest();
        }
        throw "tree is inconsistent";
    }
    findHeaviestWithMaxSize(size) {
        let path = this.pathToSize(size);
        // All nodes that have a size up to the given size are represented by this path,as follows:
        // If a node on the path has a size that is not greater,then: 
        //    that node itself and its complete left subtree is in that collection.
        // The union of those collections has all nodes with a size up to the given size.
        // So we will now find where in those collections we have the heaviest node.
        let maxWeight = -Infinity; // The greatest weight we can find among valid nodes
        let bestTree = null; // subtree with heaviest node
        for (let node of path) {
            if (node.size > size) continue; // skip this node
            // Check the weight(!) of the current node:
            if (node.weight > maxWeight) {
                maxWeight = node.weight; // not its maxWeight!!
                bestTree = node;
            }
            // Check the maxWeight(!) of the left subtree
            node = node.left;
            if (node !== null && node.maxWeight > maxWeight) {
                maxWeight = node.maxWeight;
                bestTree = node;
            }
        }
        if (bestTree === null) return null; // there is no such node
        // Get the node with that maxWeight in the identified node/subtree 
        if (bestTree.weight === maxWeight) return bestTree;
        return bestTree.findHeaviest();
    }
    toString(node) {
        // Provide a string representation of this tree
        let left = [];
        let right = [];
        if (this.left !== null) left = this.left.toString(node).split("\n");
        if (this.right !== null) right = this.right.toString(node).split("\n");
        let leftWidth = left[0]?.length || 0;
        let rightWidth = right[0]?.length || 0;
        let width = leftWidth + 5 + rightWidth;
        let lines = Array.from({length: Math.max(left.length,right.length)},(_,i) =>
            (left[i] || " ".repeat(leftWidth)) + "     " + (right[i] || ""));
        if (lines.length) {
            lines.unshift(
                (leftWidth ? left[0].replace(/\S.*/,"┌").padEnd(leftWidth+2,"─") : " ".repeat(leftWidth+2))
                 + "┴┘└"[!leftWidth * 2 + !rightWidth]
                 + (rightWidth ? right[0].replace(/.*\S/,"┐").padStart(rightWidth+2,"─") : " ".repeat(rightWidth+2))
            );
            lines.unshift("│".padStart(leftWidth+3).padEnd(width));
        }
        lines.unshift((String(this.weight).padStart(4,"0") + "g").padStart(leftWidth+5).padEnd(width));
        lines.unshift((String(this.size).padStart(4,"0") + "m").padStart(leftWidth+5).padEnd(width));
        lines.unshift((node === this ? "▼" : "│").padStart(leftWidth+3).padEnd(width));
        return lines.join("\n");
    }
}

class Tree {
    constructor() {
        this.root = null;
        // Only needed for this demo:
        // for exporting the insertions that are made
        this.array = []; 
    }
    add(size,weight) {
        this.array.push([size,weight]);
        if (this.root === null) {
            this.root = new Node(size,weight);
            return;
        }
        let path = this.root.pathToSize(size);
        let parent = path.pop();
        if (size < parent.size) {
            parent.left = new Node(size,weight);
        } else {
            parent.right = new Node(size,weight);
        }
        // Adjust weight of ancestors
        while (parent !== undefined && parent.maxWeight < weight) {
            parent.maxWeight = weight;
            parent = path.pop();
        }
    }
    toString(markedNode) {
        if (this.root === null) return "";
        return this.root.toString(markedNode);
    }
    findHeaviestWithMaxSize(size) {
        if (this.root === null) return null;
        return this.root.findHeaviestWithMaxSize(size);
    }
    static from(arr) {
        let tree = new Tree;
        for (let pair of arr) {
            tree.add(...pair);
        }
        return tree;
    }
}

let tree = new Tree;

// I/O handling below:

let inpJson = document.getElementById("json");
let inpSize = document.getElementById("size");
let inpWeight = document.getElementById("weight");
let btnCreate = document.getElementById("create");
let output = document.querySelector("pre");

function refresh() {
    let node = tree.findHeaviestWithMaxSize(+inpSize.value);
    output.textContent = tree.toString(node);
    json.value = JSON.stringify(tree.array);
}

function load() {
    let array;
    try {
        array = JSON.parse(json.value);
    } catch {
        return;
    }
    tree = Tree.from(array);
    refresh();
}

inpJson.addEventListener("input",load);
inpSize.addEventListener("input",refresh);

btnCreate.addEventListener("click",function() {
    tree = randomTree();
    refresh();
});

function randBetween(a,b) {
    return Math.floor(Math.random() * (b - a + 1) + a);
}

function randPair() {
    return [randBetween(0,10),randBetween(0,10)];
}

function randomTree() {
    let array = Array.from({length: 10},randPair);
    return Tree.from(array);
}

load();
#json { width: 100% }
#size { width: 3em }
JSON with [size,weight] pairs,in insertion order:<br>
<input id="json" value="[[6,8],[4,1],[8,10],[5,5],[7,[9,6],9],[6,7],6]]"><br>
<button id="create">Randomise</button><br>
<hr>
Find heaviest node with size at most: <input id="size" type="number" value="5">m<br>
<pre>
</pre>

为了更好地欣赏这段代码,请确保在运行后使用“整页”链接将其放大。