- (u -> w) + (v -> w): 這樣的兩條路徑各自求解後合併
LCA 的找法可以參考演算法筆記中的 Jump Pointer Algorithm,他的基本概念就是對每個 node 的前 1、2、4、8 ... (2^n) 倍的祖先,如此一來,假設要知道一個 node 的前 5 代祖先是誰,只要知道他的前一代祖先的前四代祖先是誰就可以了。(簡單來說,看成用二進位表示法就知道這種儲存方式可以得知任意一個點的任意一代的祖先囉)
透過這種方法,對任意 2 點 u, v,我們可以知道最後的共同的祖先必然是 root,從上往下追蹤直到出現分歧為止,這就是最近的共同祖先拉。
最後的實作如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* ===================================================================================== | |
* | |
* Filename: LCA.cpp | |
* | |
* Description: Find the lowest common ancestor (LCA) of two nodes in a tree | |
* | |
* Version: 1.0 | |
* Created: 2018/02/24 (yyyy/mm/dd) | |
* Revision: none | |
* Compiler: g++ (C++ 14) | |
* | |
* Author: lionking | |
* Organization: None | |
* | |
* ===================================================================================== | |
*/ | |
#include <cstdio> | |
#include <cstdlib> | |
#include <vector> | |
#include <iterator> | |
#include <algorithm> | |
#include <limits> | |
/************************************************************************************** | |
* This class will use dynamic programming to store info for querying LCA | |
* The time complexity for construction step and query will | |
* require O( |V| * log |V| ) and O( log |V| ), respectively | |
* where |V| is the number of vertices in a tree. | |
* | |
* Note: | |
* 1. The given tree should be stored in adjacecy list. | |
* 2. Construction step should be called prior to the query step. | |
* 3. Once the given tree is modiefied, it should be re-construct before next query. | |
**************************************************************************************/ | |
class LCA | |
{ | |
public: | |
// Construction Step: | |
// it will construct necessary tables for querying LCA. | |
// Return false when the construction step fails. | |
// | |
// Parameters: | |
// 1. first/last: iterators for iterating tree nodes within [first, last) | |
// 2. root: index of tree root | |
// | |
// Note: | |
// 1. Every tree node should provide the following two iterators for iterating through its neighbors | |
// a. Iterator begin(); | Iterator cbeing(); for const qualification | |
// b. Iterator end(); | Iterator cend(); for const qualification | |
// This is because the construction step uses range-based for to iterate neighboring nodes | |
template <typename RandomAccessIterator> | |
bool construct(RandomAccessIterator first, RandomAccessIterator last, size_t root); | |
// Return the index of LCA(u, v) | |
// The given two arguments are the indices of the tree nodes. | |
// Return value is std::numeric_limits<size_t> when the query fails | |
size_t query(size_t u, size_t v); | |
void clear(); | |
private: | |
std::vector< std::vector<size_t> > lca; | |
std::vector<int> depth; | |
// initialization step for construction | |
void init(size_t root, size_t node_count); | |
// DFS for tree traversal during construction | |
template <typename RandomAccessIterator> | |
void dfs(RandomAccessIterator first, size_t curr, size_t prev, int d = 0); | |
}; | |
void LCA::clear() | |
{ | |
lca.clear(); | |
depth.clear(); | |
} | |
void LCA::init(size_t root, size_t node_count) | |
{ | |
this->clear(); | |
// initialization steps | |
this->depth.resize(node_count); | |
size_t log2 = 1; | |
for(; (1u << log2) <= node_count; ++log2) ; | |
this->lca.resize(log2); | |
for (auto& member : lca) { | |
member.resize(node_count, 0); | |
} | |
} | |
template <typename RandomAccessIterator> | |
void LCA::dfs(RandomAccessIterator first, size_t curr, size_t prev, int d) | |
{ | |
lca[0][curr] = prev; | |
depth[curr] = d; | |
auto& node = *(first + curr); | |
for (const auto& next : node) { | |
if (next != prev) { | |
dfs(first, next, curr, d + 1); | |
} | |
} | |
} | |
template <typename RandomAccessIterator> | |
bool LCA::construct(RandomAccessIterator first, RandomAccessIterator last, size_t root) | |
{ | |
const size_t node_count = std::distance(first, last); | |
if (node_count == 0) { return false; } | |
this->init(root, node_count); | |
// The previous node of tree root is root itself | |
// Apply DFS to traverse the tree | |
dfs(first, root, root); | |
// Use DP to construct LCA table | |
// lca[i][j]: the (2^i)-th ancestor of node j | |
for (size_t i = 1; i < lca.size(); ++i) { | |
for (size_t j = 0; j < node_count; ++j) { | |
lca[i][j] = lca[i-1][ lca[i-1][j] ]; | |
} | |
} | |
return true; | |
} | |
size_t LCA::query(size_t u, size_t v) | |
{ | |
if ((u >= depth.size()) || (v >= depth.size())) { return std::numeric_limits<size_t>::max(); } | |
// make u be the node lower than v | |
if (depth[u] < depth[v]) { std::swap(u, v); } | |
// Then, find the ancestor of u, say w, such that depth[w] = depth[v]. | |
// This is because the lowest possible level of LCA(u, v) = depth[v] | |
// Let u be w before we find the LCA(u, v) because LCA(u, v) = LCA(w, v). | |
const auto diff = depth[u] - depth[v]; | |
for (size_t i = 0; i < lca.size(); ++i) { | |
if ((diff >> i) & 1) { | |
u = lca[i][u]; | |
} | |
} | |
// one possibility: v is ancestor of u; then LCA(u, v) = v. | |
if (u == v) { return v; } | |
// another posibility: | |
for (int i = lca.size() - 1; i >= 0; --i) { | |
if (lca[i][u] != lca[i][v]) { | |
u = lca[i][u]; | |
v = lca[i][v]; | |
} | |
} | |
return lca[0][u]; | |
} | |
int main(int argc, char *argv[]) | |
{ | |
LCA lca; | |
std::vector< std::vector<size_t> > tree(11); | |
tree[0].push_back(1); | |
tree[0].push_back(2); | |
tree[1].push_back(0); | |
tree[1].push_back(3); | |
tree[1].push_back(4); | |
tree[2].push_back(0); | |
tree[2].push_back(5); | |
tree[2].push_back(6); | |
tree[3].push_back(1); | |
tree[3].push_back(7); | |
tree[3].push_back(8); | |
tree[4].push_back(1); | |
tree[4].push_back(9); | |
tree[4].push_back(10); | |
lca.construct(tree.cbegin(), tree.cend(), 0); | |
int u, v; | |
while (scanf("%d %d", &u, &v) == 2) { | |
if ((u < 0) || (v < 0)) { break; } | |
printf("LCA(%d, %d) = %lu\n", u, v, lca.query(u, v)); | |
} | |
return 0; | |
} |
沒有留言:
張貼留言