ここでのバイナリツリーは、必ずしもバイナリ検索ツリーであるとは限りません。
構造は次のように解釈できます-
struct node {
int data;
struct node *left;
struct node *right;
};
友達と一緒に解決できる最大の解決策は、このようなものでした-
検討 この二分木 :
順方向のトラバースの収量-8、4、9、2、5、1、6、3、7
そして、後順走査の歩留まり-8、9、4、5、2、6、7、3、1
したがって、たとえば、ノード8と5の共通の祖先を検索する場合は、順序ツリーのトラバーサルで8と5の間にあるすべてのノードのリストを作成します。この場合は[4、9 、2]。次に、このリストのどのノードがポストオーダートラバーサルの最後に表示されるかを確認します。これは2です。したがって、8と5の共通の祖先は2です。
このアルゴリズムの複雑さは、O(n)(inorder/postorderトラバーサルのO(n)、残りのステップはO(n)であると信じています配列内の単純な反復にすぎません)。しかし、これは間違っている可能性が高いです。 :-)
しかし、これは非常に粗雑なアプローチであり、場合によっては故障するかどうかはわかりません。この問題に対する他の(おそらくより最適な)解決策はありますか?
ニックジョンソンは、O(n)時間複雑度アルゴリズムが親ポインターがない場合にできる最善の方法であることは正しいです。そのアルゴリズムの単純な再帰バージョンについては、 Kindingの投稿 これはO(n)時間で実行されます。
ただし、ノードに親ポインターがある場合、改善されたアルゴリズムが可能であることに注意してください。問題の両方のノードについて、ノードから開始し、親を挿入する前に、ルートからノードへのパスを含むリストを作成します。
したがって、あなたの例の8の場合、次のようになります(ステップを表示):{4}、{2、4}、{1、2、4}
問題になっている他のノードに対しても同じことを行い、結果として(ステップは表示されません):{1、2}
次に、リストが異なる最初の要素、またはリストのいずれかの最後の要素のどちらか早い方を探して、作成した2つのリストを比較します。
このアルゴリズムでは、O(h) timeが必要です。hはツリーの高さです。最悪の場合、O(h)はO(n)と同等ですが、ツリーのバランスが取れている場合、O(log(n))のみです。また、O(h)スペースも必要です。 CEGRDの投稿 に示されているコードで、一定のスペースのみを使用する改良バージョンが可能です。
ツリーの構築方法に関係なく、これをツリー間で何度も実行する操作である場合、O(n) [linear]時間を必要とする他のアルゴリズムを使用できます準備はできますが、その後のペアの検索にはO(1) [constant]時間しかかかりません。これらのアルゴリズムの参照については、 Wikipedia の最も一般的な先祖問題のページを参照してください。 (このリンクを最初に投稿したことに対するジェイソンへのクレジット)
root
name__ノードから開始し、p
name__またはq
name__のいずれかを直接の子として持つノードを見つけた場合、それはLCAです。 (編集-p
name__またはq
name__がノードの値である場合、これを返す必要があります。そうでない場合、p
name__またはq
name__の一方が他方の直接の子である場合に失敗します。)
そうでない場合、右(または左)サブツリーにp
name__があり、左(または右)サブツリーにq
name__があるノードが見つかった場合、それはLCAです。
修正されたコードは次のようになります。
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;
}
}
}
以下のコードは、どちらかが他の直接の子である場合に失敗します。
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;
}
}
}
これが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;
}
これまでの回答では、再帰を使用したり、メモリ内のパスなどを保存したりしています。
非常に深いツリーがある場合、これらのアプローチは両方とも失敗する可能性があります。
この質問に対する私の見解を以下に示します。両方のノードの深さ(ルートからの距離)を確認するとき、それらが等しい場合は、両方のノードから共通の祖先に向かって安全に上に移動できます。一方の深さが大きい場合は、もう一方のノードにとどまりながら、より深いノードから上に移動する必要があります。
コードは次のとおりです。
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;
このアルゴリズムの時間の複雑さは、O(n)です。このアルゴリズムのスペースの複雑さは、O(1)です。
深さの計算に関して、最初に定義を覚えておくことができます。vがルートの場合、depth(v)= 0;それ以外の場合、depth(v)= depth(parent(v))+1。次のように深さを計算できます。
depth(v):
int d = 0;
vv = v;
while ( vv is not root ) {
vv = parent(vv);
d++;
}
return d;
まあ、この種のバイナリツリーの構造に依存します。おそらく、ツリーのルートを指定して目的のリーフノードを見つける方法があります。選択したブランチが分岐するまで、両方の値にそれを適用するだけです。
ルートから目的のリーフを見つける方法がない場合、唯一の解決策は、通常の操作でも最後の共通ノードを見つけることでも、ツリーの総当たり検索です。
これは次の場所にあります: 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;
}
}
}
2つのノードの共通の祖先を見つけるには:-
これは、バイナリ検索ツリーで機能します。
Tarjanのオフライン最小共通祖先アルゴリズム で十分です(参照 Wikipedia )。 Wikipedia には、問題(最も一般的な祖先の問題)に関する詳細があります。
私は、実例となる画像とJavaで動作するコードを試みましたが、
http://tech.bragboy.com/2010/02/least-common-ancestor-without-using.html
以下の再帰アルゴリズムは、バランスの取れたバイナリツリーのO(log N)で実行されます。 getLCA()関数に渡されたノードのいずれかがルートと同じ場合、ルートはLCAになり、再検討を実行する必要はありません。
テストケース。 [1]ノードn1とn2は両方ともツリー内にあり、親ノードの両側にあります。 [2]ノードn1またはn2がルートで、LCAがルートです。 [3]ツリーにはn1またはn2のみがあり、LCAはツリールートの左側のサブツリーのルートノード、またはLCAはツリーの右側のサブツリーのルートノードになります。ルート。
[4]ツリーにはn1もn2もありません。LCAはありません。 [5] n1とn2は両方とも隣り合った直線上にあり、LCAはツリーのルートに近いn1またはn2のいずれかになります。
//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);
}
祖先を見つける必要があるroot
とp
の両方のノードが同じサブツリーにある限り(ツリーの値が両方ともルートの値よりも小さいか、または両方が大きいことを意味する)、ツリー全体のq
から降ります。
これは、ルートから最小共通祖先までまっすぐに進み、ツリーの残りの部分を見ることはありません。そのため、取得する速度とほぼ同じです。それを行ういくつかの方法。
反復、O(1)スペース
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;
}
オーバーフローの場合、私はやります(root.val-(long)p.val)*(root.val-(long)q.val)
再帰
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);
}
ノードxの子が2 * xおよび2 * x + 1である完全な二分木である場合、より高速な方法があります
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;
}
仕組み
- バイナリ検索を使用してO(log(32))であるxおよびyを表すために必要なビットを取得します
- x&yのバイナリ表記の共通プレフィックスは共通の祖先です
- 大きい方のビットで表される方は、k >> diffによって同じビットになります
- k = x ^ yは、xとyの共通プレフィックスを消去します
- 残りの接尾辞を表すビットを見つける
- 共通の祖先である共通の接頭辞を取得するには、接尾辞ビットだけxまたはyをシフトします。
これは、両方の数が等しくなるまで、基本的に大きい数を2で再帰的に分割するためです。その番号は共通の祖先です。分割は、実質的に右シフトの操作です。したがって、最も近い祖先を見つけるには、2つの数字の共通の接頭辞を見つける必要があります
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
}
Scalaでは、コードは次のとおりです。
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
}
}
この木を考えて
ポストオーダーおよびプレオーダートラバーサルを実行し、最初に発生する共通の先行および後続を見つけると、共通の祖先を取得します。
postorder => 0,2,1,5,4,6,3,8,10,11,9,14,15,13,12,7 preorder => 7,3,1,0,2,6,4 、5、12、9、8、11、10、13、15、14
8,11の最小共通祖先
ポストオーダーでは、8および11の後に=> 9,14,15,13,12,7があります
9は、ポストオーダーで8および11の後、プレオーダーで8および11の前に現れる最初の共通番号です。したがって、9が答えです
5,10の最小共通祖先
ポストオーダーの11,9,14,15,13,12,7プレオーダーの7,3,1,0,2,6,4
7は、ポストオーダーで5,10の後、プレオーダーで5,10の前に現れる最初の数です。したがって、7が答えです
こうやって
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;
}
解決策1:再帰的-高速
- 時間の複雑さ:O(n)
- スペースの複雑さ:O(h)-再帰呼び出しスタック用
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)
}
}
解決策2:反復-親ポインターを使用する-遅い
- 時間の複雑さ:O(n)-最悪の場合、バイナリツリーのすべてのノードを訪問している可能性があります。
- スペースの複雑さ:O(n)-親ポインターHash-table、ancestor_set、およびqueueを使用するスペースは、それぞれO(n)になります。
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;
}
}
A Breadth First Searchのコードを作成して、両方のノードがツリーにあることを確認します。その後、LCA検索で前進します。改善する提案があればコメントしてください。おそらくそれらを訪問済みとしてマークし、2番目のノードの改善のために中断した特定のポイントで検索を再開できると思います(VISITTEDが見つからない場合)
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;
}
}
最も低い共通の祖先を見つける最も簡単な方法は、次のアルゴリズムを使用することです。
ルートノードの調査 value1とvalue2がルートノードの値より厳密に小さい場合 左のサブツリーの調査 else if value1とvalue2ルートノードの値よりも厳密に大きい 右サブツリーを調べます else return root
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;
}
以下は これを解決する への最速の方法です。スペースの複雑さO(1)、時間の複雑さO(n)。もし
ノードの左側と右側の両方の値がnullでない場合、そのノードが答えです(上から3番目の「if」)。値が見つかった場合は反復しますが、すべての値は一意であり、存在している必要があるため、その子孫を検索する必要はありません。見つかった一致したノードを返すだけです。ノードの左右両方のブランチの内容がnullの場合、nullは上方向に伝播します。トップレベルの再帰に達したときに、1つのブランチが値を返した場合、他のブランチはその値を伝播し続けません。 1つのnullと別のnullではないルートレベルの再帰に達した場合、null以外の値を返します。質問で値が存在すると約束されているため、見つかったノードの子ツリーに存在する必要があります。
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);
}
}
参考のために、c#(.net)(上記で両方説明)の2つのアプローチを示します。
バイナリツリーでLCAを見つける再帰バージョン(O(N)-最大で各ノードがアクセスされる)(ソリューションの主なポイントはLCAである(a)両方の要素が存在するバイナリツリーのノードのみサブツリー(左右)のどちら側もLCA (b)また、どちらのノードがどちら側に存在するかは関係ありません-最初はその情報を保持しようとしましたが、明らかに再帰関数私はそれを実現すると、非常にエレガントになりました。
両方のノードを検索し(O(N))、パスを追跡します(余分なスペースを使用します-したがって、バイナリツリーのバランスが取れていれば余分なメモリ消費がちょうどあるため、スペースはおそらく無視できると考えられても#1はおそらく優れていますO(log(N))。
そのため、パスが比較されます(受け入れられた答えと本質的に同様ですが、パスは、ポインタツリーがバイナリツリーノードに存在しないと仮定して計算されます)
完了のためだけに(questionとは無関係)、BSTのLCA(O(log(N))
テスト
再帰:
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;
}
上記のプライベート再帰バージョンは、次のパブリックメソッドによって呼び出されます:
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;
}
両方のノードのパスを追跡することによる解決策:
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;
}
FindNodeAndPathは次のように定義されています
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)-関連していません(参照用に完了のためのみ)
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;
}
単体テスト
[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);
}
}
親ノードがない場合、トラバーサルを使用したソリューションではO(n)時間の複雑さが生じることは間違いありません。
トラバーサルアプローチノードAとBのLCAを見つけていると仮定すると、最も簡単なアプローチは、最初にルートからAへのパスを取得し、次にパスを取得することですルートからBへ。これら2つのパスを取得したら、それらを簡単に反復処理して、AとBの最も低い共通の祖先である最後の共通ノードを見つけることができます。
再帰的ソリューション別のアプローチは再帰を使用することです。最初に、左ツリーと右ツリーの両方からLCAを取得できます(存在する場合)。 AまたはBのいずれかがルートノードである場合、ルートはLCAであり、再帰の終点であるルートを返すだけです。ツリーをサブツリーに分割し続けると、最終的にはAとBのいずれかがヒットします。
副問題の解決策を組み合わせるために、LCA(左のツリー)がノードを返す場合、AとBの両方が左のツリーにあり、返されたノードが最終結果であることがわかります。 LCA(左)とLCA(右)の両方が空でないノードを返す場合、AとBがそれぞれ左と右のツリーにあることを意味します。この場合、ルートノードは最も低い共通ノードです。
詳細な分析と解決策については、 最低共通祖先 を確認してください。
ここでのソリューションの一部は、ルートノードへの参照があることを前提とし、ツリーがBSTであると想定しています。 root
ノードとツリーを参照せずにハッシュマップを使用してソリューションを共有すると、BSTまたは非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
}
もう1つのアプローチがあります。ただし、回答ですでに提案されているものほど効率的ではありません。
ノードn1のパスベクトルを作成します。
ノードn2の2番目のパスベクトルを作成します。
問題のノードに到達するために通過するセットノードを意味するパスベクトル。
両方のパスベクトルを比較します。不一致のインデックスは、そのインデックスのノードを返します-1。これはLCAを提供します。
このアプローチの短所:
パスベクトルを計算するためにツリーを2回走査する必要があります。パスベクトルを保存するには、追加のO(h)スペースが必要です。
ただし、これは実装も理解も簡単です。
パスベクトルを計算するためのコード:
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;
}
粗い方法:
上記の方法の問題は、「検索」を複数回実行することです。つまり、各ノードが複数回トラバースされる可能性があります。情報を再度処理しないように記録できる場合は、この問題を克服できます(動的プログラミングを考えてください)。
したがって、すべてのノードを検索するのではなく、すでに見つかったものに関する記録を保持します。
もっと良い方法:
コード:
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;
}
}
擬似コードに興味がある人(大学の家庭用)がここにあります。
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
これはすでに回答されていますが、これはCプログラミング言語を使用したこの問題に対する私のアプローチです。コードは(insert()に関する限り)バイナリ検索ツリーを示していますが、アルゴリズムはバイナリツリーでも機能します。アイデアは、ノードAからノードBにあるすべてのノードを順序トラバーサルで調べ、ポストオーダートラバーサルでこれらのインデックスを検索することです。ポストオーダートラバーサルで最大のインデックスを持つノードは、最も低い共通の祖先です。
これは、バイナリツリーで最も低い共通の祖先を見つけるための関数を実装するためのCコードです。すべてのユーティリティ関数なども提供していますが、すぐに理解するためにCommonAncestor()にジャンプしてください。
#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);
}
これが私が思うことです、
複雑さ:ステップ1:O(n)、ステップ2 =〜O(n)、合計=〜O(n)。
解決策を見つけた
3つのトラバーサルに応じて、だれがLCAであるかを決定できます。 LCAから、両方のノードの距離を見つけます。答えであるこれらの2つの距離を追加します。
これを行うC++の方法を次に示します。アルゴリズムをできるだけ理解しやすいものにしようとしています:
// 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;
}
};
どうやって使うのですか:
LowestCommonAncestor lca;
BinaryNode_t* pNode = lca.Search(pWhateverBinaryTreeNodeToBeginWith);
if(pNode != 0)
...