Подтвердить что ты не робот

Создать сбалансированное двоичное дерево поиска из сортированного связанного списка

Какой лучший способ создать сбалансированное двоичное дерево поиска из отсортированного отдельно связанного списка?

4b9b3361

Ответ 1

Как создать узлы снизу вверх?

Эта временная сложность решения - O (N). Подробное объяснение в моем сообщении в блоге:

http://www.leetcode.com/2010/11/convert-sorted-list-to-balanced-binary.html

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

BinaryTree* sortedListToBST(ListNode *& list, int start, int end) {
  if (start > end) return NULL;
  // same as (start+end)/2, avoids overflow
  int mid = start + (end - start) / 2;
  BinaryTree *leftChild = sortedListToBST(list, start, mid-1);
  BinaryTree *parent = new BinaryTree(list->data);
  parent->left = leftChild;
  list = list->next;
  parent->right = sortedListToBST(list, mid+1, end);
  return parent;
}

BinaryTree* sortedListToBST(ListNode *head, int n) {
  return sortedListToBST(head, 0, n-1);
}

Ответ 2

Вы не можете сделать лучше, чем линейное время, так как вам нужно, по крайней мере, прочитать все элементы списка, чтобы вы могли также скопировать список в массив (линейное время), а затем эффективно построить дерево в обычным способом, т.е. если у вас есть список [9,12,18,23,24,51,84], то вы должны начать с создания корня 23, с детьми 12 и 51, затем 9 и 18 станут детьми 12, а 24 и 84 становятся детьми 51. В целом, если вы сделаете это правильно, должно быть O (n).

Фактический алгоритм, для которого он стоит, - это "взять средний элемент списка как корень и рекурсивно построить BST для суб-списков слева и справа от среднего элемента и прикрепить их под корнем".

Ответ 3

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

Обратите внимание, что первая и третья записи становятся дочерними элементами второго, тогда четвертый node имеет детей второй и шестой (у которых есть дети пятый и седьмой) и т.д.

в коде psuedo

read three elements, make a node from them, mark as level 1, push on stack
loop
    read three elemeents and make a node of them
    mark as level 1
    push on stack
    loop while top two enties on stack have same level (n)
         make node of top two entries, mark as level n + 1, push on stack
while elements remain in list

(с небольшой настройкой, когда осталось меньше трех элементов или несимметричное дерево в любой точке)

EDIT:

В любой точке в стеке находится левая node высоты N. Следующий шаг - прочитать один элемент, затем прочитать и построить еще один node высоты N в стеке. Чтобы построить node высоты N, сделайте и нажмите a node с высотой N -1 в стеке, затем прочитайте элемент, сделайте еще один node высоты N-1 в стеке, который является рекурсивным звоните.

Собственно, это означает, что алгоритм (даже модифицированный) не будет создавать сбалансированное дерево. Если есть 2N + 1 узлов, он будет генерировать дерево с 2N-1 значениями слева, а 1 - справа.

Итак, я думаю, что ответ @sgolodetz лучше, если только я не могу думать о способе балансировки дерева по мере его создания.

Ответ 4

Трюк!

Лучший способ - использовать STL и преимущество в том, что отсортированный ассоциативный контейнер ADT, из которого набор является реализацией, требует, чтобы вставка отсортированных диапазонов амортизировала линейное время. Любой проходной набор основных структур данных для любого языка должен предлагать аналогичную гарантию. Для реального ответа см. Довольно умные решения, которые другие предоставили.


Что это? Я должен предложить что-то полезное?

Hum...

Как насчет этого?

Наименьшее возможное значащее дерево в сбалансированном двоичном дереве - это 3 узла. Родитель и двое детей. Самый первый экземпляр такого дерева - это первые три элемента. Ребенок-родитель-ребенок. Теперь представьте себе, что это единственный node. Ладно, хорошо, у нас больше нет дерева. Но мы знаем, что форма, которую мы хотим, - ребенок-родитель-ребенок.
На мгновение, с нашими мыслями, мы хотим сохранить указатель на родителя в этом первоначальном триумвирате. Но это односвязано!
Мы хотим иметь четыре указателя, которые я назову A, B, C и D. Итак, мы перемещаем A в 1, устанавливаем B равным A и продвигаем его. Установите C равным B и продвиньте его два. node под B уже указывает на право будущего ребенка. Мы строим наше начальное дерево. Мы оставляем B у родителя Tree one. C сидит в node, который будет иметь два наших минимальных дерева в качестве детей. Установите A равным C и продвиньте его. Установите D, равный A, и продвиньте его. Теперь мы можем построить наше следующее минимальное дерево. D указывает на корень этого дерева, B указывает на корень другого, а C указывает на... новый корень, из которого мы будем вставлять наши два минимальных дерева.

