Поиск узла с максимальным весом, размер которого не превышает заданного предела

Я смотрю на это:

Предложите структуру данных для обработки почтовых ящиков, где у каждого ящика есть: специальный идентификатор, вес и размер.

Из всех ящиков с максимальным размером v (т. е. size <= v) найдите самый тяжелый за время O(log(n)), где n — общее количество сохраненных ящиков.

Можно предположить, что все веса разные.

Я хочу сохранить эти ящики в дереве AVL.

Я знаю, что решением будет сортировка ящиков по их размеру и сохранение некоторых дополнительных данных в каждом узле, но я не уверен, какие данные будут полезны.

Пример дополнительных данных, сохраняемых в каждом узле:

  • Идентификатор самого тяжелого ящика в правом поддереве.

Пример

Рассмотрим следующее дерево AVL, где мы ищем самую тяжелую коробку, размер которой меньше 25:

введите здесь описание изображения

Какая информация поможет решить, идти ли вправо или влево от корня?


* дерево было построено с использованием: https://www.cs.usfca.edu/%7Egalles/visualization/AVLtree.html*


person Community    schedule 18.01.2021    source источник
comment
Каков будет порядок n и количество запросов?   -  person Abhinav Mathur    schedule 19.01.2021
comment
Не думайте, что вам здесь нужен AVLTree. Кучи должно хватить.   -  person displayName    schedule 19.01.2021


Ответы (1)


Вы должны указать свое дерево AVL по размеру и сохранить (и поддерживать) максимальный вес, который можно найти в поддереве узла, принимая во внимание также собственный вес узла.

Это позволит:

  • найти путь от корня до места, куда можно было бы вставить узел размером v (фактически не делая этого). Последний узел на этом пути будет ближайшим предшественником или преемником этого потенциального будущего узла с точки зрения размера.
  • по этому пути (от корня до найденного узла) вы можете отфильтровать узлы, которые не имеют большего размера, и посмотреть на максимальные веса, хранящиеся в их левом дочернем элементе, и на вес узла на сам путь. Выберите узел с наибольшим весом или поддерево с наибольшим максимальным весом среди этих кандидатов. Если это поддерево, пройдите вниз по поддереву в этом узле, чтобы найти фактический узел, который имеет этот максимальный вес. Все это состоит из одного обхода вниз (чтобы найти узел наибольшего размера), обхода вверх (по пути) и еще одного спуска (по пути равного максимального веса). Таким образом, это представляет собой логарифмическую временную сложность.

Операции с деревом AVL необходимо расширить, чтобы обеспечить правильное поддержание максимального веса. Например, когда узел вставлен, его вес должен увеличиваться до тех пор, пока этот вес определяет максимальный вес, хранящийся в узле-предке. Алгоритмы удаления и ротации должны быть обновлены, чтобы также правильно поддерживать эту дополнительную информацию. Но все это возможно, не затрагивая связанных с этим временных сложностей.

Реализация

Эта реализация будет сосредоточена только на поиске нужного узла. Он не реализует ни балансировку AVL, ни операцию удаления. Таким образом, это обычное бинарное дерево с функциями вставки и поиска.

Я определяю следующие методы для базового класса Node, который имеет свойства size, weight и maxWeight:

  • pathToSize(size): это найдет место вставки, где можно было бы вставить узел с заданным размером в виде листа. Он возвращает массив узлов-предков, составляющих путь от корня к родителю, ниже которого может быть вставлен такой узел.

  • findHeaviest(): это возьмет информацию maxWeight из текущего узла и вернет узел в поддереве этого узла, который соответствует этому весу. Есть три возможности:

    • The current node has a weight that matches its maxWeight: return it
    • Левый дочерний элемент узла имеет тот же maxWeight: используйте рекурсию, чтобы найти узел в этом поддереве.
    • Правый потомок узла имеет тот же maxWeight: используйте рекурсию, чтобы найти узел в этом поддереве.
  • findHeaviestWithMaxSize(size): реализует фактический алгоритм. Он вызывает pathToSize(size), чтобы получить путь. Затем он будет искать узлы на этом пути, размер которых не превышает заданного размера. Иначе говоря: это узлы, в которых путь повернул направо (если только это не был последний узел на пути). Для этих узлов нам нужно проверить две вещи:

    • does that node itself have a greater weight than we have found so far
    • содержит ли левое поддерево этого узла узел с большим весом, чем мы нашли до сих пор. Нам пока не нужно искать это поддерево, так как мы можем просто свериться с 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,5],[9,6],[5,9],[6,7],[8,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>

Чтобы лучше насладиться этим фрагментом, не забудьте увеличить его с помощью ссылки на всю страницу, как только он будет запущен.

person trincot    schedule 18.01.2021
comment
Привет, большое спасибо за ваш ответ, но я не согласен с тем, что этот тип информации может помочь определить, в каком направлении идти, найти узел с наибольшим размером, который не превышает заданную v. как вы можете это сделать? - person ; 19.01.2021
comment
Ну, это легкая часть. Это стандарт для работы деревьев AVL. Это бинарные деревья поиска, поэтому вы идете вправо, пока размер узла меньше, и влево, когда он больше. Если в последнем случае левого потомка нет, вы возвращаетесь к первому узлу меньшего размера. Это определяет конец пути от корня к узлу. Затем начинается вторая фаза поиска наибольшего веса. У меня нет времени писать код для этого, так как это довольно сложная работа: она должна охватывать вставку, удаление, вращение, извлечение,... - person trincot; 19.01.2021
comment
Это невозможно сделать за O (log n): и пройтись по поддереву в этом узле, чтобы найти фактический узел с этим максимальным весом, дерево не отсортировано по весу. - person ; 19.01.2021
comment
@john, может быть: мы не полагаемся на сортировку по весу. Когда вы находитесь в узле и хотите найти узел с весом, соответствующим максимальному весу, зарегистрированному в этом узле, у вас есть 3 возможности: либо это сам узел (легко проверить), либо он находится в левом поддереве, в этом случае левый дочерний элемент также имеет такой же зарегистрированный максимальный вес или находится в правом поддереве (то же самое рассуждение). Таким образом, вы можете выбрать путь, чтобы найти его. - person trincot; 19.01.2021
comment
Я добавил более подробное описание различных этапов и реализацию на JavaScript, которую вы можете запустить здесь. Он интерактивен, поэтому вы можете создать случайное дерево (или закодировать последовательность вставки с помощью нотации JSON), а затем указать размер, чтобы увидеть, какой узел выбран в визуальном представлении дерева. - person trincot; 19.01.2021
comment
что, если вы ищете вес 20, где максимальный вес для левого сына составляет 22, а для правого сына - 23? ты не можешь решить, куда идти - person ; 19.01.2021
comment
@john, я не понимаю, что ты имеешь в виду. Этот алгоритм не ищет определенный вес. Он просматривает выбранный узел (который находится в пределах ограничения по размеру), проверяет, что имеет его свойство maxWeight, а затем ищет этот вес. В этом случае дочерние элементы этого узла никогда не будут иметь больше maxWeight, чем это. Он может быть равен или меньше. Алгоритм выбирает ребенка, где он равен. Если у вас есть встречный пример, запустите фрагмент кода, введите его (в поле JSON) и посмотрите, что он делает. - person trincot; 19.01.2021
comment
Еще какие-то замечания? - person trincot; 20.01.2021
comment
Вы проделали хорошую работу! БОЛЬШОЕ СПАСИБО, никогда не встречал здесь таких как вы :) - person ; 20.01.2021
comment
Рад слышать ;-) - person trincot; 20.01.2021