diff --git a/data_structure/trie_modern.cpp b/data_structure/trie_modern.cpp new file mode 100644 index 000000000..218c90b68 --- /dev/null +++ b/data_structure/trie_modern.cpp @@ -0,0 +1,167 @@ +/** + * Copyright 2020 @author Anmol3299 + * @file + * + * A basic implementation of trie class to store only lower-case strings. + */ +#include // for io operations +#include // for std::shared_ptr<> +#include // for std::string class + +/** + * A basic implementation of trie class to store only lower-case strings. + * You can extend the implementation to all the ASCII characters by changing the + * value of @ ALPHABETS to 128. + */ +class Trie { + private: + static constexpr size_t ALPHABETS = 26; + + /** + * Structure of trie node. + * This struct doesn't need a constructor as we are initializing using + * intializer list which is more efficient than if we had done so with + * constructor. + */ + struct TrieNode { + // An array of pointers of size 26 which tells if a character of word is + // present or not. + std::shared_ptr character[ALPHABETS]{nullptr}; + + bool isEndOfWord{false}; + }; + + /** + * Function to check if a node has some children which can form words. + * @param node whose character array of pointers need to be checked for + * children. + * @return if a child is found, it returns @ true, else it returns @ false. + */ + inline static bool hasChildren(std::shared_ptr node) { + for (size_t i = 0; i < ALPHABETS; i++) { + if (node->character[i]) { + return true; + } + } + return false; + } + + /** + * A recursive helper function to remove a word from the trie. First, it + * recursively traverses to the location of last character of word in the + * trie. However, if the word is not found, the function returns a runtime + * error. Upon successfully reaching the last character of word in trie, if + * sets the isEndOfWord to false and deletes the node if and only if it has + * no children, else it returns the current node. + * @param word is the string which needs to be removed from trie. + * @param curr is the current node we are at. + * @param index is the index of the @word we are at. + * @return if current node has childern, it returns @ curr, else it returns + * nullptr. + * @throw a runtime error in case @ word is not found in the trie. + */ + std::shared_ptr removeWordHelper(const std::string& word, + std::shared_ptr curr, + size_t index) { + if (word.size() == index) { + if (curr->isEndOfWord) { + curr->isEndOfWord = false; + } + if (hasChildren(curr)) { + return curr; + } + return nullptr; + } + + size_t idx = word[index] - 'a'; + + // Throw a runtime error in case the user enters a word which is not + // present in the trie. + if (!curr->character[idx]) { + throw std::runtime_error(std::move(std::string("Word not found."))); + } + + curr->character[idx] = + removeWordHelper(word, curr->character[idx], index + 1); + + // This if condition checks if the node has some childern. + // The 1st if check, i.e. (curr->character[idx]) is checked specifically + // because if the older string is a prefix of some other string, then, + // there would be no need to check all 26 characters. Example- str1 = + // abbey, str2 = abbex and we want to delete string "abbey", then in + // this case, there would be no need to check all characters for the + // chars a,b,b. + if (curr->character[idx] || hasChildren(curr)) { + return curr; + } + return nullptr; + } + + public: + // constructor to initialise the root of the trie. + Trie() : m_root(std::make_shared()) {} + + /** + * Insert a word into the trie. + * @param word which needs to be inserted into the string. + */ + void insert(const std::string& word) { + auto curr = m_root; + for (char ch : word) { + size_t index = ch - 'a'; + + // if a node for current word is not already present in trie, create + // a new node for it. + if (!curr->character[index]) { + curr->character[index] = std::make_shared(); + } + + curr = curr->character[index]; + } + curr->isEndOfWord = true; + } + + /** + * Search if a word is present in trie or not. + * @param word which is needed to be searched in the trie. + * @return True if the word is found in trie and isEndOfWord is set to true. + * @return False if word is not found in trie or isEndOfWord is set to + * false. + */ + bool search(const std::string& word) { + auto curr = m_root; + for (char ch : word) { + size_t index = ch - 'a'; + + // if any node for a character is not found, then return that the + // word cannot be formed. + if (!curr->character[index]) { + return false; + } + curr = curr->character[index]; + } + return curr->isEndOfWord; + } + + // Function to remove the word which calls the helper function. + void removeWord(const std::string& word) { + m_root = removeWordHelper(word, m_root, 0); + } + + private: + // data member to store the root of the trie. + std::shared_ptr m_root; +}; + +/** + * Main function + */ +int main() { + Trie trie; + trie.insert("hel"); + trie.insert("hello"); + trie.removeWord("hel"); + std::cout << trie.search("hello") << '\n'; + + return 0; +} diff --git a/graph/cycle_check_directed_graph.cpp b/graph/cycle_check_directed_graph.cpp new file mode 100644 index 000000000..0f7b84cd3 --- /dev/null +++ b/graph/cycle_check_directed_graph.cpp @@ -0,0 +1,302 @@ +/** + * Copyright 2020 + * @file cycle_check_directed graph.cpp + * + * @brief BFS and DFS algorithms to check for cycle in a directed graph. + * + * @author Anmol3299 + * contact: mittalanmol22@gmail.com + * + */ + +#include // for std::cout +#include // for std::queue +#include // for throwing errors +#include // for std::remove_reference_t +#include // for std::unordered_map +#include // for std::move +#include // for std::vector + +/** + * Implementation of non-weighted directed edge of a graph. + * + * The source vertex of the edge is labelled "src" and destination vertex is + * labelled "dest". + */ +struct Edge { + unsigned int src; + unsigned int dest; + + Edge() = delete; + ~Edge() = default; + Edge(Edge&&) = default; + Edge& operator=(Edge&&) = default; + Edge(Edge const&) = default; + Edge& operator=(Edge const&) = default; + + /** Set the source and destination of the vertex. + * + * @param source is the source vertex of the edge. + * @param destination is the destination vertex of the edge. + */ + Edge(unsigned int source, unsigned int destination) + : src(source), dest(destination) {} +}; + +using AdjList = std::unordered_map>; + +/** + * Implementation of graph class. + * + * The graph will be represented using Adjacency List representation. + * This class contains 2 data members "m_vertices" & "m_adjList" used to + * represent the number of vertices and adjacency list of the graph + * respectively. The vertices are labelled 0 - (m_vertices - 1). + */ +class Graph { + public: + Graph() : m_vertices(0), m_adjList({}) {} + ~Graph() = default; + Graph(Graph&&) = default; + Graph& operator=(Graph&&) = default; + Graph(Graph const&) = default; + Graph& operator=(Graph const&) = default; + + /** Create a graph from vertices and adjacency list. + * + * @param vertices specify the number of vertices the graph would contain. + * @param adjList is the adjacency list representation of graph. + */ + Graph(unsigned int vertices, AdjList const& adjList) + : m_vertices(vertices), m_adjList(adjList) {} + + /** Create a graph from vertices and adjacency list. + * + * @param vertices specify the number of vertices the graph would contain. + * @param adjList is the adjacency list representation of graph. + */ + Graph(unsigned int vertices, AdjList&& adjList) + : m_vertices(std::move(vertices)), m_adjList(std::move(adjList)) {} + + /** Create a graph from vertices and a set of edges. + * + * Adjacency list of the graph would be created from the set of edges. If + * the source or destination of any edge has a value greater or equal to + * number of vertices, then it would throw a range_error. + * + * @param vertices specify the number of vertices the graph would contain. + * @param edges is a vector of edges. + */ + Graph(unsigned int vertices, std::vector const& edges) + : m_vertices(vertices) { + for (auto const& edge : edges) { + if (edge.src >= vertices || edge.dest >= vertices) { + throw std::range_error( + "Either src or dest of edge out of range"); + } + m_adjList[edge.src].emplace_back(edge.dest); + } + } + + /** Return a const reference of the adjacency list. + * + * @return const reference to the adjacency list + */ + std::remove_reference_t const& getAdjList() const { + return m_adjList; + } + + /** + * @return number of vertices in the graph. + */ + std::remove_reference_t const& getVertices() const { + return m_vertices; + } + + /** Add vertices in the graph. + * + * @param num is the number of vertices to be added. It adds 1 vertex by + * default. + * + */ + void addVertices(unsigned int num = 1) { m_vertices += num; } + + /** Add an edge in the graph. + * + * @param edge that needs to be added. + */ + void addEdge(Edge const& edge) { + if (edge.src >= m_vertices || edge.dest >= m_vertices) { + throw std::range_error("Either src or dest of edge out of range"); + } + m_adjList[edge.src].emplace_back(edge.dest); + } + + /** Add an Edge in the graph + * + * @param source is source vertex of the edge. + * @param destination is the destination vertex of the edge. + */ + void addEdge(unsigned int source, unsigned int destination) { + if (source >= m_vertices || destination >= m_vertices) { + throw std::range_error( + "Either source or destination of edge out of range"); + } + m_adjList[source].emplace_back(destination); + } + + private: + unsigned int m_vertices; + AdjList m_adjList; +}; + +class CycleCheck { + private: + enum nodeStates : uint8_t { not_visited = 0, in_stack, visited }; + + /** Helper function of "isCyclicDFS". + * + * @param adjList is the adjacency list representation of some graph. + * @param state is the state of the nodes of the graph. + * @param node is the node being evaluated. + * + * @return true if graph has a cycle, else false. + */ + static bool isCyclicDFSHelper(AdjList const& adjList, + std::vector* state, + unsigned int node) { + // Add node "in_stack" state. + (*state)[node] = in_stack; + + // If the node has children, then recursively visit all children of the + // node. + if (auto const& it = adjList.find(node); it != adjList.end()) { + for (auto child : it->second) { + // If state of child node is "not_visited", evaluate that child + // for presence of cycle. + if (auto state_of_child = (*state)[child]; + state_of_child == not_visited) { + if (isCyclicDFSHelper(adjList, state, child)) { + return true; + } + } else if (state_of_child == in_stack) { + // If child node was "in_stack", then that means that there + // is a cycle in the graph. Return true for presence of the + // cycle. + return true; + } + } + } + + // Current node has been evaluated for the presence of cycle and had no + // cycle. Mark current node as "visited". + (*state)[node] = visited; + // Return that current node didn't result in any cycles. + return false; + } + + public: + /** Driver function to check if a graph has a cycle. + * + * This function uses DFS to check for cycle in the graph. + * + * @param graph which needs to be evaluated for the presence of cycle. + * @return true if a cycle is detected, else false. + */ + static bool isCyclicDFS(Graph const& graph) { + /** State of the node. + * + * It is a vector of "nodeStates" which represents the state node is in. + * It can take only 3 values: "not_visited", "in_stack", and "visited". + * + * Initially, all nodes are in "not_visited" state. + */ + std::vector state(graph.getVertices(), not_visited); + + // Start visiting each node. + for (auto node = 0; node < graph.getVertices(); node++) { + // If a node is not visited, only then check for presence of cycle. + // There is no need to check for presence of cycle for a visited + // node as it has already been checked for presence of cycle. + if (state[node] == not_visited) { + // Check for cycle. + if (isCyclicDFSHelper(graph.getAdjList(), &state, node)) { + return true; + } + } + } + + // All nodes have been safely traversed, that means there is no cycle in + // the graph. Return false. + return false; + } + + /** Check if a graph has cycle or not. + * + * This function uses BFS to check if a graph is cyclic or not. + * + * @param graph which needs to be evaluated for the presence of cycle. + * @return true if a cycle is detected, else false. + */ + static bool isCyclicBFS(Graph const& graph) { + AdjList graphAjdList = graph.getAdjList(); + + std::vector indegree(graph.getVertices(), 0); + // Calculate the indegree i.e. the number of incident edges to the node. + for (auto const& [parent, children] : graphAjdList) { + for (auto const& child : children) { + indegree[child]++; + } + } + + std::queue can_be_solved; + for (auto node = 0; node < graph.getVertices(); node++) { + // If a node doesn't have any input edges, then that node will + // definately not result in a cycle and can be visited safely. + if (!indegree[node]) { + can_be_solved.emplace(node); + } + } + + // Vertices that need to be traversed. + auto remain = graph.getVertices(); + // While there are safe nodes that we can visit. + while (!can_be_solved.empty()) { + auto front = can_be_solved.front(); + // Visit the node. + can_be_solved.pop(); + // Decrease number of nodes that need to be traversed. + remain--; + + // Visit all the children of the visited node. + if (auto it = graphAjdList.find(front); it != graphAjdList.end()) { + for (auto child : it->second) { + // Check if we can visited the node safely. + if (--indegree[child] == 0) { + // if node can be visited safely, then add that node to + // the visit queue. + can_be_solved.emplace(child); + } + } + } + } + + // If there are still nodes that we can't visit, then it means that + // there is a cycle and return true, else return false. + return !(remain == 0); + } +}; + +/** + * Main function. + */ +int main() { + // Instantiate the graph. + Graph g(7, std::vector{{0, 1}, {1, 2}, {2, 0}, {2, 5}, {3, 5}}); + // Check for cycle using BFS method. + std::cout << CycleCheck::isCyclicBFS(g) << '\n'; + + // Check for cycle using DFS method. + std::cout << CycleCheck::isCyclicDFS(g) << '\n'; + return 0; +}