Как насчет некоторых изображений?

[A][B][-][C]  

Наш образ минимального дерева как node...

[B = Tree][C][A][D][-]

И затем

[Tree A][C][Tree B]

За исключением проблемы. node два после D - наш следующий корень.

[B = Tree A][C][A][D][-][Roooooot?!]  

Нам было бы намного легче, если бы мы могли просто поддерживать указатель на него, а не на него. C. Оказывается, поскольку мы знаем, что это будет указывать на C, мы можем продолжить и начать строить node в двоичном дереве, которое будет удерживать его, и как часть этого, мы можем ввести C в него как левое - node. Как мы можем сделать это элегантно?

Установите указатель node под C на node под B.
Он обманывает во всех смыслах слова, но, используя этот трюк, мы освобождаем B.
В качестве альтернативы вы можете быть нормальными и фактически начать строить структуру node. В конце концов, вы действительно не можете повторно использовать узлы из SLL, это, вероятно, структуры POD.

Итак, теперь...

[TreeA]<-[C][A][D][-][B]  
[TreeA]<-[C]->[TreeB][B] 

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

[TreeC]<-[B][A][D][-][C]  

Мы можем дополнительно обобщить наши трюки.

[TreeC]<-[B][TreeD]<-[C][-]<-[D][-][A]    
[TreeC]<-[B][TreeD]<-[C]->[TreeE][A]  
[TreeC]<-[B]->[TreeF][A]  
[TreeG]<-[A][B][C][-][D]
[TreeG]<-[A][-]<-[C][-][D]  
[TreeG]<-[A][TreeH]<-[D][B][C][-]  
[TreeG]<-[A][TreeH]<-[D][-]<-[C][-][B]  
[TreeG]<-[A][TreeJ]<-[B][-]<-[C][-][D]  
[TreeG]<-[A][TreeJ]<-[B][TreeK]<-[D][-]<-[C][-]      
[TreeG]<-[A][TreeJ]<-[B][TreeK]<-[D][-]<-[C][-]  

Нам не хватает критического шага!

[TreeG]<-[A]->([TreeJ]<-[B]->([TreeK]<-[D][-]<-[C][-]))  

Становится:

[TreeG]<-[A]->[TreeL->([TreeK]<-[D][-]<-[C][-])][B]    
[TreeG]<-[A]->[TreeL->([TreeK]<-[D]->[TreeM])][B]  
[TreeG]<-[A]->[TreeL->[TreeN]][B]  
[TreeG]<-[A]->[TreeO][B]  
[TreeP]<-[B]  

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

Фокус в основном заключается в том, что каждый раз, когда мы достигаем следующей середины, которую мы знаем, является родителем, мы знаем, что его левое поддерево уже закончено. Другой трюк заключается в том, что мы закончили с node, когда у него есть двое детей и что-то указывающее на него, даже если все поддеревья еще не закончены. Используя это, мы можем получить то, что я уверен, это линейное временное решение, так как каждый элемент касается всего 4 раза. Проблема в том, что это зависит от предоставления списка, который сформирует по-настоящему сбалансированное двоичное дерево поиска. Другими словами, существуют некоторые скрытые ограничения, которые могут сделать это решение слишком сложным для применения или невозможным. Например, если у вас есть нечетное число элементов или много нематериальных значений, это начинает создавать довольно глупое дерево.

Вопросы:

  • Оставить элемент уникальным.
  • Вставить фиктивный элемент в конец, если число узлов нечетно.
  • Сядьте с тоской для более наивной реализации.
  • Используйте deque, чтобы сохранить корни завершенных поддеревьев и середины, вместо того, чтобы обманывать мой второй трюк.

Ответ 5

Это реализация python:

