【问题标题】:Create Balanced Binary Search Tree from Sorted linked list从排序链表创建平衡二叉搜索树
【发布时间】:2011-05-13 05:02:54
【问题描述】:

从已排序的单链表中创建平衡二叉搜索树的最佳方法是什么?

【问题讨论】:

  • 几个问题:你知道sll中有多少元素吗?

标签: algorithm tree linked-list


【解决方案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);
}

【讨论】:

  • 我非常喜欢这种自下而上的方法,而且确实是 O(N)。
  • 为什么要做list = list->next; ?
  • 博文说找不到页面。请您帮忙更新链接吗?
【解决方案2】:

你不能比线性时间做得更好,因为你至少要读取列表的所有元素,所以你不妨将列表复制到一个数组中(线性时间),然后在通常的方式,即如果你有列表 [9,12,18,23,24,51,84],那么你首先将 23 设为根,孩子 12 和 51,然后 9 和 18 成为 12 的孩子, 24 和 84 成为 51 的孩子。总的来说,如果你做对了,应该是 O(n)。

真正的算法,就其价值而言,是“以列表的中间元素为根,并递归地为中间元素左右的子列表构建 BST,并将它们附加到根之下” .

【讨论】:

  • 你应该可以在遍历链表时构建树,而不必先将它复制到数组中...
  • 我们两种算法之间的差异非常有趣(至少对我而言)。你自上而下建树,我自下而上。
  • 嗯,我也觉得这很有趣 :) 只是看看——我跑去做一些工作......
  • 我们也都使用堆栈。你的隐含在调用堆栈中,我的是显式的
  • 我认为它也是这样做的。唯一不同的是,我没有将整个链表转换为 int,而是创建了一个树节点数组。然后使用递归将树节点链接在一起,这样我们就不会浪费内存了。
【解决方案3】:

Best 不仅仅与 asynmptopic 运行时间有关。排序后的链表有直接创建二叉树所需的所有信息,我想这大概就是他们要找的吧

注意第一个和第三个条目成为第二个的孩子,然后第四个节点有第二个和第六个的孩子(它有第五个和第七个孩子)等等......

在伪代码中

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

(当剩下的元素少于三个或任何时候不平衡的树时进行一些调整)

编辑:

在任何时候,堆栈上都有一个高度为 N 的左节点。下一步是读取一个元素,然后读取并在堆栈上构造另一个高度为 N 的节点。要构造一个高度为 N 的节点,在堆栈上创建并压入一个高度为 N -1 的节点,然后读取一个元素,在堆栈上创建另一个高度为 N-1 的节点——这是一个递归调用。

实际上,这意味着算法(即使经过修改)不会产生平衡树。如果有 2N+1 个节点,它将产生一棵树,左边有 2N-1 个值,右边有 1 个值。

所以我认为@sgolodetz 的答案更好,除非我能想出一种在树构建时重新平衡树的方法。

【讨论】:

  • 我从伪代码中不清楚您的算法在做什么——当您说“标记为 1 级”时,您是否仅将三个元素的中间元素标记为 1 级,还是您要标记所有这些?你能澄清一下吗?
  • 我将由三个节点组成的节点标记为级别 1。不清楚我是在用它们制作节点,所以我编辑了我的答案。
  • 前两个条目的节点制作时的细节是什么?
  • @TimothChen,啊,你发现了一个缺陷。您需要另一个值用作该节点的值。它并没有打破整个想法,但我需要纠正那里的内容。我认为您只是将另一个值推送到两个节点之间的堆栈上。然后弹出节点,值和节点并制作更高级别的节点。今晚太晚了,无法确定它的正确性(这里是晚上 10 点)
  • @Paul: 和 Timothy 一样的问题 :) 所以你有两个包含 [9,12,18] 和 [23,24,51] 的节点,你将它们组合起来...... ?
【解决方案4】:

技巧题!

最好的方法是使用 STL,并利用排序关联容器 ADT(其中 set 是一种实现)要求插入排序范围具有摊销线性时间的事实。任何语言的任何可通过的核心数据结构集都应该提供类似的保证。要获得真正的答案,请参阅其他人提供的非常聪明的解决方案。


那是什么?我应该提供一些有用的东西吗?

哼...

这个怎么样?

平衡二叉树中最小的可能有意义的树是 3 个节点。 一个家长,两个孩子。这种树的第一个实例是前三个元素。子-父-子。现在让我们把它想象成一个单一的节点。好吧,好吧,我们不再有一棵树了。但是我们知道我们想要的形状是 Child-parent-Child。
暂时完成了我们的想象,我们希望在最初的三驾马车中保留一个指向父级的指针。但它是单链接的!
我们需要四个指针,我将它们称为 A、B、C 和 D。因此,我们将 A 移动到 1,将 B 设置为等于 A,然后将其推进一。将 C 设置为 B,然后将其推进两个。 B 下的节点已经指向它的右子节点。我们建立我们的初始树。我们将 B 留在树一的父级。 C 位于将我们的两棵最小树作为子节点的节点上。将 A 设置为等于 C,并将其推进一。将 D 设置为 A,并将其提前一。我们现在可以构建我们的下一个最小树。 D 指向那棵树的根,B 指向另一棵树的根,C 指向...新的根,我们将从中挂起我们的两棵最小的树。

