【问题标题】:Merging 2 Binary Search Trees合并 2 个二叉搜索树
【发布时间】:2011-11-24 07:53:12
【问题描述】:

如何合并 2 个二叉搜索树,以使生成的树包含两棵树的所有元素并保持 BST 属性。

我看到了提供的解决方案 How to merge two BST's efficiently?

但是,该解决方案涉及转换为双链表。我想知道是否有一种更优雅的方法可以在没有转换的情况下就地完成。我想出了以下伪代码。它适用于所有情况吗?我也遇到了第三种情况。

node* merge(node* head1, node* head2) {
        if (!head1)
            return head2;
        if (!head2)
            return head1;

        // Case 1.
        if (head1->info > head2->info) {
            node* temp = head2->right;
            head2->right = NULL;
            head1->left = merge(head1->left, head2);
            head1 = merge(head1, temp);
            return head1;
        } else if (head1->info < head2->info)  { // Case 2
            // Similar to case 1.
        } else { // Case 3
            // ...
        }
}

【问题讨论】:

  • 树不像LinkedLists那么简单,所以必须遍历它们以检索每个项目,存储在更“线性”的数据结构中,然后添加到另一棵树中进行合并。跨度>
  • 优雅是相当主观的......我发现扁平化和重建方法非常优雅! :)
  • BST 是否提供自平衡功能?

标签: algorithm binary-tree


【解决方案1】:

BST 是有序或排序的二叉树。我的算法很简单:

  • 遍历两棵树
  • 比较值
  • 将两者中较小的一个插入新的 BST。

python遍历代码如下:

def traverse_binary_tree(node, callback):
    if node is None:
        return
    traverse_binary_tree(node.leftChild, callback)
    callback(node.value)
    traverse_binary_tree(node.rightChild, callback)

遍历 BST 和构建新的合并 BST 的成本将保持 O(n)

【讨论】:

  • 除非它是自平衡树(AVL、Red-Black 等),否则新树不会非常不平衡(本质上是链表)吗?
  • 剩下的两棵树呢?您需要删除它们。请注意,您的伪代码正在访问一棵树,访问两棵树进行合并操作会大不相同。
  • @OmriBarel 在这种情况下也是 O(nlogn)
  • 嗯..我也在想同样的事情,也想回答,但后来看到你的;)
  • 您需要用多个变量来衡量这一点。例如。 n 表示第一棵树的大小,m 表示第二棵树的大小。迄今为止最好的算法接缝运行在O(min(m+n, m*logn, n*logm))
【解决方案2】:

我们可以将树合并到适当位置的最佳方法是:

For each node n in first BST {
    Go down the 2nd tree and find the appropriate place to insert n
    Insert n there
}

for循环中的每次迭代都是O(log n),因为我们处理的是树,而for循环将迭代n次,所以我们总共有O(n log n)。

【讨论】:

  • 不是最好的方法!但这确实是一种方法。
【解决方案3】:

假设我们有两棵树 A 和 B,我们将树 A 的根插入到树 B 中,并使用旋转移动插入的根成为树 B 的新根。接下来我们递归合并树 A 和 B 的左右子树。该算法考虑了两种树的结构,但插入仍然取决于目标树的平衡程度。可以利用这个思路将O(n+m)时间和O(1)空间的两棵树合并。


下面的实现是由于Dzmitry Huba:

// Converts tree to sorted singly linked list and appends it
// to the head of the existing list and returns new head.
// Left pointers are used as next pointer to form singly
// linked list thus basically forming degenerate tree of
// single left oriented branch. Head of the list points
// to the node with greatest element.
static TreeNode<T> ToSortedList<T>(TreeNode<T> tree, TreeNode<T> head)
{
    if (tree == null)
        // Nothing to convert and append
        return head;
    // Do conversion using in order traversal
    // Convert first left sub-tree and append it to
    // existing list
    head = ToSortedList(tree.Left, head);
    // Append root to the list and use it as new head
    tree.Left = head;
    // Convert right sub-tree and append it to list
    // already containing left sub-tree and root
    return ToSortedList(tree.Right, tree);
}