def sll_to_bbst(sll, start, end):
    """Build a balanced binary search tree from sorted linked list.

    This assumes that you have a class BinarySearchTree, with properties
    'l_child' and 'r_child'.

    Params:
        sll: sorted linked list, any data structure with 'popleft()' method,
            which removes and returns the leftmost element of the list. The
            easiest thing to do is to use 'collections.deque' for the sorted
            list.
        start: int, start index, on initial call set to 0
        end: int, on initial call should be set to len(sll)

    Returns:
        A balanced instance of BinarySearchTree

    This is a python implementation of solution found here: 
    http://leetcode.com/2010/11/convert-sorted-list-to-balanced-binary.html

    """

    if start >= end:
        return None

    middle = (start + end) // 2
    l_child = sll_to_bbst(sll, start, middle)
    root = BinarySearchTree(sll.popleft())
    root.l_child = l_child
    root.r_child = sll_to_bbst(sll, middle+1, end)

    return root

Ответ 6

Вместо отсортированного связанного списка я спросил по отсортированному массиву (неважно, хотя логически, но да, время выполнения варьируется), чтобы создать BST минимальной высоты, следующий код, который я мог бы выйти:

typedef struct Node{
     struct Node *left;
     int info;
     struct Node  *right;
}Node_t;

Node_t* Bin(int low, int high) {

     Node_t* node = NULL;
     int mid = 0;

     if(low <= high) {
         mid = (low+high)/2;
         node = CreateNode(a[mid]);
         printf("DEBUG: creating node for %d\n", a[mid]);

        if(node->left == NULL) {
            node->left = Bin(low, mid-1);
        }

        if(node->right == NULL) {
            node->right = Bin(mid+1, high);
        }

        return node;
    }//if(low <=high)
    else {
        return NULL;
    }
}//Bin(low,high)


Node_t* CreateNode(int info) {

    Node_t* node = malloc(sizeof(Node_t));
    memset(node, 0, sizeof(Node_t));
    node->info = info;
    node->left = NULL;
    node->right = NULL;

    return node;

}//CreateNode(info)

// call function for an array example: 6 7 8 9 10 11 12, it gets you desired 
// result

 Bin(0,6); 

HTH Кто-то..

Ответ 7

Это псевдорекурсивный алгоритм, который я предложу.


createTree(treenode *root, linknode *start, linknode *end)
{
   if(start == end or start = end->next)
   {
      return; 
   } 
   ptrsingle=start;
   ptrdouble=start;
   while(ptrdouble != end and ptrdouble->next !=end)
   {
    ptrsignle=ptrsingle->next;
    ptrdouble=ptrdouble->next->next;
   }
   //ptrsignle will now be at the middle element. 
   treenode cur_node=Allocatememory;
   cur_node->data = ptrsingle->data;
   if(root = null)
       {
           root = cur_node; 
       }
   else
      {
         if(cur_node->data (less than) root->data)
          root->left=cur_node
         else
           root->right=cur_node
      }
   createTree(cur_node, start, ptrSingle);
   createTree(cur_node, ptrSingle, End); 
}

Root = null; Инициальный вызов будет createtree (Root, list, null);

Мы делаем рекурсивное построение дерева, но без использования промежуточного массива. Чтобы добраться до среднего элемента каждый раз, когда мы продвигаем два указателя, один за другим, другие - по двум элементам. К тому времени, когда второй указатель находится в конце, первый указатель будет посередине.

Время работы будет o (nlogn). Дополнительным пространством будет o (logn). Неэффективное решение для реальной ситуации, когда вы можете иметь дерево R-B, которое гарантирует вложение nlogn. Но достаточно для интервью.

Ответ 8

Подобно @Stuart Golodetz и @Jake Kurzer, важно то, что список уже отсортирован.

В ответе @Stuart массив, который он представил, представляет собой структуру данных резервного копирования для BST. Например, для операции поиска просто нужно выполнить вычисления массива индекса, чтобы пересечь дерево. Выращивание массива и удаление элементов было бы более сложной частью, поэтому я предпочел бы векторную или другую структуру данных с постоянным временем поиска.

@Ответ Jake также использует этот факт, но, к сожалению, вам нужно пройти по списку, чтобы каждый раз находить операцию get (index). Но не требует дополнительного использования памяти.

