简体   繁体   中英

Algorithms for converting from complete-binary-search-tree order to sorted order and vice versa

This question is similar to Sorted list to complete BST array representation but perhaps more specifically focused. This question could be used to solve Inserting node dynamically in Complete Binary Search Tree .

Consider a complete binary tree represented in memory as a contiguous array a[0..n) , where element a[0] is the root of the tree, and for any node a[i] , it has left child a[2*i+1] and right child a[2*i+2] (if those indices are less than n ).

C++ programmers will be familiar with this representation because it's used by std::make_heap . std::make_heap(a, a+n) takes an unsorted array (which can be viewed as an unsorted complete binary tree) and permutes its elements (which can be viewed as tree rotations) to turn the tree into a complete binary heap where each node's value is greater than either of its children. We say that the resulting array is in "max-heap order."

On the other hand, if each node's value is greater than its left child but less than its right child, then we say that the complete binary tree is a complete binary search tree . In this case let's say that the resulting array is in "level order." [1]

Whereas there are many permissible "max-heap orders" for a given set of elements, each set of elements has only a single unique "level order."

The following vectors are in level order:

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

What I'm looking for is a family of efficient algorithms for:

  1. permuting an unsorted sequence into level order
  2. permuting a sorted sequence into level order
  3. permuting a level-order sequence into sorted order

When I say efficient , I mean algorithms that work without deep recursion, without dynamic memory allocation, and without temporary arrays. I already know that the permutation cannot be done particularly quickly; I'd hope for O(n lg n).

Notice that parts 2 and 3 are basically asking to come up with a mapping OldIndex -> NewIndex ; once you have such a function, you can do the permutation in-place using one of these algorithms .

Part 1 is asking for the implementation of nonstd::make_searchtree by analogy to std::make_heap . Part 3 is asking for the implementation of nonstd::sort_searchtree by analogy to std::sort_heap .


[1] — I basically made up this term "level order." If you know a more widely recognized academic term for this ordering, please leave a comment!

We can get a Theta(n log n)-time algorithm for 1 and a linear time algorithm for 2 and 3 as follows. For 1, we sort and apply 2. For 2, we use an inverse Faro shuffle and a rotation to get the leaves to the end of the array, then "recurse" (tail recursion, so it's actually just a for loop) on the subtree with the leaves removed. For 3, we do the inverse steps of 2 in reverse order.

The C++ code below uses a Theta(n log n) Faro shuffle/inverse shuffle algorithm because it's easier than Peiyush Jain's algorithm . Note that Peiyush's algorithm may not be faster on real hardware for any realistic value of n due its poor cache utilization.

I have tested the code below on literally one input. You are hereby warned.

#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);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM