Реализация пользовательского двоичного дерева кучи — случайное удаление узла

Я пытался решить этот вопрос. Постановка задачи немного похожа на настроенное двоичное дерево кучи, если сравнить его с определением двоичного дерева кучи в ADT. В двоичном дереве кучи вы всегда делаете deleteMax/deletetMin в зависимости от типа дерева кучи, но здесь они хотят, чтобы вы вместо этого удалили конкретный узел в этой проблеме.

Что ж, мое решение не работает только в одном тестовом случае, когда я удаляю значение, которое является конечным узлом:

Вот усилия, которые я приложил до сих пор при написании класса Heap. Хотя исходный код большой, вы можете сосредоточиться на методе DeleteSpecificValueFromHeap, где я столкнулся с проблемой.

Я реализовал двоичное дерево кучи, используя массив (список в С# поддерживается массивами). Рассмотрим текущее состояние двоичного дерева кучи в массиве:

-1 12 5 13 20 6 7

Бинарное дерево кучи выглядит примерно так:

        -1
    /        \
   12         5
  /   \     /    \
13    20    6     7

Теперь я хочу удалить узел со значением 13. Этот случай не работает в моем двоичном дереве кучи. Можете ли вы указать, как это исправить? Метод DeleteSpecificValueFromHeap - это тот, который в настоящее время борется.

public class Heap
{
    List<int> items;

    public int Root
    {
        get { return items[0]; }
    }

    public Heap()
    {
        items = new List<int>();
    }

    public void Insert(int item)
    {
        items.Add(item);

        if (items.Count <= 1)
            return;

        var i = items.Count - 1;

        while (i > 0)
        {
            var parentNodeValue = items[(i - 1) / 2];
            var newNodeValue = items[i];

            if (newNodeValue < parentNodeValue)
            {
                //we need to percolate up this node. so swap it with parent.
                items[(i - 1) / 2] = newNodeValue;
                items[i] = parentNodeValue;
                //update the position of newly inserted node after swapping
                i = (i - 1) / 2;
            }
            else
                break;
        }
    }

    public void DeleteSpecificValueFromHeap(int val)
    {
        for (var i = 0; i < items.Count; i++)
        {
            if (items[i] == val)
            {
                items[i] = items[items.Count - 1];
                items.RemoveAt(items.Count - 1);
                //reheapify : percolate down this node ith position

                var leftChildIndex = (i * 2) + 1;
                var rightChildIndex = (i * 2) + 2;



                while (leftChildIndex <= items.Count - 1) //chilren are there in the array.
                {
                    //child nodes of node at ith position
                    var child1Value = items[leftChildIndex];

                    if (rightChildIndex <= items.Count - 1)
                    {
                        var child2Value = items[rightChildIndex];
                        var currentNodeValue = items[i];
                        if (child1Value < child2Value)
                        {
                            //swap ith node with child 1
                            items[i] = child1Value;
                            items[leftChildIndex] = currentNodeValue;
                            i = leftChildIndex;
                        }
                        else
                        {
                            items[i] = child2Value;
                            items[rightChildIndex] = currentNodeValue;
                            i = rightChildIndex;
                        }
                    }
                    else
                    {
                        //case of only one child
                        var currentNodeValue = items[i];
                        items[i] = child1Value;
                        items[leftChildIndex] = currentNodeValue;
                        i = leftChildIndex;
                    }
                    leftChildIndex = (i * 2) + 1;
                    rightChildIndex = (i * 2) + 2;
                }
                break;
            }
        }

    }

Обновление:

Я изменил свой метод DeleteSpecificValueFromHeap, как показано ниже, в соответствии с рекомендацией @Raudel, после чего тестовый пример, который я упомянул в посте, теперь в порядке, но тестовый пример № 9 по ссылке все еще не работает. Мне очень жаль, что я не могу предоставить входные данные, поскольку у него есть 0,1 миллиона входных данных, которые невозможно разместить здесь. Теперь мне нужен орлиный глаз, который может запустить мой код в пробном режиме и помочь мне, если все еще что-то не так?

public void DeleteSpecificValueFromHeap(int val)
        {
            for (var i = 0; i < items.Count; i++)
            {
                if (items[i] == val)
                {
                    items[i] = items[items.Count - 1];

                    if (i == items.Count - 1)
                    {
                        //you are deleting the right most leaf node at the lowest level
                        //so nothing needs to be done apart from deleting the node.
                        items.RemoveAt(items.Count - 1);
                        break;
                    }

                    items.RemoveAt(items.Count - 1);

                    if (i == 0)
                        //it is the root node. The only option is to percolate down.
                        PercolateDownTheNode(i);
                    else
                    {
                        var parentNodeValue = items[(i - 1) / 2];
                        if (items[i] < parentNodeValue)
                            PercolateUpTheNode(i);
                        else
                            PercolateDownTheNode(i);
                    }

                    break;
                }
            }

        }

        private void PercolateDownTheNode(int i)
        {
            //reheapify : percolate down this node ith position
            var leftChildIndex = (i * 2) + 1;
            var rightChildIndex = (i * 2) + 2;

            while (leftChildIndex <= items.Count - 1) //chilren are there in the array.
            {
                //child nodes of node at ith position
                var child1Value = items[leftChildIndex];

                if (rightChildIndex <= items.Count - 1)
                {
                    var child2Value = items[rightChildIndex];
                    var currentNodeValue = items[i];
                    if (child1Value < child2Value)
                    {
                        //swap ith node with child 1
                        items[i] = child1Value;
                        items[leftChildIndex] = currentNodeValue;
                        i = leftChildIndex;
                    }
                    else
                    {
                        items[i] = child2Value;
                        items[rightChildIndex] = currentNodeValue;
                        i = rightChildIndex;
                    }
                }
                else
                {
                    //case of only one child
                    var currentNodeValue = items[i];
                    items[i] = child1Value;
                    items[leftChildIndex] = currentNodeValue;
                    i = leftChildIndex;
                }
                leftChildIndex = (i * 2) + 1;
                rightChildIndex = (i * 2) + 2;
            }
        }

        private void PercolateUpTheNode(int i)
        {
            while (i > 0)
            {
                var parentNodeValue = items[(i - 1) / 2];
                var newNodeValue = items[i];

                if (newNodeValue < parentNodeValue)
                {
                    //we need to percolate up this node. so swap it with parent.
                    items[(i - 1) / 2] = newNodeValue;
                    items[i] = parentNodeValue;
                    //update the position of newly inserted node after swapping
                    i = (i - 1) / 2;
                }
                else
                    break;
            }
        }

person RBT    schedule 08.01.2018    source источник
comment
Когда вы говорите, что бинарное дерево кучи выглядит примерно так, вы показываете дерево со значением -1, но строка перед вами имеет 1. Также большая часть дерева имеет наибольшее значение на левом узле, за исключением нижнего левого. Я что-то пропустил?   -  person Enigmativity    schedule 08.01.2018
comment
@Enigmativity Ох! не моя вина. Я только что понял, что -1 был преобразован редактором в маркер. Я внес изменения, чтобы исправить значения массива. Это двоичное дерево с минимальной кучей, поэтому минимальный элемент всегда находится в корне после каждой вставки.   -  person RBT    schedule 08.01.2018
comment
@RBT, поскольку я не заметил точную ошибку в вашем коде, мне было интересно, помог ли вам мой ответ как-то. А как насчет оптимизации, о которой я тебе говорил?   -  person Raudel Ravelo    schedule 10.01.2018
comment
@RaudelRavelo Ваш пост был очень полезен. У меня определенно есть правильные указатели из ваших шагов алгоритма. На самом деле изначально у меня сложилось впечатление, что, поскольку это минимальная куча, каждый раз мне нужно будет фильтровать узел, который заменяет удаленный узел. Но это было бы верно только в случае стандартного АТД бинарного дерева кучи. В этом случае, когда мы можем удалить любой узел случайным образом, есть вероятность просачивания вверх или вниз по целевому узлу в зависимости от условия. Я отвечу на ваш ответ, как только закончу исправлять свой код. Я на это!   -  person RBT    schedule 10.01.2018
comment
@RaudelRavelo сначала я хочу исправить фундаментальную алгоритмическую ошибку в моей программе при удалении узла, а затем я попробую улучшить хеш-таблицу. Интересно, что при отправке текущего решения только один из тестовых случаев не проходит, а все остальные проходят. Моя первоначальная мысль заключалась в том, что текущий подход грубой силы с обходом O (n), который я делаю, чтобы найти удаляемый узел, безусловно, вызовет тайм-аут в нескольких тестовых случаях, но этого не произошло: P   -  person RBT    schedule 10.01.2018
comment
Спасибо за ответ! Я прочитал пару комментариев из обсуждения проблемы людей, получающих TLE из-за линейного поиска. Я модифицировал подобную кучу раньше, и поэтому я сообщил вам об оптимизации. Я помню, мы поставили дополнительную задачу для продвинутых студентов, где единственным способом получить решение nlogn было сделать это или использовать очень сложную структуру данных, которую они не должны были видеть ;)   -  person Raudel Ravelo    schedule 10.01.2018


Ответы (1)


Проблема в том, что когда вы удаляете в любой позиции, кроме последней, все элементы справа сдвигаются влево на одну позицию, и это может оказаться в куче, чтобы перестать быть кучей. Из 3 шагов ниже вы делаете первые два, но вы не делаете 3-й правильно.

1, Delete the value from the array but do not remove it (this creates a "hole" and the tree is no longer "complete") 

2. Replace the deleted value with the "fartest right node" on the lowest level of the heap.
//you can see the first two steps like a swap

3. Heapify (fix the heap)://but you have two possible cases now

     if ( newValue < its parent node )
        Do an UPHeap
     else
        Do a DownHeap

На 3-м шаге вы сравниваете с родителем, и это говорит вам, что делать: ВВЕРХ или ВНИЗ. Для этого я рекомендую вам создавать методы для UpHeap и DownHeap отдельно, потому что вы будете использовать их не один раз, и код станет более понятным.

Я также должен указать, что вы находите значение с помощью цикла, и это делает каждое удаление O (n). Как видно из условия задачи, они могут задать вам до 1e5 вопросов. Это, вероятно, даст вам превышение лимита времени (TLE) в зависимости от размера массива. Согласно эмпирическому правилу, практически для любого онлайн-судьи ожидаемое время решения проблемы составляет около 1 секунды. Итак, для массива размером 1e5 вам придется ждать дольше, чем это заставляет вас думать, что должно быть что-то лучше, и это правда.

Дело в том, что вы можете отслеживать позицию внутри кучи, которую имеет значение. Вы можете сохранить его в HashTable<int, int> (например), чтобы вы могли запросить заданное значение, чтобы получить позицию внутри кучи. Таким образом, вы избегаете цикла, чтобы получить позицию внутри кучи, но вам нужно обновлять ее каждый раз, когда вы перемещаете это значение в куче. Чтобы обновить его, вы должны добавить пару строк в методы UpHeap и DownHeap, и каждый раз, когда вы перемещаете значение вверх/вниз по куче, вы обновляете позиции замененных элементов в HashTable.

ОБНОВЛЕНИЕ

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

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ContestLibrary
{
    public class Heap
    {
        List<int> items;

        public int Root
        {
            get { return items[0]; }
        }

        public Heap()
        {
            items = new List<int>();
        }

        public int GetMin()
        {
            if(items.Count == 0)
                throw new Exception("Empty Heap");
            return items[0];
        }

        public void Insert(int item)
        {
            items.Add(item);
            PercolateUpTheNode(items.Count - 1);
        }

        public void DeleteSpecificValueFromHeap(int val)
        {
            for (var i = 0; i < items.Count; i++)
            {
                if (items[i] == val)
                {
                    items[i] = items[items.Count - 1];
                    items.RemoveAt(items.Count - 1);

                    if (i == items.Count)
                        return;//cause you deleted the right most node

                    var parentNodeValue = items[(i - 1) / 2];

                    if (items[i] < parentNodeValue)
                        PercolateUpTheNode(i);
                    else
                        PercolateDownTheNode(i);

                    return;
                }
            }
        }

        private void PercolateDownTheNode(int i)
        {
            while (i < items.Count / 2) {
                //get the min child first
                int minChildIndex = 2 * i + 1;
                if (minChildIndex < items.Count - 1 && items[minChildIndex] > items[minChildIndex + 1]) {
                    minChildIndex++;
                }

                if (items[i] <= items[minChildIndex])
                    return;//I'm smaller than the minimum of my children

                //swap
                int temp = items[i];
                items[i] = items[minChildIndex];
                items[minChildIndex] = temp;

                i = minChildIndex;
            }
        }

        private int ParentIndex(int i)
        {
            return (i - 1) / 2;
        }

        private void PercolateUpTheNode(int i)
        {
            while (i > 0)
            {
                var parentValue = items[ParentIndex(i)];
                var currentValue = items[i];

                if (currentValue < parentValue)//swap
                {
                    items[ParentIndex(i)] = currentValue;
                    items[i] = parentValue;
                    i = ParentIndex(i);
                }
                else
                    return;
            }
        }
    }

    public class Problem
    {

        static void Main(string[] args)
        {
            Heap heap = new Heap();
            int q = int.Parse(Console.ReadLine());
            while (q-->0)
            {
                var line = Console.ReadLine().Split();
                int type = int.Parse(line[0]);
                switch (type)
                {
                        case 1:
                            heap.Insert(int.Parse(line[1]));
                        break;
                        case 2:
                            heap.DeleteSpecificValueFromHeap(int.Parse(line[1]));
                        break;
                        default:
                            Console.WriteLine(heap.GetMin());
                        break;
                }
            }
        }
    }
}
person Raudel Ravelo    schedule 08.01.2018
comment
Я изменил свой код в соответствии с вашей рекомендацией, но тестовый пример № 9 по-прежнему не работает. Я думал, что тестовый пример, который я упомянул в своем посте, будет основной причиной тестового примера № 9, который будет автоматически исправлен в тот момент, когда я его исправлю, но мое предположение оказалось неверным. Я упускаю что-то еще, из-за чего случай № 9 не работает. Просто к вашему сведению, это не случай TLE. - person RBT; 11.01.2018
comment
Я еще раз взгляну на ваш код, чтобы увидеть, смогу ли я обнаружить ошибку. - person Raudel Ravelo; 11.01.2018
comment
@RBT Думаю, наконец, я дал вам ответ, который вы искали;) взгляните на ОБНОВЛЕНИЕ - person Raudel Ravelo; 11.01.2018
comment
Вот это да. Это сработало. Хотя вы помогли мне получить только 0,85 дополнительных балла, так как 17 из 18 тестов уже пройдены, но знания, которые вы мне дали, просто бесценны. Большое спасибо за то, что постоянно следите за тем, чтобы я достиг своей цели. #communityRocks - person RBT; 11.01.2018
comment
Кстати, как я упоминал ранее, вы должны были видеть, что даже обход O (n) для поиска удаляемого узла не приводил к истечению времени ожидания тестовых случаев, хотя это решение на основе хэш-карты для амортизированного поиска O (1) удаляемого узла, безусловно, является потрясающим улучшением этого решения, которое делает его еще лучше. - person RBT; 11.01.2018