Алгоритмы преобразования порядка полного бинарного дерева поиска в отсортированный порядок и наоборот
Этот вопрос похож на Сортированный список для завершения представления массива BST, но, возможно, более конкретно. Этот вопрос можно использовать для динамического решения проблемы вставки узла в полное дерево двоичного поиска.
Рассмотрим полное двоичное дерево, представленное в памяти как непрерывный массив a[0..n)
где элемент a[0]
это корень дерева, и для любого узла a[i]
оставила ребенка a[2*i+1]
и правильный ребенок a[2*i+2]
(если эти показатели меньше n
).
Программисты C++ будут знакомы с этим представлением, потому что оно используется std::make_heap
, std::make_heap(a, a+n)
берет несортированный массив (который можно рассматривать как несортированное полное двоичное дерево) и переставляет его элементы (которые можно рассматривать как повороты дерева), чтобы превратить дерево в полную двоичную кучу, где значение каждого узла больше, чем любой из его дочерних элементов. Мы говорим, что результирующий массив находится в "порядке максимальной кучи".
С другой стороны, если значение каждого узла больше, чем его левый потомок, но меньше, чем его правый потомок, то мы говорим, что полное двоичное дерево является полным двоичным деревом поиска. В этом случае предположим, что результирующий массив находится в "порядке уровня". [1]
Принимая во внимание, что существует много допустимых "порядков максимальной кучи" для данного набора элементов, каждый набор элементов имеет только один уникальный "порядок уровней".
Следующие векторы расположены в порядке уровней:
std::vector<int> v1 = { 3, 1, 4, 0, 2 };
// corresponds to the complete binary search tree
// 3
// 1 4
// 0 2
std::vector<int> v2 = { 6, 3, 8, 1, 5, 7, 9, 0, 2, 4 };
// corresponds to the complete binary search tree
// 6
// 3 8
// 1 5 7 9
// 0 2 4
Я ищу семейство эффективных алгоритмов для:
- перестановка несортированной последовательности в порядке уровней
- перестановка отсортированной последовательности в порядке уровня
- перестановка последовательности порядка уровней в отсортированный порядок
Когда я говорю " эффективный", я имею в виду алгоритмы, которые работают без глубокой рекурсии, без динамического выделения памяти и без временных массивов. Я уже знаю, что перестановка не может быть сделана особенно быстро; Я надеюсь на O (n lg n).
Обратите внимание, что части 2 и 3 в основном просят придумать карту OldIndex -> NewIndex
; Если у вас есть такая функция, вы можете выполнить перестановку на месте, используя один из этих алгоритмов.
Часть 1 просит об осуществлении nonstd::make_searchtree
по аналогии с std::make_heap
, Часть 3 просит об осуществлении nonstd::sort_searchtree
по аналогии с std::sort_heap
,
[1] - Я в основном придумал этот термин "уровень порядка". Если вы знаете более широко признанный академический термин для этого заказа, пожалуйста, оставьте комментарий!
1 ответ
Мы можем получить алгоритм тета (n log n) времени для 1 и алгоритм линейного времени для 2 и 3 следующим образом. Для 1 мы сортируем и применяем 2. Для 2 мы используем обратное перемешивание Фаро и вращение, чтобы получить листья до конца массива, затем "recurse" (хвостовая рекурсия, так что это фактически просто цикл for) на поддерево с удаленными листьями. Для 3 мы делаем обратные шаги 2 в обратном порядке.
Приведенный ниже код C++ использует алгоритм тэта (n log n) Фаро в случайном порядке / обратного перемешивания, поскольку он проще, чем алгоритм Пейюша Джайна. Обратите внимание, что алгоритм Пейюша не может быть быстрее на реальном оборудовании для любого реалистического значения n из-за его плохого использования кэша.
Я проверил код ниже буквально на одном входе. Настоящим вас предупреждают.
#include <algorithm>
#include <cassert>
#include <iostream>
#include <iterator>
#include <numeric>
#include <vector>
namespace {
// Transforms [a0 b0 a1 b1 ... an-1 bn-1 an] to [a0 a1 ... an b0 b1 ... bn-1]
// and returns an iterator to b0. The running time is Theta(n log n). If you're
// feeling ambitious, you could try Peiyush Jain's linear-time algorithm.
template <typename RandomAccessIterator>
RandomAccessIterator InvertFaroShuffle(RandomAccessIterator first,
RandomAccessIterator last) {
using Index =
typename std::iterator_traits<RandomAccessIterator>::difference_type;
Index size = last - first;
assert((size & 1) == 1);
if (size == 1) {
return last;
}
RandomAccessIterator middle = first + (((size + 1) >> 2) << 1);
return std::rotate(InvertFaroShuffle(first, middle - 1), middle,
InvertFaroShuffle(middle, last));
}
// Theta(n log n)-time algorithm for #2.
template <typename RandomAccessIterator>
void SortedToLevelOrder(RandomAccessIterator first, RandomAccessIterator last) {
using Index =
typename std::iterator_traits<RandomAccessIterator>::difference_type;
Index size = last - first;
if (size <= 1) {
return;
}
unsigned height = 1;
while ((Index{2} << height) - 1 < size) {
height++;
}
for (unsigned level = height; level > 0; level--) {
Index effective_size = std::min((Index{2} << level) - 1, size);
Index leaf_count =
std::min(Index{1} << level, size - ((Index{1} << level) - 1));
InvertFaroShuffle(first, first + 2 * leaf_count - 1);
std::rotate(first, first + leaf_count, first + effective_size);
}
}
// Theta(n log n)-time algorithm for #1.
template <typename RandomAccessIterator>
void UnsortedToLevelOrder(RandomAccessIterator first,
RandomAccessIterator last) {
std::sort(first, last);
SortedToLevelOrder(first, last);
}
// Transforms [a0 a1 ... an b0 b1 ... bn-1] to [a0 b0 a1 b1 ... an-1 bn-1 an].
// The running time is Theta(n log n). If you're feeling ambitious, you could
// try Peiyush Jain's linear-time algorithm.
template <typename RandomAccessIterator>
void FaroShuffle(RandomAccessIterator first, RandomAccessIterator last) {
using Index =
typename std::iterator_traits<RandomAccessIterator>::difference_type;
Index size = last - first;
assert((size & 1) == 1);
if (size == 1) {
return;
}
Index half = (size + 1) >> 1;
RandomAccessIterator middle = first + half;
Index quarter = half >> 1;
middle = std::rotate(first + quarter, middle, middle + quarter);
FaroShuffle(first, middle - 1);
FaroShuffle(middle, last);
}
// Theta(n log n)-time algorithm for #3.
template <typename RandomAccessIterator>
void LevelOrderToSorted(RandomAccessIterator first, RandomAccessIterator last) {
using Index =
typename std::iterator_traits<RandomAccessIterator>::difference_type;
Index size = last - first;
if (size <= 1) {
return;
}
unsigned height = 1;
while ((Index{2} << height) - 1 < size) {
height++;
}
for (unsigned level = 1; level < height + 1; level++) {
Index effective_size = std::min((Index{2} << level) - 1, size);
Index leaf_count =
std::min(Index{1} << level, size - ((Index{1} << level) - 1));
std::rotate(first, first + (effective_size - leaf_count),
first + effective_size);
FaroShuffle(first, first + 2 * leaf_count - 1);
}
}
void PrintList(const std::vector<int>& list) {
for (int elem : list) {
std::cout << ' ' << elem;
}
std::cout << '\n';
}
} // namespace
int main() {
std::vector<int> list(10);
std::iota(list.begin(), list.end(), 0);
PrintList(list);
SortedToLevelOrder(list.begin(), list.end());
PrintList(list);
LevelOrderToSorted(list.begin(), list.end());
PrintList(list);
}