From def8f4937e5c878cbf7ea9cc29d219426514a5ac Mon Sep 17 00:00:00 2001 From: Filip Hlasek Date: Fri, 14 Aug 2020 09:35:11 -0700 Subject: [PATCH] Refactor lowest comomon ancestor. (#980) * Refactor lowest comomon ancestor. * Fix linter warnings. * Address comments and linter warnings. * Added Kaprekar number implementation * updating DIRECTORY.md * Added Collatz Conjecture implementation * updating DIRECTORY.md * Added Ugly Numbers implementation * updating DIRECTORY.md * Add lowest common ancestor to the graph namespace. * updating DIRECTORY.md * static tests function * Revert ugly number kaprekar and collatz. Co-authored-by: Deepak Vijay Agrawal <64848982+DebugAgrawal@users.noreply.github.com> Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> --- DIRECTORY.md | 2 +- graph/lca.cpp | 99 ------------ graph/lowest_common_ancestor.cpp | 259 +++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+), 100 deletions(-) delete mode 100644 graph/lca.cpp create mode 100644 graph/lowest_common_ancestor.cpp diff --git a/DIRECTORY.md b/DIRECTORY.md index 82787cad..76fa1154 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -84,7 +84,7 @@ * [Hamiltons Cycle](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/graph/hamiltons_cycle.cpp) * [Kosaraju](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/graph/kosaraju.cpp) * [Kruskal](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/graph/kruskal.cpp) - * [Lca](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/graph/lca.cpp) + * [Lowest Common Ancestor](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/graph/lowest_common_ancestor.cpp) * [Max Flow With Ford Fulkerson And Edmond Karp Algo](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/graph/max_flow_with_ford_fulkerson_and_edmond_karp_algo.cpp) * [Prim](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/graph/prim.cpp) * [Topological Sort](https://github.com/TheAlgorithms/C-Plus-Plus/blob/master/graph/topological_sort.cpp) diff --git a/graph/lca.cpp b/graph/lca.cpp deleted file mode 100644 index c05cf7b9..00000000 --- a/graph/lca.cpp +++ /dev/null @@ -1,99 +0,0 @@ -//#include -#include - -using namespace std; -// Find the lowest common ancestor using binary lifting in O(nlogn) -// Zero based indexing -// Resource : https://cp-algorithms.com/graph/lca_binary_lifting.html -const int N = 1005; -const int LG = log2(N) + 1; -struct lca { - int n; - vector adj[N]; // Graph - int up[LG][N]; // build this table - int level[N]; // get the levels of all of them - - lca(int n_) : n(n_) { - memset(up, -1, sizeof(up)); - memset(level, 0, sizeof(level)); - for (int i = 0; i < n - 1; ++i) { - int a, b; - cin >> a >> b; - a--; - b--; - adj[a].push_back(b); - adj[b].push_back(a); - } - level[0] = 0; - dfs(0, -1); - build(); - } - void verify() { - for (int i = 0; i < n; ++i) { - cout << i << " : level: " << level[i] << endl; - } - cout << endl; - for (int i = 0; i < LG; ++i) { - cout << "Power:" << i << ": "; - for (int j = 0; j < n; ++j) { - cout << up[i][j] << " "; - } - cout << endl; - } - } - - void build() { - for (int i = 1; i < LG; ++i) { - for (int j = 0; j < n; ++j) { - if (up[i - 1][j] != -1) { - up[i][j] = up[i - 1][up[i - 1][j]]; - } - } - } - } - - void dfs(int node, int par) { - up[0][node] = par; - for (auto i : adj[node]) { - if (i != par) { - level[i] = level[node] + 1; - dfs(i, node); - } - } - } - int query(int u, int v) { - u--; - v--; - if (level[v] > level[u]) { - swap(u, v); - } - // u is at the bottom. - int dist = level[u] - level[v]; - // Go up this much distance - for (int i = LG - 1; i >= 0; --i) { - if (dist & (1 << i)) { - u = up[i][u]; - } - } - if (u == v) { - return u; - } - assert(level[u] == level[v]); - for (int i = LG - 1; i >= 0; --i) { - if (up[i][u] != up[i][v]) { - u = up[i][u]; - v = up[i][v]; - } - } - assert(up[0][u] == up[0][v]); - return up[0][u]; - } -}; - -int main() { - int n; // number of nodes in the tree. - lca l(n); // will take the input in the format given - // n-1 edges of the form - // a b - // Use verify function to see. -} diff --git a/graph/lowest_common_ancestor.cpp b/graph/lowest_common_ancestor.cpp new file mode 100644 index 00000000..497d8318 --- /dev/null +++ b/graph/lowest_common_ancestor.cpp @@ -0,0 +1,259 @@ +/** + * + * \file + * + * \brief Data structure for finding the lowest common ancestor + * of two vertices in a rooted tree using binary lifting. + * + * \details + * Algorithm: https://cp-algorithms.com/graph/lca_binary_lifting.html + * + * Complexity: + * - Precomputation: \f$O(N \log N)\f$ where \f$N\f$ is the number of vertices in the tree + * - Query: \f$O(\log N)\f$ + * - Space: \f$O(N \log N)\f$ + * + * Example: + *
Tree: + *
+ *             _  3  _
+ *          /     |     \
+ *        1       6       4
+ *      / |     /   \       \
+ *    7   5   2       8       0
+ *            |
+ *            9
+ * 
+ * + *
lowest_common_ancestor(7, 4) = 3 + *
lowest_common_ancestor(9, 6) = 6 + *
lowest_common_ancestor(0, 0) = 0 + *
lowest_common_ancestor(8, 2) = 6 + * + * The query is symmetrical, therefore + * lowest_common_ancestor(x, y) = lowest_common_ancestor(y, x) + */ + +#include +#include +#include +#include +#include + +/** + * \namespace graph + * \brief Graph algorithms + */ +namespace graph { +/** + * Class for representing a graph as an adjacency list. + * Its vertices are indexed 0, 1, ..., N - 1. + */ +class Graph { + public: + /** + * \brief Populate the adjacency list for each vertex in the graph. + * Assumes that evey edge is a pair of valid vertex indices. + * + * @param N number of vertices in the graph + * @param undirected_edges list of graph's undirected edges + */ + Graph(size_t N, const std::vector< std::pair > &undirected_edges) { + neighbors.resize(N); + for (auto &edge : undirected_edges) { + neighbors[edge.first].push_back(edge.second); + neighbors[edge.second].push_back(edge.first); + } + } + + /** + * Function to get the number of vertices in the graph + * @return the number of vertices in the graph. + */ + int number_of_vertices() const { + return neighbors.size(); + } + + /** \brief for each vertex it stores a list indicies of its neighbors */ + std::vector< std::vector > neighbors; +}; + +/** + * Representation of a rooted tree. For every vertex its parent is precalculated. + */ +class RootedTree : public Graph { + public: + /** + * \brief Constructs the tree by calculating parent for every vertex. + * Assumes a valid description of a tree is provided. + * + * @param undirected_edges list of graph's undirected edges + * @param root_ index of the root vertex + */ + RootedTree(const std::vector< std::pair > &undirected_edges, int root_) + : Graph(undirected_edges.size() + 1, undirected_edges), root(root_) { + populate_parents(); + } + + /** + * \brief Stores parent of every vertex and for root its own index. + * The root is technically not its own parent, but it's very practical + * for the lowest common ancestor algorithm. + */ + std::vector parent; + /** \brief Stores the distance from the root. */ + std::vector level; + /** \brief Index of the root vertex. */ + int root; + + protected: + /** + * \brief Calculate the parents for all the vertices in the tree. + * Implements the breadth first search algorithm starting from the root + * vertex searching the entire tree and labeling parents for all vertices. + * @returns none + */ + void populate_parents() { + // Initialize the vector with -1 which indicates the vertex + // wasn't yet visited. + parent = std::vector(number_of_vertices(), -1); + level = std::vector(number_of_vertices()); + parent[root] = root; + level[root] = 0; + std::queue queue_of_vertices; + queue_of_vertices.push(root); + while (!queue_of_vertices.empty()) { + int vertex = queue_of_vertices.front(); + queue_of_vertices.pop(); + for (int neighbor : neighbors[vertex]) { + // As long as the vertex was not yet visited. + if (parent[neighbor] == -1) { + parent[neighbor] = vertex; + level[neighbor] = level[vertex] + 1; + queue_of_vertices.push(neighbor); + } + } + } + } + +}; + +/** + * A structure that holds a rooted tree and allow for effecient + * queries of the lowest common ancestor of two given vertices in the tree. + */ +class LowestCommonAncestor { + public: + /** + * \brief Stores the tree and precomputs "up lifts". + * @param tree_ rooted tree. + */ + explicit LowestCommonAncestor(const RootedTree& tree_) : tree(tree_) { + populate_up(); + } + + /** + * \brief Query the structure to find the lowest common ancestor. + * Assumes that the provided numbers are valid indices of vertices. + * Iterativelly modifies ("lifts") u an v until it finnds their lowest + * common ancestor. + * @param u index of one of the queried vertex + * @param v index of the other queried vertex + * @return index of the vertex which is the lowet common ancestor of u and v + */ + int lowest_common_ancestor(int u, int v) const { + // Ensure u is the deeper (higher level) of the two vertices + if (tree.level[v] > tree.level[u]) { + std::swap(u, v); + } + + // "Lift" u to the same level as v. + int level_diff = tree.level[u] - tree.level[v]; + for (int i = 0; (1 << i) <= level_diff; ++i) { + if (level_diff & (1 << i)) { + u = up[u][i]; + } + } + assert(tree.level[u] == tree.level[v]); + + if (u == v) { + return u; + } + + // "Lift" u and v to their 2^i th ancestor if they are different + for (int i = static_cast(up[u].size()) - 1; i >= 0; --i) { + if (up[u][i] != up[v][i]) { + u = up[u][i]; + v = up[v][i]; + } + } + + // As we regressed u an v such that they cannot further be lifted so + // that their ancestor would be different, the only logical + // consequence is that their parent is the sought answer. + assert(up[u][0] == up[v][0]); + return up[u][0]; + } + + /* \brief reference to the rooted tree this structure allows to query */ + const RootedTree& tree; + /** + * \brief for every vertex stores a list of its ancestors by powers of two + * For each vertex, the first element of the corresponding list contains + * the index of its parent. The i-th element of the list is an index of + * the (2^i)-th ancestor of the vertex. + */ + std::vector< std::vector > up; + + protected: + /** + * Populate the "up" structure. See above. + */ + void populate_up() { + up.resize(tree.number_of_vertices()); + for (int vertex = 0; vertex < tree.number_of_vertices(); ++vertex) { + up[vertex].push_back(tree.parent[vertex]); + } + for (int level = 0; (1 << level) < tree.number_of_vertices(); ++level) { + for (int vertex = 0; vertex < tree.number_of_vertices(); ++vertex) { + // up[vertex][level + 1] = 2^(level + 1) th ancestor of vertex = + // = 2^level th ancestor of 2^level th ancestor of vertex = + // = 2^level th ancestor of up[vertex][level] + up[vertex].push_back(up[up[vertex][level]][level]); + } + } + } +}; + +} // namespace graph + +/** + * Unit tests + * @rerturns none + */ +static void tests() { + /** + * _ 3 _ + * / | \ + * 1 6 4 + * / | / \ \ + * 7 5 2 8 0 + * | + * 9 + */ + std::vector< std::pair > edges = { + {7, 1}, {1, 5}, {1, 3}, {3, 6}, {6, 2}, {2, 9}, {6, 8}, {4, 3}, {0, 4} + }; + graph::RootedTree t(edges, 3); + graph::LowestCommonAncestor lca(t); + assert(lca.lowest_common_ancestor(7, 4) == 3); + assert(lca.lowest_common_ancestor(9, 6) == 6); + assert(lca.lowest_common_ancestor(0, 0) == 0); + assert(lca.lowest_common_ancestor(8, 2) == 6); +} + +/** Main function */ +int main() { + tests(); + return 0; +} -- GitLab