// Merges two sorted singly linked lists into one and
// calculates the size of merged list. Merged list uses
// right pointers to form singly linked list thus forming
// degenerate tree of single right oriented branch.
// Head points to the node with smallest element.
static TreeNode<T> MergeAsSortedLists<T>(TreeNode<T> left, TreeNode<T> right, IComparer<T> comparer, out int size)
{
    TreeNode<T> head = null;
    size = 0;
    // See merge phase of merge sort for linked lists
    // with the only difference in that this implementations
    // reverts the list during merge
    while (left != null || right != null)
    {
        TreeNode<T> next;
        if (left == null)
            next = DetachAndAdvance(ref right);
        else if (right == null)
            next = DetachAndAdvance(ref left);
        else
            next = comparer.Compare(left.Value, right.Value) > 0
                        ? DetachAndAdvance(ref left)
                        : DetachAndAdvance(ref right);
        next.Right = head;
        head = next;
        size++;
    }
    return head;
}



static TreeNode<T> DetachAndAdvance<T>(ref TreeNode<T> node)
{
    var tmp = node;
    node = node.Left;
    tmp.Left = null;
    return tmp;
}

// Converts singly linked list into binary search tree
// advancing list head to next unused list node and
// returning created tree root
static TreeNode<T> ToBinarySearchTree<T>(ref TreeNode<T> head, int size)
{
    if (size == 0)
        // Zero sized list converts to null
        return null;

    TreeNode<T> root;
    if (size == 1)
    {
        // Unit sized list converts to a node with
        // left and right pointers set to null
        root = head;
        // Advance head to next node in list
        head = head.Right;
        // Left pointers were so only right needs to
        // be nullified
        root.Right = null;
        return root;
    }

    var leftSize = size / 2;
    var rightSize = size - leftSize - 1;
    // Create left substree out of half of list nodes
    var leftRoot = ToBinarySearchTree(ref head, leftSize);
    // List head now points to the root of the subtree
    // being created
    root = head;
    // Advance list head and the rest of the list will
    // be used to create right subtree
    head = head.Right;
    // Link left subtree to the root
    root.Left = leftRoot;
    // Create right subtree and link it to the root
    root.Right = ToBinarySearchTree(ref head, rightSize);
    return root;
}

public static TreeNode<T> Merge<T>(TreeNode<T> left, TreeNode<T> right, IComparer<T> comparer)
{
    Contract.Requires(comparer != null);

    if (left == null || right == null)
        return left ?? right;
    // Convert both trees to sorted lists using original tree nodes
    var leftList = ToSortedList(left, null);
    var rightList = ToSortedList(right, null);
    int size;
    // Merge sorted lists and calculate merged list size
    var list = MergeAsSortedLists(leftList, rightList, comparer, out size);
    // Convert sorted list into optimal binary search tree
    return ToBinarySearchTree(ref list, size);
}

【讨论】:

  • 你写的idea不一样,给出的代码也不一样。用于创建树的代码 y 将它们都转换为排序的 LL,然后合并,然后从中创建树
【解决方案4】:

两个二叉搜索树 (BST) 在递归遍历期间不能直接合并。 假设我们应该合并图中所示的Tree 1和Tree 2。

递归应该将合并减少到更简单的情况。我们不能减少 只合并到各自的左子树 L1 和 L2,因为 L2 可以包含 大于 10 的数字,所以我们需要包括右 子树 R1 进入进程。但是我们包括更大的数字 大于 10 并且可能大于 20,所以我们需要包括 右子树 R2 也是如此。类似的推理表明 我们不能通过包含树 1 和树 2 的子树来简化合并 同时。

减少的唯一可能性是仅在相应的树内进行简化。 所以,我们可以改造 带有排序节点的树到它们的右棘:

现在,我们可以轻松地将两个脊椎合并为一个脊椎。这 脊柱实际上是一个 BST,所以我们可以在这里停下来。然而,这个 BST 完全不平衡,因此我们将其转换为平衡的 BST。

复杂度是:

Spine 1: time = O(n1),    space = O(1) 
Spine 2: time = O(n2),    space = O(1) 
Merge:   time = O(n1+n2), space = O(1) 
Balance: time = O(n1+n2), space = O(1) 
Total:   time = O(n1+n2), space = O(1)

完整的运行代码在http://ideone.com/RGBFQ。以下是基本部分。顶层代码如下:

Node* merge(Node* n1, Node* n2) {
    Node *prev, *head1, *head2;   
    prev = head1 = 0; spine(n1, prev, head1); 
    prev = head2 = 0; spine(n2, prev, head2);
    return balance(mergeSpines(head1, head2));
}

辅助功能是转化为spines:

void spine(Node *p, Node *& prev, Node *& head) {   
    if (!p) return;   
    spine(p->left, prev, head);   
    if (prev) prev->right = p;   
    else head = p;  
    prev = p; 
    p->left = 0;  
    spine(p->right, prev, head); 
} 

脊柱合并:

void advance(Node*& last, Node*& n) {
    last->right = n; 
    last = n;
    n = n->right; 
}

Node* mergeSpines(Node* n1, Node* n2) {
    Node head;
    Node* last = &head;
    while (n1 || n2) {
        if (!n1) advance(last, n2);
        else if (!n2) advance(last, n1);
        else if (n1->info < n2->info) advance(last, n1);
        else if (n1->info > n2->info) advance(last, n2);
        else {
            advance(last, n1);
            printf("Duplicate key skipped %d \n", n2->info);
            n2 = n2->right;
        }
    }
    return head.right; 
}

平衡:

Node* balance(Node *& list, int start, int end) {
    if (start > end) return NULL;  
    int mid = start + (end - start) / 2;    
    Node *leftChild = balance(list, start, mid-1);   
    Node *parent = list;
    parent->left = leftChild;   
    list = list->right;   
    parent->right = balance(list, mid+1, end);   
    return parent; 
}   

Node* balance(Node *head) {
    int size = 0;
    for (Node* n = head; n; n = n->right) ++size;
    return balance(head, 0, size-1); 
} 

【讨论】:

  • 你的“脊椎”肯定等同于将树扁平化为链表吗?
  • 是的,spine相当于一个单链表,其中“右子指针”就是“下一个元素指针”。 “左子指针”为“空”。所以,它仍然是一棵只有一个分支的树,也就是排序列表。
  • 很公平。我只是指出来,因为问题建议想要一个“没有转换的地方”方法,而您的方法(我喜欢)涉及相同的转换。无论如何都要为细节+1。
  • 平衡为每个父节点创建一个新指针。这不是 O(n) 空间复杂度吗?
  • @tamir - 只有在构建原始树时才会创建指针。之后,指针只会四处移动。因此,空间复杂度为 O(1)。我注意到上面提到的我在 Ideone.com 上的完整代码消失了。我在link 上放了一份新副本。完整的代码有助于查看内存管理。
【解决方案5】:

以下算法来自Algorithms in C++

这个想法与 PengOne 发布的算法几乎相同。该算法进行就地合并,时间复杂度为O(n+m)。

link join(link a, link b) {
    if (b == 0) return a;
    if (a == 0) return b;
    insert(b, a->item);
    b->left = join(a->left, b->left);
    b->right = join(a->right, b->right);
    delete a;
    return b;
}

insert 只是在树的正确位置插入一个项目。

void insert(link &h, Item x) {
    if (h == 0) {
        h = new node(x);
        return;
    }
    if (x.key() < h->item.key()) {
        insert(h->left, x);
        rotateRight(h);
    }
    else {
        insert(h->right, x);
        rotateLeft(h);
    }
}

rotateRightrotateLeft 保持树的正确顺序。

void rotateRight(link &h) {
    link x = h->left;
    h->left = x->right;
    x->right = h;
    h = x;
}

void rotateLeft(link &h) {
    link x = h->right;
    h->right = x->left;
    x->left = h;
    h = x;
}

这里linknode *

