从已排序的链接列表创建平衡二进制搜索树

时间:2010-11-20 21:07:31

标签: algorithm tree linked-list

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

11 个答案:

答案 0 :(得分:25)

如何自下而上创建节点?

该解决方案的时间复杂度为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);
}

答案 1 :(得分:3)

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

实际的算法,就是它的价值,是“将列表的中间元素作为根,并递归地为中间元素的左侧和右侧的子列表构建BST,并将它们附加到根目录下”

答案 2 :(得分:2)

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的答案更好,除非我能想到一种重新平衡树的方法。

答案 3 :(得分:2)

欺骗性问题!

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


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

坎...

这个怎么样?

平衡二叉树中最小的可能有意义的树是3个节点。 父母和两个孩子。这种树的第一个实例是前三个元素。儿童亲子。现在让我们将其想象为单个节点。好吧,我们不再有树。但我们知道我们想要的形状是Child-parent-Child 用我们的想象做了一会儿,我们想在最初的三位一体中保持指向父母的指针。但它是单独联系的!
我们想要有四个指针,我将其称为A,B,C和D.因此,我们将A移动到1,将B设置为A并将其提前一个。设置C等于B,并将其前进两次。 B下的节点已经指向其右边的孩子。我们构建了我们的初始树。我们把B留在Tree one的父母那里。 C坐在节点上,这两个小树将成为孩子。设置A等于C,并将其前进一个。设置D等于A,并将其前进一个。我们现在可以构建我们的下一个最小树。 D指向那棵树的根,B指向另一棵树的根,C指向......我们将悬挂两棵小树的新根。

有些图片怎么样?

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

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

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

然后

[Tree A][C][Tree B]

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

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

显然,算法可以大大清理,但我认为通过迭代设计算法来演示如何优化算法会很有趣。我认为这种过程是一个好雇主应该寻找的东西。

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

<强>考虑:

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

答案 4 :(得分:2)

这是一个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

答案 5 :(得分:1)

而不是排序的链表我被问到排序的数组(虽然逻辑上没关系,但是运行时间不同)以创建一个最小高度的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有人......

答案 6 :(得分:0)

这是我建议的伪递归算法。


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)。对于您可以拥有保证nlogn插入的R-B树的真实情况,这不是一个有效的解决方案。但对于采访来说还不错。

答案 7 :(得分:0)

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

在@Stuart的回答中,他提出的数组是BST的后备数据结构。例如,find操作只需要执行索引数组计算来遍历树。增长数组和删除元素将是更棘手的部分,所以我更喜欢矢量或其他常量时间查找数据结构。

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

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

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

答案 8 :(得分:0)

答案 9 :(得分:0)

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

答案 10 :(得分:0)

如果你知道链表中有多少个节点,你可以这样做:

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