Comment fusionner 2 arbres de recherche binaire de telle sorte que l'arbre résultant contienne tous les éléments des deux arbres et conserve également la propriété BST.
J'ai vu la solution proposée dans Comment fusionner deux BST efficacement?
Cependant, cette solution implique la conversion en une liste double liée. Je me demandais s'il y avait une manière plus élégante de le faire qui pourrait être faite en place sans conversion. Je suis venu avec le pseudocode suivant. Cela fonctionne-t-il dans tous les cas? De plus, j'ai des problèmes avec le 3ème cas.
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
// ...
}
}
Les deux arbres de recherche binaires (BST) ne peuvent pas être fusionnés directement lors d'une traversée récursive. Supposons que nous devrions fusionner les arbres 1 et 2 présentés dans la figure.
La récursivité devrait réduire la fusion à une situation plus simple. Nous ne pouvons pas réduire La fusion uniquement aux sous-arbres gauche respectifs L1 et L2, car L2 peut contenir des nombres Supérieurs à 10, nous aurions donc besoin d'inclure le sous-arbre droit R1 dans le processus. Mais ensuite, nous incluons des nombres supérieurs à 10 et éventuellement supérieurs à 20. Il nous faudrait donc également inclure Le sous-arbre droit R2. Un raisonnement similaire montre que Nous ne pouvons pas simplifier la fusion en incluant les sous-arbres de Tree 1 et de Tree 2 En même temps.
La seule possibilité de réduction consiste à simplifier uniquement à l'intérieur des arbres respectifs. Ainsi, nous pouvons transformer Les arbres en épines droites avec les nœuds triés:
Maintenant, nous pouvons facilement fusionner les deux épines en une seule. Cette colonne vertébrale Est en fait une BST, nous pourrions donc nous arrêter ici. Cependant, cette BST Est complètement déséquilibrée, nous la transformons donc en une BST équilibrée.
La complexité est la suivante:
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)
Le code complet est disponible sur http://ideone.com/RGBFQ . Voici les pièces essentielles. Le code de niveau supérieur est le suivant:
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));
}
Les fonctions auxiliaires sont destinées à la transformation d’épines:
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);
}
Fusion des épines:
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;
}
Équilibrage:
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);
}
En supposant que nous ayons deux arbres A et B, nous insérons la racine de l’arbre A dans l’arbre B et en utilisant les rotations, la racine insérée devient la nouvelle racine de l’arbre B. Nous fusionnons ensuite de manière récursive les sous-arbres gauche et droit des arbres A et B. Cet algorithme prend La structure des deux arbres est prise en compte, mais l’insertion dépend toujours de la façon dont l’arbre cible est équilibré. Vous pouvez utiliser cette idée pour fusionner les deux arbres dans l'espace O(n+m)
time et O(1)
.
La mise en oeuvre suivante est due à 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);
}
La meilleure façon de fusionner les arbres en place est la suivante:
For each node n in first BST {
Go down the 2nd tree and find the appropriate place to insert n
Insert n there
}
Chaque itération dans la boucle for est O (log n) puisque nous avons affaire à des arbres, et la boucle for sera itérée n fois; nous avons donc au total O (n log n).
Un BST est un arbre binaire commandé ou trié. Mon algorithme serait trop simple:
Le code python pour traverser est le suivant:
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)
Le coût pour traverser la BST et construire une nouvelle BST fusionnée resterait O(n)
Cet article de blog fournit une solution au problème de la complexité de l'espace O(logn). (Faites attention à ce que l'approche donnée ne modifie pas les arbres d'entrée.)
En supposant que la question ne concerne que l’impression triée à partir des deux BST. Alors le plus simple est,
Complexité: o (m + n) Espace auxiliaire: o (m + n) pour les 2 tableaux
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;
}
}
L'algorithme suivant provient de Algorithmes en C++ .
L'idée est presque la même que dans l'algorithme publié par PengOne. Cet algorithme fusionne en place, la complexité temporelle est 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
insère simplement un élément au bon endroit dans l'arborescence.
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
et rotateLeft
gardez l'arbre dans le bon ordre.
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;
}
Ici link
est node *
.