【讨论】:

    【解决方案6】:

    This blog post 提供了 O(logn) 空间复杂度问题的解决方案。 (请注意,给定的方法不会修改输入树。)

    【讨论】:

      【解决方案7】:

      假设问题只是从两个 BST 中排序打印。那么更简单的方法是,

      1. 将 2 个 BST 的按顺序遍历存储在 2 个单独的数组中。
      2. 现在问题简化为合并\打印来自 2 个排序数组的元素,这是我们从第一步中得到的。这种合并可以在 m>n 时在 o(m) 中完成,或者在 m 时在 o(n) 中完成

      复杂度:o(m+n) 辅助空间:2 个数组的 o(m+n)

      【讨论】:

        【解决方案8】:

        MergeTwoBST_to_BalancedBST.java

        public class MergeTwoBST_to_BalancedBST {
        
        // arr1 and arr2 are the input arrays to be converted into a binary search
        // structure and then merged and then balanced.
        int[] arr1 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
        int[] arr2 = new int[] { 11, 12, 13, 14, 15, 16, 17, 18 };
        
        BSTNode root1;
        BSTNode root2;
        
        // vector object to hold the nodes from the merged unbalanced binary search
        // tree.
        Vector<BSTNode> vNodes = new Vector<BSTNode>();
        
        /**
         * Constructor to initialize the Binary Search Tree root nodes to start
         * processing. This constructor creates two trees from two given sorted
         * array inputs. root1 tree from arr1 and root2 tree from arr2.
         * 
         * Once we are done with creating the tree, we are traversing the tree in
         * inorder format, to verify whether nodes are inserted properly or not. An
         * inorder traversal should give us the nodes in a sorted order.
         */
        public MergeTwoBST_to_BalancedBST() {
            // passing 0 as the startIndex and arr1.length-1 as the endIndex.
            root1 = getBSTFromSortedArray(arr1, 0, arr1.length - 1);
            System.out.println("\nPrinting the first binary search tree");
            inorder(root1); // traverse the tree in inorder format to verify whether
                            // nodes are inserted correctly or not.
        
            // passing 0 as the startIndex and arr2.length-1 as the endIndex.
            root2 = getBSTFromSortedArray(arr2, 0, arr2.length - 1);
            System.out.println("\nPrinting the second binary search tree");
            inorder(root2); // same here - checking whether the nodes are inserted
                            // properly or not.
        }
        
        /**
         * Method to traverse the tree in inorder format. Where it traverses the
         * left child first, then root and then right child.
         * 
         * @param node
         */
        public void inorder(BSTNode node) {
            if (null != node) {
                inorder(node.getLeft());
                System.out.print(node.getData() + " ");
                inorder(node.getRight());
            }
        }
        
        /**
         * Method to traverse the tree in preorder format. Where it traverses the
         * root node first, then left child and then right child.
         * 
         * @param node
         */
        public void preorder(BSTNode node) {
            if (null != node) {
                System.out.print(node.getData() + " ");
                preorder(node.getLeft());
                preorder(node.getRight());
            }
        }
        
        /**
         * Creating a new Binary Search Tree object from a sorted array and
         * returning the root of the newly created node for further processing.
         * 
         * @param arr
         * @param startIndex
         * @param endIndex
         * @return
         */
        public BSTNode getBSTFromSortedArray(int[] arr, int startIndex, int endIndex) {
            if (startIndex > endIndex) {
                return null;
            }
        
            int middleIndex = startIndex + (endIndex - startIndex) / 2;
            BSTNode node = new BSTNode(arr[middleIndex]);
            node.setLeft(getBSTFromSortedArray(arr, startIndex, middleIndex - 1));
            node.setRight(getBSTFromSortedArray(arr, middleIndex + 1, endIndex));
            return node;
        }
        
        /**
         * This method involves two operation. First - it adds the nodes from root1
         * tree to root2 tree, and hence we get a merged root2 tree.Second - it
         * balances the merged root2 tree with the help of a vector object which can
         * contain objects only of BSTNode type.
         */
        public void mergeTwoBinarySearchTree() {
            // First operation - merging the trees. root1 with root2 merging should
            // give us a new root2 tree.
            addUtil(root1);
            System.out.println("\nAfter the root1 tree nodes are added to root2");
            System.out.println("Inorder Traversal of root2 nodes");
            inorder(root2); // inorder traversal of the root2 tree should display
                            // the nodes in a sorted order.
        
            System.out.println("\nPreorder traversal of root2 nodes");
            preorder(root2);
        
            // Second operation - this will take care of balancing the merged binary
            // search trees.
            balancedTheMergedBST();
        }
        
        /**
         * Here we are doing two operations. First operation involves, adding nodes
         * from root2 tree to the vector object. Second operation involves, creating
         * the Balanced binary search tree from the vector objects.
         */
        public void balancedTheMergedBST() {
            // First operation : adding nodes to the vector object
            addNodesToVector(root2, vNodes);
            int vSize = vNodes.size();
        
            // Second operation : getting a balanced binary search tree
            BSTNode node = getBalancedBSTFromVector(vNodes, 0, vSize - 1);
            System.out
                    .println("\n********************************************************");
            System.out.println("After balancing the merged trees");
            System.out.println("\nInorder Traversal of nodes");
            inorder(node); // traversing the tree in inoder process should give us
                            // the output in sorted order ascending
            System.out.println("\nPreorder traversal of root2 nodes");
            preorder(node);
        }
        
        /**
         * This method will provide us a Balanced Binary Search Tree. Elements of
         * the root2 tree has been added to the vector object. It is parsed
         * recursively to create a balanced tree.
         * 
         * @param vNodes
         * @param startIndex
         * @param endIndex
         * @return
         */
        public BSTNode getBalancedBSTFromVector(Vector<BSTNode> vNodes,
                int startIndex, int endIndex) {
            if (startIndex > endIndex) {
                return null;
            }
        
            int middleIndex = startIndex + (endIndex - startIndex) / 2;
            BSTNode node = vNodes.get(middleIndex);
            node.setLeft(getBalancedBSTFromVector(vNodes, startIndex,
                    middleIndex - 1));
            node.setRight(getBalancedBSTFromVector(vNodes, middleIndex + 1,
                    endIndex));
        
            return node;
        }
        
        /**
         * This method traverse the tree in inorder process and adds each node from
         * root2 to the vector object vNodes object only accepts objects of BSTNode
         * type.
         * 
         * @param node
         * @param vNodes
         */
        public void addNodesToVector(BSTNode node, Vector<BSTNode> vNodes) {
            if (null != node) {
                addNodesToVector(node.getLeft(), vNodes);
                // here we are adding the node in the vector object.
                vNodes.add(node);
                addNodesToVector(node.getRight(), vNodes);
            }
        }
        
        /**
         * This method traverse the root1 tree in inorder process and add the nodes
         * in the root2 tree based on their value
         * 
         * @param node
         */
        public void addUtil(BSTNode node) {
            if (null != node) {
                addUtil(node.getLeft());
                mergeToSecondTree(root2, node.getData());
                addUtil(node.getRight());
            }
        }
        
        /**
         * This method adds the nodes found from root1 tree as part it's inorder
         * traversal and add it to the second tree.
         * 
         * This method follows simple Binary Search Tree inserstion logic to insert
         * a node considering the tree already exists.
         * 
         * @param node
         * @param data
         * @return
         */
        public BSTNode mergeToSecondTree(BSTNode node, int data) {
        
            if (null == node) {
                node = new BSTNode(data);
            } else {
                if (data < node.getData()) {
                    node.setLeft(mergeToSecondTree(node.getLeft(), data));
                } else if (data > node.getData()) {
                    node.setRight(mergeToSecondTree(node.getRight(), data));
                }
            }
        
            return node;
        }
        
        /**
         * 
         * @param args
         */
        public static void main(String[] args) {
            MergeTwoBST_to_BalancedBST mergeTwoBST = new MergeTwoBST_to_BalancedBST();
            mergeTwoBST.mergeTwoBinarySearchTree();
          }
        }
        

        BSTNode.java:

        public class BSTNode {
        
        BSTNode left, right;
        int data;
        
        /* Default constructor */
        public BSTNode() {
            left = null;
            right = null;
            data = 0;
        }
        
        /* Constructor */
        public BSTNode(int data) {
            left = null;
            right = null;
            this.data = data;
        }
        
        public BSTNode getLeft() {
            return left;
        }
        
        public void setLeft(BSTNode left) {
            this.left = left;
        }
        
        public BSTNode getRight() {
            return right;
        }
        
        public void setRight(BSTNode right) {
            this.right = right;
        }
        
        public int getData() {
            return data;
        }
        
        public void setData(int data) {
            this.data = data;
          }
        }
        

        【讨论】:

          【解决方案9】:

          这可以分三步完成:

          1. 将 BST 转换为已排序的链表(这可以用 O(m+n) 时间就地完成)
          2. 将这两个已排序的链表合并为一个链表(这可以用 O(m+n) 时间就地完成)
          3. 将已排序的链表转换为平衡的 BST(这可以用 O(m+n) 时间就地完成)

          【讨论】:

            【解决方案10】:

            这就是我要做的。

            这个解是 O(n1+n2) 时间复杂度。

            步骤:

            1. 执行两棵树的中序遍历以获得排序的数组 --> 线性时间
            2. 合并两个数组 --> 再次线性时间
            3. 将合并后的数组转换为平衡二叉搜索树 -->再次线性时间

            这需要 O(n1+n2) 时间和空间。

            在实施过程中您可能会发现有用的链接:

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-02-07
              • 1970-01-01
              • 1970-01-01
              • 2023-03-08
              • 2017-10-01
              • 1970-01-01
              相关资源
              最近更新 更多