Если специально не было указано интервьюером, что они хотят представления структуры объектов в дереве, я бы использовал ответ @Stuart.

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

Ответ 10

Немного улучшена реализация из @1337c0d3r в моем блоге.

// create a balanced BST using @len elements starting from @head & move @head forward by @len
TreeNode *sortedListToBSTHelper(ListNode *&head, int len) {
    if (0 == len)   return NULL;

    auto left = sortedListToBSTHelper(head, len / 2);
    auto root = new TreeNode(head->val);
    root->left = left;
    head = head->next;
    root->right = sortedListToBSTHelper(head, (len - 1) / 2);
    return root;
}

TreeNode *sortedListToBST(ListNode *head) {
    int n = length(head);
    return sortedListToBSTHelper(head, n);
}

Ответ 11

Если вы знаете, сколько узлов находится в связанном списке, вы можете сделать это следующим образом:

// Gives path to subtree being built.  If branch[N] is false, branch
// less from the node at depth N, if true branch greater.
bool branch[max depth];

// If rem[N] is true, then for the current subtree at depth N, it's
// greater subtree has one more node than it less subtree.
bool rem[max depth];

// Depth of root node of current subtree.
unsigned depth = 0;

// Number of nodes in current subtree.
unsigned num_sub = Number of nodes in linked list;

// The algorithm relies on a stack of nodes whose less subtree has
// been built, but whose right subtree has not yet been built.  The
// stack is implemented as linked list.  The nodes are linked
// together by having the "greater" handle of a node set to the
// next node in the list.  "less_parent" is the handle of the first
// node in the list.
Node *less_parent = nullptr;

// h is root of current subtree, child is one of its children.
Node *h, *child;

Node *p = head of the sorted linked list of nodes;

LOOP // loop unconditionally

    LOOP WHILE (num_sub > 2)
        // Subtract one for root of subtree.
        num_sub = num_sub - 1;

        rem[depth] = !!(num_sub & 1); // true if num_sub is an odd number
        branch[depth] = false;
        depth = depth + 1;
        num_sub = num_sub / 2;
    END LOOP

    IF (num_sub == 2)
        // Build a subtree with two nodes, slanting to greater.
        // I arbitrarily chose to always have the extra node in the
        // greater subtree when there is an odd number of nodes to
        // split between the two subtrees.

        h = p;
        p = the node after p in the linked list;
        child = p;
        p = the node after p in the linked list;
        make h and p into a two-element AVL tree;
    ELSE  // num_sub == 1

        // Build a subtree with one node.

        h = p;
        p = the next node in the linked list;
        make h into a leaf node;
    END IF

    LOOP WHILE (depth > 0)
        depth = depth - 1;
        IF (not branch[depth])
            // We've completed a less subtree, exit while loop.
            EXIT LOOP;
        END IF

        // We've completed a greater subtree, so attach it to
        // its parent (that is less than it).  We pop the parent
        // off the stack of less parents.
        child = h;
        h = less_parent;
        less_parent = h->greater_child;
        h->greater_child = child;
        num_sub = 2 * (num_sub - rem[depth]) + rem[depth] + 1;
        IF (num_sub & (num_sub - 1))
          // num_sub is not a power of 2
          h->balance_factor = 0;
        ELSE
          // num_sub is a power of 2
          h->balance_factor = 1;
        END IF
    END LOOP

    IF (num_sub == number of node in original linked list)
        // We've completed the full tree, exit outer unconditional loop
        EXIT LOOP;
    END IF

    // The subtree we've completed is the less subtree of the
    // next node in the sequence.

    child = h;
    h = p;
    p = the next node in the linked list;
    h->less_child = child;

    // Put h onto the stack of less parents.
    h->greater_child = less_parent;
    less_parent = h;

    // Proceed to creating greater than subtree of h.
    branch[depth] = true;
    num_sub = num_sub + rem[depth];
    depth = depth + 1;

END LOOP

// h now points to the root of the completed AVL tree.

Для кодирования этого в С++ см. функцию члена сборки (в настоящее время в строке 361) в https://github.com/wkaras/C-plus-plus-intrusive-container-templates/blob/master/avl_tree.h. Это на самом деле более общий, шаблон с использованием любого форвардного итератора, а не специально связанного списка.