L’arbre binaire ici n’est peut-être pas nécessairement un arbre de recherche binaire.
La structure pourrait être considérée comme -
struct node {
int data;
struct node *left;
struct node *right;
};
La solution maximale que je pouvais trouver avec un ami était quelque chose de ce genre -
Considérons cet arbre binaire :
Arbre binaire http://lcm.csa.iisc.ernet.in/dsa/img151.gif
Le parcours en ordre donne - 8, 4, 9, 2, 5, 1, 6, 3, 7
Et la traversée post-commande donne 8, 9, 4, 5, 2, 6, 7, 3, 1
Ainsi, par exemple, si nous voulons trouver l’ancêtre commun des nœuds 8 et 5, nous dressons une liste de tous les nœuds compris entre 8 et 5 dans la traversée de l’arbre en ordre, qui dans ce cas est [4, 9. , 2]. Ensuite, nous vérifions quel nœud de cette liste apparaît en dernier dans la traversée postérieure, qui est 2. Ainsi, l'ancêtre commun de 8 et 5 est 2.
La complexité de cet algorithme, je crois, est O(n) (O (n) pour les traversées en ordre/post-commande, le reste des étapes étant à nouveau O(n) car elles ne sont que simples itérations dans les tableaux). Mais il y a de fortes chances pour que ce soit faux. :-)
Mais c’est une approche très grossière et je ne suis pas sûr que cela se casse dans certains cas. Existe-t-il une autre solution (éventuellement plus optimale) à ce problème?
Nick Johnson a raison d'affirmer qu'un algorithme de complexité temporelle O(n) est le mieux que vous puissiez faire si vous n'avez pas de pointeur parent.) Pour une version récursive simple de cet algorithme, voir le code dans Le post de Kinding qui court dans O(n) temps.
Mais gardez à l'esprit que si vos nœuds ont des pointeurs parents, un algorithme amélioré est possible. Pour les deux nœuds en question, construisez une liste contenant le chemin d'accès de la racine au nœud en commençant par le nœud et en insérant le parent.
Ainsi, pour 8 dans votre exemple, vous obtenez (en indiquant les étapes): {4}, {2, 4}, {1, 2, 4}
Faites de même pour votre autre noeud en question, ce qui entraîne (étapes non illustrées): {1, 2}
Comparez maintenant les deux listes que vous avez créées en recherchant le premier élément où la liste diffère, ou le dernier élément d’une des listes, selon la première éventualité.
Cet algorithme nécessite O(h) temps, où h est la hauteur de l'arbre. Dans le pire des cas, O(h) équivaut à O (n), mais si l’arbre est équilibré, il ne s’agit que de O (log (n)). Il nécessite également O(h) d’espace. Une version améliorée est possible qui utilise uniquement un espace constant, avec le code indiqué dans Message de CEGRD
Quelle que soit la manière dont l'arborescence est construite, s'il s'agit d'une opération que vous exécutez plusieurs fois sans modifier l'arborescence, vous pouvez utiliser d'autres algorithmes nécessitant une préparation de temps O(n) [linéaire], mais trouver une paire ne prend que O(1) [constante]. Pour les références à ces algorithmes, voir la page du problème d'ancêtre commun le plus bas sur Wikipedia . (Nous remercions Jason pour avoir publié ce lien à l'origine)
En commençant par le noeud root
et en descendant vers le bas, si vous trouvez un noeud qui a soit p
ou q
comme enfant direct, il s'agit de la LCA. (edit - cela devrait être le cas si p
ou q
est la valeur du nœud, retournez-le. Sinon, cela échouera si l'une des p
ou q
est un enfant direct de l'autre.)
Sinon, si vous trouvez un noeud avec p
dans sa sous-arborescence droite (ou gauche) et q
dans sa sous-arborescence gauche (ou droite), il s'agit de la LCA.
Le code fixe ressemble à ceci:
treeNodePtr findLCA(treeNodePtr root, treeNodePtr p, treeNodePtr q) {
// no root no LCA.
if(!root) {
return NULL;
}
// if either p or q is the root then root is LCA.
if(root==p || root==q) {
return root;
} else {
// get LCA of p and q in left subtree.
treeNodePtr l=findLCA(root->left , p , q);
// get LCA of p and q in right subtree.
treeNodePtr r=findLCA(root->right , p, q);
// if one of p or q is in leftsubtree and other is in right
// then root it the LCA.
if(l && r) {
return root;
}
// else if l is not null, l is LCA.
else if(l) {
return l;
} else {
return r;
}
}
}
Le code ci-dessous échoue lorsque l'un est l'enfant direct de l'autre.
treeNodePtr findLCA(treeNodePtr root, treeNodePtr p, treeNodePtr q) {
// no root no LCA.
if(!root) {
return NULL;
}
// if either p or q is direct child of root then root is LCA.
if(root->left==p || root->left==q ||
root->right ==p || root->right ==q) {
return root;
} else {
// get LCA of p and q in left subtree.
treeNodePtr l=findLCA(root->left , p , q);
// get LCA of p and q in right subtree.
treeNodePtr r=findLCA(root->right , p, q);
// if one of p or q is in leftsubtree and other is in right
// then root it the LCA.
if(l && r) {
return root;
}
// else if l is not null, l is LCA.
else if(l) {
return l;
} else {
return r;
}
}
}
Voici le code de travail en Java
public static Node LCA(Node root, Node a, Node b) {
if (root == null) {
return null;
}
// If the root is one of a or b, then it is the LCA
if (root == a || root == b) {
return root;
}
Node left = LCA(root.left, a, b);
Node right = LCA(root.right, a, b);
// If both nodes lie in left or right then their LCA is in left or right,
// Otherwise root is their LCA
if (left != null && right != null) {
return root;
}
return (left != null) ? left : right;
}
Les réponses données jusqu'à présent utilisent la récursivité ou stockent, par exemple, un chemin en mémoire.
Ces deux approches peuvent échouer si vous avez un arbre très profond.
Voici mon point de vue sur cette question… .. Lorsque nous vérifions la profondeur (distance depuis la racine) des deux nœuds, s'ils sont égaux, nous pouvons alors nous déplacer en toute sécurité des deux nœuds vers l'ancêtre commun. Si l’une des profondeurs est plus grande, nous devrions remonter du nœud le plus profond tout en restant dans l’autre.
Voici le code:
findLowestCommonAncestor(v,w):
depth_vv = depth(v);
depth_ww = depth(w);
vv = v;
ww = w;
while( depth_vv != depth_ww ) {
if ( depth_vv > depth_ww ) {
vv = parent(vv);
depth_vv--;
else {
ww = parent(ww);
depth_ww--;
}
}
while( vv != ww ) {
vv = parent(vv);
ww = parent(ww);
}
return vv;
La complexité temporelle de cet algorithme est la suivante: O (n) ..__ La complexité spatiale de cet algorithme est la suivante: O (1).
En ce qui concerne le calcul de la profondeur, nous pouvons d’abord nous rappeler la définition: Si v est la racine, profondeur (v) = 0; Sinon, profondeur (v) = profondeur (parent (v)) + 1. Nous pouvons calculer la profondeur de la manière suivante:
depth(v):
int d = 0;
vv = v;
while ( vv is not root ) {
vv = parent(vv);
d++;
}
return d;
Eh bien, cela dépend de la structure de votre arbre binaire. Vraisemblablement, vous avez un moyen de trouver le nœud feuille souhaité en fonction de la racine de l’arbre - appliquez-le simplement aux deux valeurs jusqu’à ce que les branches que vous choisissez divergent.
Si vous ne pouvez pas trouver la feuille souhaitée à partir de la racine, votre seule solution - en fonctionnement normal et pour trouver le dernier nœud commun - est une recherche brutale de l’arbre.
Cela peut être trouvé à: - http://goursaha.freeoda.com/DataStructure/LowestCommonAncestor.html
tree_node_type *LowestCommonAncestor(
tree_node_type *root , tree_node_type *p , tree_node_type *q)
{
tree_node_type *l , *r , *temp;
if(root==NULL)
{
return NULL;
}
if(root->left==p || root->left==q || root->right ==p || root->right ==q)
{
return root;
}
else
{
l=LowestCommonAncestor(root->left , p , q);
r=LowestCommonAncestor(root->right , p, q);
if(l!=NULL && r!=NULL)
{
return root;
}
else
{
temp = (l!=NULL)?l:r;
return temp;
}
}
}
Pour trouver l'ancêtre commun de deux nœuds: -
Cela fonctionnerait pour l'arbre de recherche binaire.
L'algorithme moins commun des ancêtres hors ligne de Tarjan est assez bon (cf. aussi Wikipedia ). Il y a plus sur le problème (le problème le plus bas des ancêtres communs) sur Wikipedia .
J'ai essayé des images illustratives et du code de travail en Java,
http://tech.bragboy.com/2010/02/least-common-ancestor-without-using.html
L'algorithme récursif ci-dessous s'exécutera dans O (log N) pour un arbre binaire équilibré. Si l'un des noeuds passés à la fonction getLCA () est identique à la racine, la racine sera l'ACL et il n'y aura pas besoin d'effectuer de récursion.
Cas de test. [1] Les deux nœuds n1 et n2 sont dans l’arborescence et se trouvent de part et d’autre de leur nœud parent .[2] Le nœud n1 ou n2 est la racine, le LCA. est la racine .[3] Seulement n1 ou n2 est dans l'arborescence, LCA sera soit le nœud racine du sous-arbre de gauche de la racine de l'arborescence, soit LCA sera le nœud racine de la droite sous-arbre de la racine de l'arbre.
[4] Ni n1 ni n2 ne sont dans l'arborescence, il n'y a pas de LCA .[5] Les deux n1 et n2 sont en ligne droite l'un à côté de l'autre, LCA sera l'un ou l'autre n1 ou n2 qui est toujours ferme à la racine de l'arbre.
//find the search node below root
bool findNode(node* root, node* search)
{
//base case
if(root == NULL)
return false;
if(root->val == search->val)
return true;
//search for the node in the left and right subtrees, if found in either return true
return (findNode(root->left, search) || findNode(root->right, search));
}
//returns the LCA, n1 & n2 are the 2 nodes for which we are
//establishing the LCA for
node* getLCA(node* root, node* n1, node* n2)
{
//base case
if(root == NULL)
return NULL;
//If 1 of the nodes is the root then the root is the LCA
//no need to recurse.
if(n1 == root || n2 == root)
return root;
//check on which side of the root n1 and n2 reside
bool n1OnLeft = findNode(root->left, n1);
bool n2OnLeft = findNode(root->left, n2);
//n1 & n2 are on different sides of the root, so root is the LCA
if(n1OnLeft != n2OnLeft)
return root;
//if both n1 & n2 are on the left of the root traverse left sub tree only
//to find the node where n1 & n2 diverge otherwise traverse right subtree
if(n1OnLeft)
return getLCA(root->left, n1, n2);
else
return getLCA(root->right, n1, n2);
}
Descendez simplement de la variable root
tant que les deux nœuds donnés, par exemple p
et q
, pour lesquels Ancestor doit être trouvé, sont dans le même sous-arbre (ce qui signifie que leurs valeurs sont toutes deux plus petites ou plus grandes que la racine).
Cela va directement de la racine à l'ancêtre le moins commun, sans regarder le reste de l'arbre, donc c'est à peu près aussi rapide que possible. Quelques façons de le faire.
Itératif, O(1) espace
Python
def lowestCommonAncestor(self, root, p, q):
while (root.val - p.val) * (root.val - q.val) > 0:
root = (root.left, root.right)[p.val > root.val]
return root
Java
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while ((root.val - p.val) * (root.val - q.val) > 0)
root = p.val < root.val ? root.left : root.right;
return root;
}
en cas de débordement, je ferais (root.val - (long) p.val) * (root.val - (long) q.val)
Récursif
Python
def lowestCommonAncestor(self, root, p, q):
next = p.val < root.val > q.val and root.left or \
p.val > root.val < q.val and root.right
return self.lowestCommonAncestor(next, p, q) if next else root
Java
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return (root.val - p.val) * (root.val - q.val) < 1 ? root :
lowestCommonAncestor(p.val < root.val ? root.left : root.right, p, q);
}
S'il s'agit d'un arbre binaire complet avec des enfants du noeud x sous la forme 2 * x et 2 * x + 1, il existe un moyen plus rapide de le faire.
int get_bits(unsigned int x) {
int high = 31;
int low = 0,mid;
while(high>=low) {
mid = (high+low)/2;
if(1<<mid==x)
return mid+1;
if(1<<mid<x) {
low = mid+1;
}
else {
high = mid-1;
}
}
if(1<<mid>x)
return mid;
return mid+1;
}
unsigned int Common_Ancestor(unsigned int x,unsigned int y) {
int xbits = get_bits(x);
int ybits = get_bits(y);
int diff,kbits;
unsigned int k;
if(xbits>ybits) {
diff = xbits-ybits;
x = x >> diff;
}
else if(xbits<ybits) {
diff = ybits-xbits;
y = y >> diff;
}
k = x^y;
kbits = get_bits(k);
return y>>kbits;
}
Comment ça marche
- obtenir les bits nécessaires pour représenter x & y qui, en utilisant la recherche binaire, est O (log (32))
- le préfixe commun de la notation binaire de x & y est l'ancêtre commun
- selon ce qui est représenté par le plus grand nombre de bits est amené au même bit par k >> diff
- k = x ^ y supprime le préfixe commun de x & y
- trouver des bits représentant le suffixe restant
- décaler x ou y par le suffixe des bits pour obtenir le préfixe commun qui est l'ancêtre commun.
Cela fonctionne parce que, fondamentalement, divisez le plus grand nombre par deux de manière récursive jusqu'à ce que les deux nombres soient égaux. Ce nombre est l'ancêtre commun. La division est effectivement la bonne option. Nous avons donc besoin de trouver le préfixe commun de deux nombres pour trouver l'ancêtre le plus proche
Node *LCA(Node *root, Node *p, Node *q) {
if (!root) return NULL;
if (root == p || root == q) return root;
Node *L = LCA(root->left, p, q);
Node *R = LCA(root->right, p, q);
if (L && R) return root; // if p and q are on both sides
return L ? L : R; // either one of p,q is on one side OR p,q is not in L&R subtrees
}
En scala, le code est:
abstract class Tree
case class Node(a:Int, left:Tree, right:Tree) extends Tree
case class Leaf(a:Int) extends Tree
def lca(tree:Tree, a:Int, b:Int):Tree = {
tree match {
case Node(ab,l,r) => {
if(ab==a || ab ==b) tree else {
val temp = lca(l,a,b);
val temp2 = lca(r,a,b);
if(temp!=null && temp2 !=null)
tree
else if (temp==null && temp2==null)
null
else if (temp==null) r else l
}
}
case Leaf(ab) => if(ab==a || ab ==b) tree else null
}
}
Considérez cet arbre
Si nous effectuons une traversée pré-commande et post-commande et que nous trouvons le premier prédécesseur et successeur commun, nous obtenons l'ancêtre commun.
postorder => 0,2,1,5,4,6,3,8,10,11,9,14,15,13,12,7 précommande => 7,3,1,0,2,6,4,5,12,9,8,11,10,13,15,14
Moins ancêtre commun de 8,11
en post-commande nous avons => 9,14,15,13,12,7 après 8 & 11en précommande nous avons => 7,3,1,0,2,6,4,5,12,9 avant 8 et 11
9 est le premier numéro commun qui survient après 8 & 11 en post-commande et avant 8 & 11 en précommande, d'où 9 est la réponse
Moins ancêtre commun de 5,10
11,9,14,15,13,12,7 en post-commande 7,3,1,0,2,6,4 en précommande
7 est le premier numéro qui apparaît après 5,10 en post-commande et avant 5,10 en précommande, d'où 7 est la réponse
Essayez comme ça
node * lca(node * root, int v1,int v2)
{
if(!root) {
return NULL;
}
if(root->data == v1 || root->data == v2) {
return root;}
else
{
if((v1 > root->data && v2 < root->data) || (v1 < root->data && v2 > root->data))
{
return root;
}
if(v1 < root->data && v2 < root->data)
{
root = lca(root->left, v1, v2);
}
if(v1 > root->data && v2 > root->data)
{
root = lca(root->right, v1, v2);
}
}
return root;
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null || root == p || root == q){
return root;
}
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
return left == null ? right : right == null ? left : root;
}
Solution 1: récursive - plus rapide
- Complexité temporelle: O (n)
- Complexité de l'espace: O(h) - pour la pile d'appels récursifs
class Solution
{
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
{
if(root == null || root == p || root == q)
return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left == null)
return right;
else if(right == null)
return left;
else
return root; // If(left != null && right != null)
}
}
Solution 2: Itératif - Utilisation des pointeurs parents - Plus lent
- Complexité temporelle: O(n) - Dans le pire des cas, nous pourrions visiter tous les nœuds de l'arborescence binaire.
- Complexité de l'espace: O(n) - L'espace utilisé par le pointeur parent Hash-table, ancestor_set et queue, correspond à O(n) chacun.
class Solution
{
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)
{
HashMap<TreeNode, TreeNode> parent_map = new HashMap<>();
HashSet<TreeNode> ancestors_set = new HashSet<>();
Queue<TreeNode> queue = new LinkedList<>();
parent_map.put(root, null);
queue.add(root);
while(!parent_map.containsKey(p) || !parent_map.containsKey(q))
{
TreeNode node = queue.poll();
if(node.left != null)
{
parent_map.put(node.left, node);
queue.add(node.left);
}
if(node.right != null)
{
parent_map.put(node.right, node);
queue.add(node.right);
}
}
while(p != null)
{
ancestors_set.add(p);
p = parent_map.get(p);
}
while(!ancestors_set.contains(q))
q = parent_map.get(q);
return q;
}
}
Code pour une première recherche en profondeur pour s’assurer que les deux nœuds sont dans l’arbre . C’est seulement après que la recherche LCA avance les visités et redémarrez la recherche à un moment donné où nous nous sommes arrêtés pour améliorer le second noeud (s'il n'est pas trouvé VISITED)
public class searchTree {
static boolean v1=false,v2=false;
public static boolean bfs(Treenode root, int value){
if(root==null){
return false;
}
Queue<Treenode> q1 = new LinkedList<Treenode>();
q1.add(root);
while(!q1.isEmpty())
{
Treenode temp = q1.peek();
if(temp!=null) {
q1.remove();
if (temp.value == value) return true;
if (temp.left != null) q1.add(temp.left);
if (temp.right != null) q1.add(temp.right);
}
}
return false;
}
public static Treenode lcaHelper(Treenode head, int x,int y){
if(head==null){
return null;
}
if(head.value == x || head.value ==y){
if (head.value == y){
v2 = true;
return head;
}
else {
v1 = true;
return head;
}
}
Treenode left = lcaHelper(head.left, x, y);
Treenode right = lcaHelper(head.right,x,y);
if(left!=null && right!=null){
return head;
}
return (left!=null) ? left:right;
}
public static int lca(Treenode head, int h1, int h2) {
v1 = bfs(head,h1);
v2 = bfs(head,h2);
if(v1 && v2){
Treenode lca = lcaHelper(head,h1,h2);
return lca.value;
}
return -1;
}
}
Le moyen le plus simple de trouver l'ancêtre commun le plus bas consiste à utiliser l'algorithme suivant:
Examinez le noeud racine Si valeur1 et valeur2 sont strictement inférieures à la valeur du noeud racine Examinez la sous-arborescence gauche Else si valeur1 et valeur2 sont strictement supérieures à la valeur du nœud racine Examinez le sous-arbre droit Sinon retourne la racine
public int LCA(TreeNode root, int value 1, int value 2) {
while (root != null) {
if (value1 < root.data && value2 < root.data)
return LCA(root.left, value1, value2);
else if (value2 > root.data && value2 2 root.data)
return LCA(root.right, value1, value2);
else
return root
}
return null;
}
Ce qui suit est le moyen le plus rapide de résoudre ceci . Complexité de l'espace O(1), Complexité temporelle O (n). Si
Si un nœud a la valeur gauche et droite non null, alors ce nœud est votre réponse (3ème 'if' à partir du haut). En itérant, si une valeur est trouvée, toutes les valeurs étant uniques et devant être présentes, nous n'avons pas besoin de rechercher ses descendants. il suffit donc de renvoyer le noeud trouvé qui correspond. si le contenu des branches gauche et droite d'un nœud est null, propagez null vers le haut. Lorsque la récursion de niveau supérieur est atteinte, si une branche renvoie une valeur, les autres ne propagent pas cette valeur. Si elle atteint la récursivité au niveau racine avec une valeur null et une valeur non nulle, la valeur renvoyée n'est pas null, car la question promet que la valeur existe, elle doit figurer dans l'arborescence enfants du nœud trouvé que nous n'avons jamais parcouru.
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) return null;
if(root.val == p.val || root.val == q.val) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right !=null) return root;
if (left == null && right ==null) return null;
return (left != null ? left : right);
}
}
Voici deux approches en c # (.net) (toutes deux décrites ci-dessus) à titre de référence:
Version récursive de la recherche d’ACV dans l’arborescence binaire (O (N) - chaque nœud étant visité au maximum) .__ (les points principaux de la solution est que l’ACV est (a)les éléments résident de chaque côté des sous-arbres (gauche et droite) est LCA. (b)} _ Et peu importe le nœud présent de l'un ou l'autre côté - au départ, j'ai essayé de conserver cette information, et évidemment le caractère récursif La fonction devient tellement déroutante. Une fois que je l’ai réalisée, elle est devenue très élégante.
La recherche des deux nœuds (O (N)) et le suivi des chemins (utilise davantage d’espace - le n ° 1 est probablement supérieur même si cet espace est probablement négligeable si l’arborescence binaire est bien équilibrée, puis une consommation de mémoire supplémentaire sera juste à l’intérieur. O (log (N)).
de sorte que les chemins soient comparés (essentiellement similaires à la réponse acceptée - mais les chemins sont calculés en supposant que le nœud de pointeur n’est pas présent dans le nœud d’arbre binaire)
Juste pour l'achèvement (pas lié à la question), LCA en BST (O (log (N))
Des tests
Récursif:
private BinaryTreeNode LeastCommonAncestorUsingRecursion(BinaryTreeNode treeNode,
int e1, int e2)
{
Debug.Assert(e1 != e2);
if(treeNode == null)
{
return null;
}
if((treeNode.Element == e1)
|| (treeNode.Element == e2))
{
//we don't care which element is present (e1 or e2), we just need to check
//if one of them is there
return treeNode;
}
var nLeft = this.LeastCommonAncestorUsingRecursion(treeNode.Left, e1, e2);
var nRight = this.LeastCommonAncestorUsingRecursion(treeNode.Right, e1, e2);
if(nLeft != null && nRight != null)
{
//note that this condition will be true only at least common ancestor
return treeNode;
}
else if(nLeft != null)
{
return nLeft;
}
else if(nRight != null)
{
return nRight;
}
return null;
}
où la version récursive privée ci-dessus est invoquée par la méthode publique suivante:
public BinaryTreeNode LeastCommonAncestorUsingRecursion(int e1, int e2)
{
var n = this.FindNode(this._root, e1);
if(null == n)
{
throw new Exception("Element not found: " + e1);
}
if (e1 == e2)
{
return n;
}
n = this.FindNode(this._root, e2);
if (null == n)
{
throw new Exception("Element not found: " + e2);
}
var node = this.LeastCommonAncestorUsingRecursion(this._root, e1, e2);
if (null == node)
{
throw new Exception(string.Format("Least common ancenstor not found for the given elements: {0},{1}", e1, e2));
}
return node;
}
Solution en gardant trace des chemins des deux noeuds:
public BinaryTreeNode LeastCommonAncestorUsingPaths(int e1, int e2)
{
var path1 = new List<BinaryTreeNode>();
var node1 = this.FindNodeAndPath(this._root, e1, path1);
if(node1 == null)
{
throw new Exception(string.Format("Element {0} is not found", e1));
}
if(e1 == e2)
{
return node1;
}
List<BinaryTreeNode> path2 = new List<BinaryTreeNode>();
var node2 = this.FindNodeAndPath(this._root, e2, path2);
if (node1 == null)
{
throw new Exception(string.Format("Element {0} is not found", e2));
}
BinaryTreeNode lca = null;
Debug.Assert(path1[0] == this._root);
Debug.Assert(path2[0] == this._root);
int i = 0;
while((i < path1.Count)
&& (i < path2.Count)
&& (path2[i] == path1[i]))
{
lca = path1[i];
i++;
}
Debug.Assert(null != lca);
return lca;
}
où FindNodeAndPath est défini comme
private BinaryTreeNode FindNodeAndPath(BinaryTreeNode node, int e, List<BinaryTreeNode> path)
{
if(node == null)
{
return null;
}
if(node.Element == e)
{
path.Add(node);
return node;
}
var n = this.FindNodeAndPath(node.Left, e, path);
if(n == null)
{
n = this.FindNodeAndPath(node.Right, e, path);
}
if(n != null)
{
path.Insert(0, node);
return n;
}
return null;
}
BST (LCA) - non lié (juste pour compléter pour référence)
public BinaryTreeNode BstLeastCommonAncestor(int e1, int e2)
{
//ensure both elements are there in the bst
var n1 = this.BstFind(e1, throwIfNotFound: true);
if(e1 == e2)
{
return n1;
}
this.BstFind(e2, throwIfNotFound: true);
BinaryTreeNode leastCommonAcncestor = this._root;
var iterativeNode = this._root;
while(iterativeNode != null)
{
if((iterativeNode.Element > e1 ) && (iterativeNode.Element > e2))
{
iterativeNode = iterativeNode.Left;
}
else if((iterativeNode.Element < e1) && (iterativeNode.Element < e2))
{
iterativeNode = iterativeNode.Right;
}
else
{
//i.e; either iterative node is equal to e1 or e2 or in between e1 and e2
return iterativeNode;
}
}
//control will never come here
return leastCommonAcncestor;
}
Tests unitaires
[TestMethod]
public void LeastCommonAncestorTests()
{
int[] a = { 13, 2, 18, 1, 5, 17, 20, 3, 6, 16, 21, 4, 14, 15, 25, 22, 24 };
int[] b = { 13, 13, 13, 2, 13, 18, 13, 5, 13, 18, 13, 13, 14, 18, 25, 22};
BinarySearchTree bst = new BinarySearchTree();
foreach (int e in a)
{
bst.Add(e);
bst.Delete(e);
bst.Add(e);
}
for(int i = 0; i < b.Length; i++)
{
var n = bst.BstLeastCommonAncestor(a[i], a[i + 1]);
Assert.IsTrue(n.Element == b[i]);
var n1 = bst.LeastCommonAncestorUsingPaths(a[i], a[i + 1]);
Assert.IsTrue(n1.Element == b[i]);
Assert.IsTrue(n == n1);
var n2 = bst.LeastCommonAncestorUsingRecursion(a[i], a[i + 1]);
Assert.IsTrue(n2.Element == b[i]);
Assert.IsTrue(n2 == n1);
Assert.IsTrue(n2 == n);
}
}
Vous avez raison de dire que sans un noeud parent, une solution avec parcours vous donnera O(n) complexité de temps.
Approche transversale Supposons que vous trouviez l'ACV pour les nœuds A et B, l'approche la plus simple consiste à obtenir d'abord le chemin de la racine à A, puis le chemin de la racine à B. Une fois que vous avez ces deux chemins , vous pouvez facilement les parcourir et trouver le dernier nœud commun, qui est l’ancêtre commun le plus bas de A et B.
Solution récursive Une autre approche consiste à utiliser la récursivité. Premièrement, nous pouvons obtenir l’ACV de l’arbre gauche et de l’arbre droit (s’il existe). Si A ou B est le nœud racine, la racine est l'ACL et nous renvoyons simplement la racine, qui est le point final de la récursivité. En continuant de diviser l’arbre en sous-arbres, nous atteindrons éventuellement A et B.
Pour combiner des solutions de sous-problèmes, si l'ACL (arbre de gauche) renvoie un nœud, nous savons que A et B se localisent dans l'arborescence de gauche et que le nœud renvoyé est le résultat final. Si LCA (à gauche) et LCA (à droite) renvoient des nœuds non vides, cela signifie que A et B se trouvent respectivement dans les arborescences gauche et droite. Dans ce cas, le nœud racine est le nœud commun le plus bas.
Vérifiez Plus bas ancêtre commun pour une analyse et une solution détaillées.
Certaines des solutions ici supposent qu'il y ait une référence au nœud racine, d'autres présument que cet arbre est un BST . Partage de ma solution à l'aide de hashmap, sans référence à root
Le nœud et l'arbre peuvent être BST ou non-BST:
var leftParent : Node? = left
var rightParent : Node? = right
var map = [data : Node?]()
while leftParent != nil {
map[(leftParent?.data)!] = leftParent
leftParent = leftParent?.parent
}
while rightParent != nil {
if let common = map[(rightParent?.data)!] {
return common
}
rightParent = rightParent?.parent
}
Il peut y avoir une autre approche. Cependant, ce n'est pas aussi efficace que celui déjà suggéré dans les réponses.
Créez un vecteur de chemin pour le noeud n1.
Créez un second vecteur de chemin pour le noeud n2.
Le vecteur de chemin impliquant les nœuds définis à partir de celui-ci traverserait pour atteindre le nœud en question.
Comparez les deux vecteurs de chemin. L'index où ils ne correspondent pas, renvoie le nœud à cet index - 1. Cela donnerait le LCA.
Inconvénients de cette approche:
Nécessité de parcourir l'arbre deux fois pour calculer les vecteurs de chemin . Besoin d'un espace supplémentaire O(h) pour stocker les vecteurs de chemin.
Cependant, cela est facile à mettre en œuvre et à comprendre.
Code de calcul du vecteur de chemin:
private boolean findPathVector (TreeNode treeNode, int key, int pathVector[], int index) {
if (treeNode == null) {
return false;
}
pathVector [index++] = treeNode.getKey ();
if (treeNode.getKey () == key) {
return true;
}
if (findPathVector (treeNode.getLeftChild (), key, pathVector, index) ||
findPathVector (treeNode.getRightChild(), key, pathVector, index)) {
return true;
}
pathVector [--index] = 0;
return false;
}
Manière brute:
Le problème avec la méthode ci-dessus est que nous allons "rechercher" plusieurs fois, c’est-à-dire qu’il est possible que chaque nœud soit traversé plusieurs fois .. Nous pouvons résoudre ce problème si nous pouvons enregistrer les informations de manière à Traitez-le à nouveau (pensez à la programmation dynamique).
Ainsi, plutôt que de rechercher tous les nœuds, nous conservons une trace de ce qui a déjà été trouvé.
Meilleure façon:
Code:
struct Node *
findCA(struct Node *root, struct Node *n1, struct Node *n2, int *set) {
int left_set, right_set;
left_set = right_set = 0;
struct Node *leftCA, *rightCA;
leftCA = rightCA = NULL;
if (root == NULL) {
return NULL;
}
if (root == n1 || root == n2) {
left_set = 1;
if (n1 == n2) {
right_set = 1;
}
}
if(!left_set) {
leftCA = findCA(root->left, n1, n2, &left_set);
if (leftCA) {
return leftCA;
}
}
if (!right_set) {
rightCA= findCA(root->right, n1, n2, &right_set);
if(rightCA) {
return rightCA;
}
}
if (left_set && right_set) {
return root;
} else {
*set = (left_set || right_set);
return NULL;
}
}
Si quelqu'un s'intéresse au pseudo-code (pour le travail universitaire), il y en a un.
GETLCA(BINARYTREE BT, NODE A, NODE B)
IF Root==NIL
return NIL
ENDIF
IF Root==A OR root==B
return Root
ENDIF
Left = GETLCA (Root.Left, A, B)
Right = GETLCA (Root.Right, A, B)
IF Left! = NIL AND Right! = NIL
return root
ELSEIF Left! = NIL
Return Left
ELSE
Return Right
ENDIF
Bien que nous ayons déjà répondu à cette question, c’est mon approche de ce problème en utilisant le langage de programmation C. Bien que le code affiche un arbre de recherche binaire (en ce qui concerne insert ()), l'algorithme fonctionne également pour un arbre binaire. L'idée est de passer en revue tous les nœuds qui vont du nœud A au nœud B dans le parcours transversal, en recherchant les indices correspondants dans le parcours transversal. Le nœud avec l'index maximal dans la traversée post-commande est l'ancêtre commun le plus bas.
Il s’agit d’un code C fonctionnel permettant d’implémenter une fonction permettant de trouver l’ancêtre commun le plus bas dans un arbre binaire. Je fournis également toutes les fonctions utilitaires, etc., mais passez à CommonAncestor () pour une compréhension rapide.
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <math.h>
static inline int min (int a, int b)
{
return ((a < b) ? a : b);
}
static inline int max (int a, int b)
{
return ((a > b) ? a : b);
}
typedef struct node_ {
int value;
struct node_ * left;
struct node_ * right;
} node;
#define MAX 12
int IN_ORDER[MAX] = {0};
int POST_ORDER[MAX] = {0};
createNode(int value)
{
node * temp_node = (node *)malloc(sizeof(node));
temp_node->left = temp_node->right = NULL;
temp_node->value = value;
return temp_node;
}
node *
insert(node * root, int value)
{
if (!root) {
return createNode(value);
}
if (root->value > value) {
root->left = insert(root->left, value);
} else {
root->right = insert(root->right, value);
}
return root;
}
/* Builds inorder traversal path in the IN array */
void
inorder(node * root, int * IN)
{
static int i = 0;
if (!root) return;
inorder(root->left, IN);
IN[i] = root->value;
i++;
inorder(root->right, IN);
}
/* Builds post traversal path in the POST array */
void
postorder (node * root, int * POST)
{
static int i = 0;
if (!root) return;
postorder(root->left, POST);
postorder(root->right, POST);
POST[i] = root->value;
i++;
}
int
findIndex(int * A, int value)
{
int i = 0;
for(i = 0; i< MAX; i++) {
if(A[i] == value) return i;
}
}
int
CommonAncestor(int val1, int val2)
{
int in_val1, in_val2;
int post_val1, post_val2;
int j=0, i = 0; int max_index = -1;
in_val1 = findIndex(IN_ORDER, val1);
in_val2 = findIndex(IN_ORDER, val2);
post_val1 = findIndex(POST_ORDER, val1);
post_val2 = findIndex(POST_ORDER, val2);
for (i = min(in_val1, in_val2); i<= max(in_val1, in_val2); i++) {
for(j = 0; j < MAX; j++) {
if (IN_ORDER[i] == POST_ORDER[j]) {
if (j > max_index) {
max_index = j;
}
}
}
}
printf("\ncommon ancestor of %d and %d is %d\n", val1, val2, POST_ORDER[max_index]);
return max_index;
}
int main()
{
node * root = NULL;
/* Build a tree with following values */
//40, 20, 10, 30, 5, 15, 25, 35, 1, 80, 60, 100
root = insert(root, 40);
insert(root, 20);
insert(root, 10);
insert(root, 30);
insert(root, 5);
insert(root, 15);
insert(root, 25);
insert(root, 35);
insert(root, 1);
insert(root, 80);
insert(root, 60);
insert(root, 100);
/* Get IN_ORDER traversal in the array */
inorder(root, IN_ORDER);
/* Get post order traversal in the array */
postorder(root, POST_ORDER);
CommonAncestor(1, 100);
}
Voici ce que je pense,
Complexité: Étape 1: O(n), étape 2 = ~ O(n), total = ~ O (n).
J'ai trouvé une solution
En fonction de 3 traversées, vous pouvez décider qui est le LCA . À partir du LCA, recherchez la distance des deux nœuds . Ajoutez ces deux distances, ce qui est la réponse.
Voici la façon de faire C++. J'ai essayé de garder l'algorithme aussi facile que possible à comprendre:
// Assuming that `BinaryNode_t` has `getData()`, `getLeft()` and `getRight()`
class LowestCommonAncestor
{
typedef char type;
// Data members which would behave as place holders
const BinaryNode_t* m_pLCA;
type m_Node1, m_Node2;
static const unsigned int TOTAL_NODES = 2;
// The core function which actually finds the LCA; It returns the number of nodes found
// At any point of time if the number of nodes found are 2, then it updates the `m_pLCA` and once updated, we have found it!
unsigned int Search (const BinaryNode_t* const pNode)
{
if(pNode == 0)
return 0;
unsigned int found = 0;
found += (pNode->getData() == m_Node1);
found += (pNode->getData() == m_Node2);
found += Search(pNode->getLeft()); // below condition can be after this as well
found += Search(pNode->getRight());
if(found == TOTAL_NODES && m_pLCA == 0)
m_pLCA = pNode; // found !
return found;
}
public:
// Interface method which will be called externally by the client
const BinaryNode_t* Search (const BinaryNode_t* const pHead,
const type node1,
const type node2)
{
// Initialize the data members of the class
m_Node1 = node1;
m_Node2 = node2;
m_pLCA = 0;
// Find the LCA, populate to `m_pLCANode` and return
(void) Search(pHead);
return m_pLCA;
}
};
Comment l'utiliser:
LowestCommonAncestor lca;
BinaryNode_t* pNode = lca.Search(pWhateverBinaryTreeNodeToBeginWith);
if(pNode != 0)
...