C++で二分木の2つのノード間の距離を求める方法
問題の概要
いくつかのノードを持つ二分木が与えられているとします。このとき、2つのノード u と v の間の「距離」、つまり一方のノードからもう一方のノードへ移動する際に通る辺(エッジ)の本数を求めることを考えます。
例として、次のような二分木を扱います。
1
/ \
2 3
/ \ / \
4 5 6 7
\
8この木において、ノード (4, 6) 間の距離は 4(経路:4 → 2 → 1 → 3 → 6)、ノード (5, 8) 間の距離は 5(経路:5 → 2 → 1 → 3 → 6 → 8)となります。
解決のアプローチ:LCA(最小共通祖先)を利用する
2つのノード間の距離を効率的に求めるには、LCA(Lowest Common Ancestor:最小共通祖先)を利用するのが定石です。LCAとは、2つのノード両方の子孫となるノードの中で、最も根に近いノードのことです。
2つのノード間の距離は、次の式で表せます。
距離(u, v) = 距離(LCA, u) + 距離(LCA, v)
つまり、以下の手順で解くことができます。
- 2つのノード u と v のLCAを求める。
- LCAからノード u への距離を計算する。
- LCAからノード v への距離を計算する。
- 2つの距離を合計して答えとする。
C++での実装例
それでは、実際のコードを見てみましょう。
#include<iostream>
using namespace std;
class Node {
public:
int data;
Node *left, *right;
};
// 新しいノードを生成する補助関数
Node* getNode(int data) {
Node* node = new Node;
node->data = data;
node->left = node->right = NULL;
return node;
}
// 2つのノード n1, n2 の最小共通祖先(LCA)を再帰的に求める
Node* LowestCommonAncestor(Node* root, int n1, int n2) {
if (root == NULL)
return root;
// 現在のノードがいずれかの対象ノードなら、それがLCA候補
if (root->data == n1 || root->data == n2)
return root;
Node* left = LowestCommonAncestor(root->left, n1, n2);
Node* right = LowestCommonAncestor(root->right, n1, n2);
// 左右両方からNULL以外が返れば、現在のノードがLCA
if (left != NULL && right != NULL)
return root;
if (left != NULL)
return left;
return right;
}
// ノード k の、root からの深さ(レベル)を求める
int getLevel(Node* root, int k, int level) {
if (root == NULL) return -1;
if (root->data == k) return level;
int left = getLevel(root->left, k, level + 1);
if (left == -1)
return getLevel(root->right, k, level + 1);
return left;
}
// 2つのノード a, b 間の距離を求める
int findDistance(Node* root, int a, int b) {
Node* lca = LowestCommonAncestor(root, a, b);
int dist1 = getLevel(lca, a, 0);
int dist2 = getLevel(lca, b, 0);
return dist1 + dist2;
}
int main() {
Node* root = getNode(1);
root->left = getNode(2);
root->right = getNode(3);
root->left->left = getNode(4);
root->left->right = getNode(5);
root->right->left = getNode(6);
root->right->right = getNode(7);
root->right->left->right = getNode(8);
cout << "Distance between (4, 6) is: " << findDistance(root, 4, 6);
cout << "\nDistance between (8, 5) is: " << findDistance(root, 8, 5);
return 0;
}実行結果
Distance between (4, 6) is: 4 Distance between (8, 5) is: 5
コードの解説
1. LowestCommonAncestor 関数
木を再帰的にたどりながら、対象となる2つの値 n1、n2 を探索します。左右の部分木のそれぞれから結果が返ってきた場合、そのノードが2つのノードを分岐させる共通の祖先であるため、そのノードをLCAとして返します。
2. getLevel 関数
指定したノード k が、基準ノード(ここではLCA)から何レベル下にあるかを再帰的に調べます。見つからない場合は -1 を返し、左部分木で見つからなければ右部分木を探索します。これにより、LCAから各ノードへの距離(辺の本数)が得られます。
3. findDistance 関数
LCAを求め、そこからノード a および b までの距離をそれぞれ計算し、その合計を返します。これが2つのノード間の距離になります。
計算量について
- 時間計算量: O(n)。LCAの探索とレベルの計算は、それぞれ最悪の場合で木の全ノードを訪問するためです。
- 空間計算量: O(h)。再帰呼び出しによるスタック領域が、木の高さ h に依存します(最悪の場合、木が偏っていると O(n) になります)。
まとめ
二分木における2つのノード間の距離は、「最小共通祖先(LCA)を求め、LCAからそれぞれのノードへの距離を足し合わせる」というシンプルな方針で効率よく計算できます。木構造の問題ではLCAが応用される場面が非常に多いため、この実装パターンはぜひ覚えておきましょう。
-
【C++】二分木内の任意の2つのノード間のパスを出力する方法
はじめに 本記事では、C++プログラミングにおいて二分木(バイナリツリー)内の任意の2つのノード間のパス(経路)を出力する方法を解説します。 前提として、すべてのノードが互いに異なる値を持つ二分木が与えられ、その中から指定した2つのノードをつなぐ経路を出力することを目標とします。 例として、次のような二分木を考えます。 具体例: ノード140からノード211までの経路を出力したい場合、期待される出力は以下の通りです。 Output: 140->3->10->211 解決のアプローチ 基本的なアイデアは、「ルートノードから目的の2つのノードそれぞれへの経路」を求め、それらを
-
Pythonで二分木の2つのノード間の距離を求めるプログラム
二分木が与えられたとき、その中の2つのノード間の距離を求めることを考えます。グラフの場合と同じように、2つのノードを結ぶ経路上の辺(エッジ)の数を数え、その本数を距離として返します。 二分木のノード構造 木の各ノードは、次のような構造を持っています。 data : <整数値> right : <木の別のノードへのポインタ> left : <木の別のノードへのポインタ> 問題の例 例として、次のような二分木を考えてみましょう。 この木において、ノード「2」とノード「8」の間の距離を求めたいとします。このときの出力は 4 になります。 ノード2からノード8へ至