2つの深さ優先検索を使用して、無向木で最長パスを見つけるためのこの標準アルゴリズムがあります。
- ランダムな頂点からDFSを開始し、そこから最も遠い頂点を見つけます。それがv ′だと言う。
- 次に、からDFSを開始して、最も遠い頂点を見つけます。このパスは、グラフ内で最も長いパスです。
問題は、これをより効率的に行えるかどうかです。単一のDFSまたはBFSでそれを行うことはできますか?
(これは、無向木の直径を計算する問題と同等に説明できます。)
2つの深さ優先検索を使用して、無向木で最長パスを見つけるためのこの標準アルゴリズムがあります。
問題は、これをより効率的に行えるかどうかです。単一のDFSまたはBFSでそれを行うことはできますか?
(これは、無向木の直径を計算する問題と同等に説明できます。)
回答:
ポストオーダーで深さ優先の検索を実行し、途中で結果を集計します。つまり、問題を再帰的に解決します。
子u 1、… 、u k(検索ツリー内)を持つすべてのノードには、2つのケースがあります。
擬似コードでは、アルゴリズムは次のようになります。
procedure longestPathLength(T : Tree) = helper(T)[2]
/* Recursive helper function that returns (h,p)
* where h is the height of T and p the length
* of the longest path of T (its diameter) */
procedure helper(T : Tree) : (int, int) = {
if ( T.children.isEmpty ) {
return (0,0)
}
else {
// Calculate heights and longest path lengths of children
recursive = T.children.map { c => helper(c) }
heights = recursive.map { p => p[1] }
paths = recursive.map { p => p[2] }
// Find the two largest subtree heights
height1 = heights.max
if (heights.length == 1) {
height2 = -1
} else {
height2 = (heights.remove(height1)).max
}
// Determine length of longest path (see above)
longest = max(paths.max, height1 + height2 + 2)
return (height1 + 1, longest)
}
}
height1 + height2
このパスの長さです。それが実際に最長のパスである場合、それはによって選択されmax
ます。上記のテキストでも説明されているので、あなたの問題はあまりわかりませんか?確かに、それが実際に最長のパスであるかどうかを確認するために再帰する必要があります。
height2
明示的にheight1
考慮から除外されるので、どうして同じ子を2回選択できますか?また、それは導入テキストで説明されています。
longestPathHeight(T)
ペアを返す(h,d)
、h
の高さT
とd
の直径ですT
。(そう?)
これはより良い方法で解決できます。また、データ構造を少し変更し、反復アプローチを使用して、時間の複雑さをO(n)に減らすことができます。詳細な分析と、さまざまなデータ構造でこの問題を解決する複数の方法について。
これが私のブログ投稿で説明したいことの要約です:
再帰的アプローチ-ツリーの直径 この問題にアプローチする別の方法は次のとおりです。上で述べたように、直径は
つまり、直径は理想的には
そして、直径が最も長い経路であることを知っているので、側面のいずれかにある場合は最大1と2を、ルートを通る場合は3を取ります。
反復アプローチ–ツリー直径
ツリーがあり、各ノードが次のことを知るために、各ノードのメタ情報が必要です。
各ノードがこの情報を取得したら、最大パスを追跡するための一時変数が必要です。アルゴリズムが終了するまでに、temp変数にdiameterの値があります。
ルートの3つの値がわからないため、この問題をボトムアップで解決する必要があります。しかし、葉のこれらの値はわかっています。
解決する手順
特定のノードで、
以下は、単一のDFSトラバーサルのみを使用して直径パスを返すコードです。ツリー内の特定のノードで始まる最長パスだけでなく、これまでに見られた最適な直径を追跡するために、余分なスペースが必要です。これは、最長直径のパスにルートが含まれないか、ルートの近隣の2つの最長パスの組み合わせであるという事実に基づいた動的プログラミングアプローチです。したがって、この情報を追跡するには2つのベクトルが必要です。
int getDiam(int root, vector<vector<int>>& adj_list, int& height, vector<int>& path, vector<int>& diam) {
visited[root] = true;
int m1 = -1;
int m2 = -1;
int max_diam = -1;
vector<int> best1 = vector<int>();
vector<int> best2 = vector<int>();
vector<int> diam_path = vector<int>();
for(auto n : adj_list[root]) {
if(!visited[n]) {
visited[n] = true;
int _height = 0;
vector<int> path1;
vector<int> path2;
int _diam = getDiam(n, adj_list, _height, path1, path2);
if(_diam > max_diam) {
max_diam = _diam;
diam_path = path2;
}
if(_height > m1) {
m2 = m1;
m1 = _height;
best2 = best1;
best1 = path1;
}
else if(_height > m2) {
m2 = _height;
best2 = path1;
}
}
}
height = m1 + 1;
path.insert( path.end(), best1.begin(), best1.end() );
path.push_back(root);
if(m1 + m2 + 2 > max_diam) {
diam = path;
std::reverse(best2.begin(), best2.end());
diam.insert( diam.end(), best2.begin(), best2.end() );
}
else{
diam = diam_path;
}
return max(m1 + m2 + 2, max_diam);
}