简体   繁体   English

合并 2 个二叉搜索树

[英]Merging 2 Binary Search Trees

How do you merge 2 Binary Search Trees in such a way that the resultant tree contains all the elements of both the trees and also maintains the BST property.您如何以这样的方式合并 2 个二叉搜索树,使得结果树包含两棵树的所有元素并保持 BST 属性。

I saw the solution provided in How to merge two BST's efficiently?我看到了如何有效地合并两个 BST 中提供的解决方案

However that solution involves converting into a Double Linked List.然而,该解决方案涉及转换为双链表。 I was wondering if there is a more elegant way of doing this which could be done in place without the conversion.我想知道是否有一种更优雅的方法可以在没有转换的情况下完成。 I came up with the following pseudocode.我想出了以下伪代码。 Does it work for all cases?它适用于所有情况吗? Also I am having trouble with the 3rd case.我也遇到了第 3 种情况的问题。

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
            // ...
        }
}

The two binary search trees (BST) cannot be merged directly during a recursive traversal.在递归遍历期间不能直接合并两个二叉搜索树 (BST)。 Suppose we should merge Tree 1 and Tree 2 shown in the figure.假设我们应该合并图中所示的树 1 和树 2。

图。1

The recursion should reduce the merging to a simpler situation.递归应该将合并减少到更简单的情况。 We cannot reduce the merging only to the respective left subtrees L1 and L2, because L2 can contain numbers larger than 10, so we would need to include the right subtree R1 into the process.我们不能只将合并减少到各自的左子树 L1 和 L2,因为 L2 可以包含大于 10 的数字,因此我们需要将右子树 R1 包含到过程中。 But then we include numbers greater than 10 and possibly greater than 20, so we would need to include the right subtree R2 as well.但是随后我们包含大于 10 并且可能大于 20 的数字,因此我们还需要包含右子树 R2。 A similar reasoning shows that we cannot simplify the merging by including subtrees from Tree 1 and from Tree 2 at the same time.类似的推理表明,我们不能通过同时包含来自树 1 和来自树 2 的子树来简化合并。

The only possibility for reduction is to simplify only inside the respective trees.减少的唯一可能性是仅在各自的树内部进行简化。 So, we can transform the trees to their right spines with sorted nodes:因此,我们可以使用已排序的节点将树转换为正确的脊椎:

图2

Now, we can merge the two spines easily into one spine.现在,我们可以轻松地将两个脊椎合并为一个脊椎。 This spine is in fact a BST, so we could stop here.这个脊椎实际上是一个 BST,所以我们可以到此为止。 However, this BST is completely unbalanced, so we transform it to a balanced BST.但是,这个 BST 是完全不平衡的,所以我们将其转换为平衡的 BST。

The complexity is:复杂度是:

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)

The complete running code is on http://ideone.com/RGBFQ .完整的运行代码在http://ideone.com/RGBFQ 上 Here are the essential parts.这是必不可少的部分。 The top level code is a follows:顶层代码如下:

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

The auxiliary functions are for the tranformation to 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); 
} 

Merging of the spines:合并脊椎:

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

Balancing:平衡:

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

Assuming we have two trees A and B we insert root of tree A into tree B and using rotations move inserted root to become new root of tree B. Next we recursively merge left and right sub-trees of trees A and B. This algorithm takes into account both trees structure but insertion still depends on how balanced target tree is.假设我们有两棵树 A 和 B,我们将树 A 的根插入到树 B 中,并使用旋转移动插入的根成为树 B 的新根。接下来我们递归合并树 A 和 B 的左右子树。该算法采用考虑到两种树结构,但插入仍然取决于目标树的平衡程度。 You can use this idea to merge the two trees in O(n+m) time and O(1) space.你可以用这个想法在O(n+m)时间和O(1)空间合并两棵树。


The following implementation is due to Dzmitry Huba :以下实现归功于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);
}

The best way we could merge the trees in place is something like:我们可以将树合并到位的最佳方法是:

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

Each iteration in the for loop is O(log n) since we are dealing with trees, and the for loop will be iterated n times, so in total we have O(n log n).因为我们正在处理树,所以 for 循环中的每次迭代都是 O(log n),并且 for 循环将迭代 n 次,所以我们总共有 O(n log n)。

This blog post provides a solution to the problem with O(logn) space complexity. 这篇博文提供了 O(logn) 空间复杂度问题的解决方案。 (Pay attention that the given approach does not modify input trees.) (请注意,给定的方法不会修改输入树。)

This can be done in 3 steps:这可以通过 3 个步骤完成:

  1. covert the BSTs to sorted linked list (this can be done in place with O(m+n) time)将 BST 转换为已排序的链表(这可以在 O(m+n) 时间内完成)
  2. Merge this two sorted linked lists to a single list (this can be done in place with O(m+n) time)将这两个排序的链表合并为一个列表(这可以在 O(m+n) 时间内完成)
  3. Convert sorted linked list to balanced BST (this can be done in place with O(m+n) time)将排序的链表转换为平衡的 BST(这可以在 O(m+n) 时间就地完成)

A BST is a ordered or sorted binary tree. BST 是有序或排序的二叉树。 My algorithm would be to simple :我的算法很简单:

  • traverse through both trees穿过两棵树
  • compare the values比较值
  • insert the smaller of the two into a new BST.将两者中较小的插入到新的 BST 中。

The python code for traversing is as follows:遍历的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)

The cost for traversing through the BST and building a new merged BST would remain O(n)遍历 BST 并构建新的合并 BST 的成本将保持为O(n)

Here is what I would do.这就是我要做的。

This solution is O(n1+n2) time complexity.该解决方案的时间复杂度为 O(n1+n2)。

STEPS:脚步:

  1. Perform the inorder traversal of both the trees to get sorted arrays --> linear time对两棵树执行中序遍历以获得排序数组 --> 线性时间
  2. Merge the two arrays --> again linear time合并两个数组 --> 再次线性时间
  3. Convert the merged array into a Balanced binary search tree --> again linear time将合并后的数组转化为平衡二叉搜索树--> 再次线性时间

This would require O(n1+n2) time and space.这将需要 O(n1+n2) 时间和空间。

Links you may find useful while implementing:在实施时您可能会发现有用的链接:

Assuming the question is just to print sorted from both BSTs.假设问题只是从两个 BST 打印排序。 Then the easier way is,那么更简单的方法是,

  1. Store inorder traversal of 2 BSTs in 2 seperate arrays.将 2 个 BST 的中序遍历存储在 2 个单独的数组中。
  2. Now the problem reduces to merging\\printing elements from 2 sorted arrays, which we got from step one.现在问题简化为从 2 个排序数组合并\\打印元素,这是我们从第一步得到的。 This merging can be done in o(m) when m>n or o(n) when m当 m>n 或 o(n) 当 m 时,这种合并可以在 o(m) 中完成

Complexity: o(m+n) Aux space: o(m+n) for the 2 arrays复杂度:o(m+n) 辅助空间:o(m+n) 用于 2 个阵列

MergeTwoBST_to_BalancedBST.java 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: 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;
  }
}

The following algorithm is from Algorithms in C++ .以下算法来自Algorithms in C++

The idea is almost the same as in the algorithm posted by PengOne.这个想法与 PengOne 发布的算法几乎相同。 This algorithm does in place merging, time complexity is O(n+m).该算法进行就地合并,时间复杂度为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 just inserts an item in the right place in the tree. 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);
    }
}

rotateRight and rotateLeft keep tree in the right order. 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;
}

Here link is node * .这里的linknode *

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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