Преобразование максимальной кучи в двоичное дерево поиска - PullRequest
17 голосов
/ 11 февраля 2011

Нам дан массив из 2 m - 1 различных сопоставимых элементов, проиндексированных начиная с 1.

Мы можем рассматривать массив как полное двоичное дерево:

Node is placed at index i.
Left child is placed at 2i.
Right child is placed at 2i+1.

Например, массив

[7 6 4 5 2 3 1]

- это дерево

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

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

A[i] > A[2i] and A[i] > A[2i+1]

Существует ли достаточно быстрый, на месте алгоритм для перетасовки элементов массива вокруг так, чтобы получилось двоичное дерево (как описано выше) представляет собой двоичное дерево search ?

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

Например, перестановка указанного выше массива будет

[4 2 6 1 3 5 7]

, что соответствует бинарному дереву поиска

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

Ответы [ 3 ]

7 голосов
/ 11 февраля 2011

Сначала отметим, что мы можем - без ограничения общности - предположить, что в нашем двоичном дереве есть элементы 1,2,3, ... 2^m-1. Итак, теперь мы предполагаем, что у нас есть эти числа.

Затем я попытался бы преобразовать отсортированный массив (т.е. 1 2 3 4 5) в массив, представляющий отсортированное двоичное дерево.

В отсортированном двоичном дереве с (2^m)-1 элементами мы всегда имеем, что «основание» дерева состоит из всех неравных чисел, например, для m=3:

     4
  2     6
 1 3   5 7

Это означает, что в соответствующем массиве мы имеем, что последние числа - это все неравные числа:

4 2 6 1 3 5 7
      -------
         ^
         uneven numbers!

Таким образом, мы можем построить последнюю «строку» двоичного дерева, убедившись, что последние 2^(m-1) числа в соответствующем массиве - это все нечетные числа. Поэтому все, что нам нужно сделать для последней строки, - это создать функцию, которая перемещает все элементы в позиции с неравными индексами в последнюю строку.

Итак, давайте пока предположим, что у нас есть подпрограмма, которая - учитывая отсортированный массив в качестве входных данных - правильно устанавливает последнюю строку.

Затем мы можем вызвать подпрограмму для всего массива, чтобы построить последнюю строку, в то время как все остальные элементы остаются отсортированными. Когда мы применяем эту процедуру к массиву 1 2 3 4 5 6 7, мы имеем следующую ситуацию:

2 4 6 1 3 5 7
      -------
         ^
         correct!

После первого раунда мы применяем подпрограмму для оставшегося подмассива (а именно 2 4 6), которая создает вторую последнюю «строку» нашего двоичного дерева, в то время как мы оставляем оставшиеся элементы без изменений, поэтому получаем следующее:

 now correct as well!
   v
  ---
4 2 6 1 3 5 7
      -------
         ^
         correct from run before

Итак, все, что нам нужно сделать, - это построить функцию, которая правильно устанавливает последнюю строку (т.е. вторую половину массива)!

Это можно сделать в O(n log n), где n - входной размер массива. Поэтому мы просто пересекаем массив от конца к началу и меняем неровные позиции таким образом, чтобы последняя строка (то есть вторая половина массива) была правильной. Это можно сделать на месте. После этого мы сортируем первую половину массива (например, с помощью heapsort). Таким образом, все время выполнения этой подпрограммы составляет O(n log n).

Таким образом, время выполнения для массива размером n в общей сложности:

O(n log n) + O(n/2 log n/2) + O(n/4 log n/4) + ..., что совпадает с O(n log n). Обратите внимание, что мы должны использовать алгоритм сортировки на месте, такой как Heapsort, чтобы весь этот материал работал полностью на месте.

Мне жаль, что я не могу уточнить это дальше, но я думаю, что вы можете понять идею.

2 голосов
/ 11 февраля 2011

Пусть n = 2 m - 1. В линейном времени мы можем как создать максимальную кучу, так и извлечь элементы двоичного дерева поиска в отсортированном порядке, поэтому лучшее, на что мы можем надеяться ( при условии, что алгоритмы, основанные на сравнении) - это O (n log n) времени и O (1) пространства. Вот такой вот алгоритм.

  1. При j = n до 1, вытолкните элемент max из кучи max j-элемента и сохраните его в (недавно освобожденном) месте j. Это сортирует массив.

  2. Преобразование отсортированного массива в двоичное дерево поиска со стратегией «разделяй и властвуй». (Наивно это пространство Omega (log n), но я считаю, что мы можем сжать стек до O (1) log (n) -битных слов.)

    а. Treeify элементов меньше, чем корень.

    б. Treeify элементы больше, чем корень.

    с. Объедините деревья, повернув листья меньше, чем корень, в положение (= три переворота), чтобы оставить подзадачу в половину размера (O (n)).

    (08 04 12 02 06 10 14 01 03 05 07 09 11 13 15)16(24 20 28 18 22 26 30 17 19 21 23 25 27 29 31)

    (08 04 12 02 06 10 14)16(24 20 28 18 22 26 30)01 03 05 07 09 11 13 15 17 19 21 23 25 27 29 31

    (08 04 12)16(24 20 28)02 06 10 14 18 22 26 30 01 03 05 07 09 11 13 15 17 19 21 23 25 27 29 31

    (08)16(24)04 12 20 28 02 06 10 14 18 22 26 30 01 03 05 07 09 11 13 15 17 19 21 23 25 27 29 31

    16 08 24 04 12 20 28 02 06 10 14 18 22 26 30 01 03 05 07 09 11 13 15 17 19 21 23 25 27 29 31

0 голосов
/ 11 февраля 2011

Просто некоторые основные идеи:

  1. Двоичное дерево поиска - это двоичное дерево.
  2. Оба потомка корня - либо ноль, либо сами двоичные деревья поиска
  3. Значения удовлетворяют следующему условию: левый ребенок <корень <правый ребенок </li>

Условие 1 не является проблемой - куча также является двоичным деревом. Условие 2 проблематично, но предлагает подход снизу вверх. Условие 3 также не выполняется.

Снизу вверх означает: - Мы начинаем со всех листьев - это не проблема, это бинарные деревья поиска. - Теперь мы продолжаем рекурсивную прогулку по каждому уровню родителей до самого корня. - Поменяйте местами поддеревья, если левый ребенок больше правого. - Поменяйте корень с большим значением 2 детей (это правильный ребенок) - Этого может быть недостаточно - вам может потребоваться продолжить исправление правильного поддерева, пока оно снова не станет бинарным деревом поиска.

Это должно работать. Но все же - удаление верхнего элемента и вставка его в самобалансирующееся дерево будет более быстрым / лучшим подходом и намного проще в реализации (например, с использованием стандартных компонентов, таких как std :: map в c ++).

Другая идея: для бинарных деревьев поиска обладает свойством, что левый-правый-правый обход дерева получает отсортированные значения. Это можно сделать наоборот. Получение значений, отсортированных из кучи, также должно быть простым. Просто попробуйте объединить это - чтение из кучи и запись дерева непосредственно из отсортированных значений. Я думаю, что это можно сделать за O (n), но я не уверен, что это можно сделать на месте или нет, наверное, нет.

...