一些图片怎么样?

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

以我们的最小树图像作为节点...

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

然后

[Tree A][C][Tree B]

除非我们有问题。 D 之后的节点二是我们的下一个根。

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

如果我们可以简单地维护一个指向它而不是指向它和 C 的指针,对我们来说会容易得多。事实证明,既然我们知道它将指向 C,我们可以继续并开始在将保存它的二叉树,作为其中的一部分,我们可以将 C 作为左节点输入其中。我们怎样才能优雅地做到这一点?

将C下节点的指针设置为B下节点。
从各个意义上来说,这都是作弊,但通过使用这个技巧,我们释放了 B。
或者,您可以保持理智,并实际开始构建节点结构。毕竟,你真的不能重用 SLL 中的节点,它们可能是 POD 结构。

那么现在...

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

然后...等一下。如果我们只是让自己将其视为单个节点而不是树,我们可以使用相同的技巧来释放 C。因为毕竟它真的只是一个单一的节点。

[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]  

显然,该算法可以进行大量清理,但我认为通过迭代设计算法来演示如何在进行过程中进行优化会很有趣。我认为这种流程是一个好的雇主最应该寻找的。​​p>

基本上,诀窍在于,每次我们到达下一个中点时,我们知道它是一个准父节点,我们知道它的左子树已经完成。另一个技巧是,一旦节点有两个子节点和指向它的东西,我们就完成了节点,即使所有子树都没有完成。使用它,我们可以得到我很确定的线性时间解决方案,因为每个元素最多只被触摸 4 次。问题在于,这依赖于给定一个列表,该列表将形成一个真正平衡的二叉搜索树。换句话说,有一些隐藏的约束可能使这个解决方案更难应用,或者不可能。例如,如果您有奇数个元素,或者如果有很多非唯一值,这将开始生成一棵相当愚蠢的树。

注意事项:

  • 渲染元素唯一。
  • 如果节点数为奇数,则在末尾插入一个虚拟元素。
  • 渴望实现更天真的实现。
  • 使用双端队列来保留已完成子树的根和中点,而不是使用我的第二个技巧。

【讨论】:

  • 我想我错过了什么,我很着急,没有做正式的证明。有人看到错误吗?我认为破解 RB-tree 实现几乎更容易。
  • 我理解向雇主清楚地展示您的想法。然而,当我在面试时,从听电话问题到在屏幕上输入有效的解决方案代码,我只有 25 分钟的时间。所以我觉得这么多的话是不能按时说完的。
【解决方案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); }

      根=空; 初始调用将是 createtree(Root, list, null);

      我们正在递归构建树,但没有使用中间数组。 每次我们推进两个指针时,要到达中间元素,一个一个元素,另一个两个元素。当第二个指针位于末尾时,第一个指针将位于中间。

      运行时间为 o(nlogn)。额外的空间将是 o(logn)。对于可以拥有保证 nlogn 插入的 R-B 树的实际情况,这不是一个有效的解决方案。但足够面试了。

      【讨论】:

        【解决方案8】:

        类似于@Stuart Golodetz 和@Jake Kurzer,重要的是列表已经排序。

        在@Stuart 的回答中,他提供的数组是 BST 的支持数据结构。例如,查找操作只需要执行索引数组计算即可遍历树。增加数组和删除元素会比较棘手,所以我更喜欢向量或其他恒定时间查找数据结构。

        @Jake 的回答也使用了这个事实,但不幸的是需要您遍历列表以查找每次执行 get(index) 操作。但不需要额外的内存使用。

        除非面试官特别提到他们想要树的对象结构表示,否则我会使用@Stuart 的答案。

        在这样的问题中,您将获得额外的分数来讨论权衡和您拥有的所有选项。

        【讨论】:

          【解决方案9】:

          希望这篇文章的详细解释对您有所帮助: http://preparefortechinterview.blogspot.com/2013/10/planting-trees_1.html

          【讨论】:

            【解决方案10】:

            从@1337c0d3r in my blog 略微改进的实现。

            // 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's 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.
              

              有关 C++ 中 this 的编码,请参阅 https://github.com/wkaras/C-plus-plus-intrusive-container-templates/blob/master/avl_tree.h 中的构建成员函数(当前位于第 361 行)。它实际上更通用,模板使用任何前向迭代器,而不是专门的链表。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2023-03-17
                • 2013-12-18
                • 1970-01-01
                • 2012-05-13
                • 1970-01-01
                • 2020-04-07
                相关资源
                最近更新 更多