├── assets └── images │ └── cover.png ├── Adjacency List as my Graph representation.py ├── How do you traverse a graph ├── Breadth-First-Search (BFS).py ├── Depth-First-Search (DFS).py └── DFS Code implementation, Recursion.py ├── Undirected Graph and the Visited Pattern ├── Shortest Path │ └── BFS Iterative Solution - Shortest Path.py ├── Count Components │ ├── DFS Iterative Solution - Count Components.py │ ├── DFS Recursive Solution - Count Components.py │ └── BFS Solution - Count Components.py ├── Largest Component │ ├── DFS Iterative Solution - Largest Component.py │ ├── BFS Solution - Largest Component.py │ ├── DFS Recursive Solution - Largest Component - Variation #2.py │ └── DFS Recursive Solution - Largest Component - Variation #1.py └── Find path Between node A and B in a undirected Graph │ ├── DFS Recursive Solution - Has Path - Undirected Graph - Variation # 2.py │ ├── BFS Solution - Has Path - Undirected Graph.py │ ├── DFS Recursive Solution - Has Path - Undirected Graph - Variation # 1.py │ └── DFS Iterative Solution - Has Path - Undirected Graph.py ├── Has path ├── DFS Recursive Solution.py ├── BFS Iterative Solution.py └── DFS Iterative Solution.py ├── LICENSE ├── Problems with islands ├── Largest Island │ ├── DFS Recursive Solution - Largest Island.py │ └── BFS Iterative Solution - Largest Island.py └── Count the islands │ ├── DFS Recursive Solution - Count the islands.py │ └── BFS Iterative Solution - Count the islands.py ├── Graph with cycles └── Has a cycle solution.py └── README.md /assets/images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/francofgp/The-Definite-Guide-to-Graph-Problems/HEAD/assets/images/cover.png -------------------------------------------------------------------------------- /Adjacency List as my Graph representation.py: -------------------------------------------------------------------------------- 1 | edges = [ 2 | ["1", "2"], 3 | ["2", "3"], 4 | ["5", "3"], 5 | ["5", "4"], 6 | ["1", "4"] 7 | ] 8 | def create_graph(edges): 9 | graph = {} 10 | for edge in edges: 11 | node_a, node_b = edge 12 | if node_a not in graph: 13 | graph[node_a] = [] 14 | if node_b not in graph: 15 | graph[node_b] = [] 16 | graph[node_a].append(node_b) 17 | graph[node_b].append(node_a) 18 | return graph 19 | 20 | print(create_graph(edges)) -------------------------------------------------------------------------------- /How do you traverse a graph/Breadth-First-Search (BFS).py: -------------------------------------------------------------------------------- 1 | def breadth_first_search(graph,starting_node): 2 | queue = [starting_node] # Declare a queue and insert the starting node. 3 | while len(queue) > 0: # While the queue is not empty you remove the first node of the queue. 4 | current_node = queue.pop(0) 5 | print(current_node) 6 | for neighbor in graph[current_node]: 7 | queue.append(neighbor) # Insert all the neighbors of the node into the queue. 8 | 9 | graph = { 10 | 1: [2, 3], 11 | 2: [4], 12 | 3: [5, 6], 13 | 4: [], 14 | 5: [], 15 | 6: [], 16 | } 17 | breadth_first_search(graph, 1) 18 | # 1 2 3 4 5 6 -------------------------------------------------------------------------------- /How do you traverse a graph/Depth-First-Search (DFS).py: -------------------------------------------------------------------------------- 1 | def depth_first_search(graph, starting_node): 2 | queue = [starting_node] # Declare a stack and insert the starting node. 3 | # While the stack is not empty you remove the last node of the stack. 4 | while len(queue) > 0: 5 | current_node = queue.pop() 6 | print(current_node) 7 | for neighbor in graph[current_node]: 8 | # Insert all the neighbors of the node into the queue. 9 | queue.append(neighbor) 10 | 11 | 12 | graph = { 13 | 1: [2, 3], 14 | 2: [4], 15 | 3: [5, 6], 16 | 4: [], 17 | 5: [], 18 | 6: [], 19 | } 20 | depth_first_search(graph, 1) 21 | # 1 3 6 5 2 4 # It does not matter if you start with the right neighbor or with the left one 22 | -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Shortest Path/BFS Iterative Solution - Shortest Path.py: -------------------------------------------------------------------------------- 1 | def shortest_path(graph, source, destination): 2 | visited = set() 3 | queue = [(source, 0)] 4 | 5 | while len(queue) > 0: 6 | current_node, distance = queue.pop(0) 7 | if current_node == destination: 8 | return distance 9 | if current_node in visited: 10 | continue 11 | visited.add(current_node) 12 | 13 | for neighbor in graph[current_node]: 14 | queue.append((neighbor, distance + 1)) 15 | 16 | return -1 17 | 18 | graph = { 19 | 1: [2, 3], 20 | 2: [1, 4], 21 | 3: [1, 5, 6], 22 | 4: [2, 5], 23 | 5: [3, 4, 6], 24 | 6: [3, 5], 25 | } 26 | 27 | print(shortest_path(graph, 1, 6)) 28 | # 2 -------------------------------------------------------------------------------- /How do you traverse a graph/DFS Code implementation, Recursion.py: -------------------------------------------------------------------------------- 1 | def depth_first_search_first_search(graph, starting_node): 2 | # Create a recursive function that takes in the graph and the starting node. 3 | print(starting_node) 4 | for neighbor in graph[starting_node]: 5 | # Create your base cases. In this case my base is when the for loop contains zero elements. 6 | # When that is the case I do not call the breadth_first_search function 7 | # Traverse all the neighboring nodes and call the recursive function with the index of the neighboring node. 8 | depth_first_search_first_search(graph, neighbor) 9 | 10 | 11 | graph = { 12 | 1: [2, 3], 13 | 2: [4], 14 | 3: [5, 6], 15 | 4: [], 16 | 5: [], 17 | 6: [], 18 | } 19 | depth_first_search_first_search(graph, 1) 20 | # 1 2 4 3 5 6 21 | -------------------------------------------------------------------------------- /Has path/DFS Recursive Solution.py: -------------------------------------------------------------------------------- 1 | def has_path(graph, starting_node, destination): 2 | # The first think we do is to ask if the starting node and destination are the same 3 | if starting_node == destination: 4 | return True 5 | for neighbor in graph[starting_node]: 6 | # Here we call the same function, has_path, for all of the neighbors of a given node 7 | # This function return true or false if we find such node. 8 | if has_path(graph, neighbor, destination): 9 | return True 10 | 11 | # If we couldn't find the destination node from the starting node 12 | # we return false 13 | return False 14 | 15 | graph = { 16 | 1: [2, 3], 17 | 2: [4], 18 | 3: [5, 6], 19 | 4: [], 20 | 5: [], 21 | 6: [], 22 | 7: [8, 9, 10], 23 | 8: [], 24 | 9: [6], 25 | 10: [], 26 | } 27 | print(has_path(graph, starting_node = 7, destination = 6)) 28 | # True -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Count Components/DFS Iterative Solution - Count Components.py: -------------------------------------------------------------------------------- 1 | def connected_components_counts(graph): 2 | visited = set() 3 | count = 0 4 | 5 | for node, neighbors in graph.items(): 6 | stack = [node] 7 | if node in visited: 8 | continue 9 | while len(stack) > 0: 10 | currentNode = stack.pop() 11 | if currentNode in visited: 12 | continue 13 | visited.add(currentNode) 14 | for neighbor in graph[currentNode]: 15 | stack.append(neighbor) 16 | count += 1 17 | return count 18 | 19 | graph = { 20 | 1: [2, 3], 21 | 2: [1, 4], 22 | 3: [1, 5, 6], 23 | 4: [2], 24 | 5: [3], 25 | 6: [3], 26 | 7: [8, 9, 10], 27 | 8: [7], 28 | 9: [7], 29 | 10: [7], 30 | 11: [12], 31 | 12: [11] 32 | } 33 | print(connected_components_counts(graph)) 34 | # 3 -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Largest Component/DFS Iterative Solution - Largest Component.py: -------------------------------------------------------------------------------- 1 | def get_largests_component(graph): 2 | visited = set() 3 | max_size = float("-inf") 4 | for node in graph: 5 | stack = [node] 6 | size = 0 7 | while len(stack) > 0: 8 | current_node = stack.pop() 9 | if current_node in visited: 10 | continue 11 | size += 1 12 | visited.add(current_node) 13 | for neighbor in graph[current_node]: 14 | stack.append(neighbor) 15 | 16 | max_size = max(max_size, size) 17 | return max_size 18 | 19 | 20 | graph = { 21 | 1: [2, 3], 22 | 2: [1, 4], 23 | 3: [1, 5, 6], 24 | 4: [2], 25 | 5: [3], 26 | 6: [3], 27 | 7: [8, 9, 10], 28 | 8: [7], 29 | 9: [7], 30 | 10: [7], 31 | 11: [12], 32 | 12: [11] 33 | } 34 | print(get_largests_component(graph)) 35 | # 6 -------------------------------------------------------------------------------- /Has path/BFS Iterative Solution.py: -------------------------------------------------------------------------------- 1 | def has_path(graph, starting_node, destination): 2 | # The first think we do is to ask if the starting node and destination are the same 3 | if starting_node == destination: 4 | return True 5 | queue = [starting_node] 6 | while len(queue) > 0: 7 | current_node = queue.pop(0) 8 | if current_node == destination: 9 | # We keep asking if they are the same with every new node in the queue 10 | return True 11 | for neighbor in graph[current_node]: 12 | queue.append(neighbor) 13 | 14 | # If we couldn't find the destination node from the starting node 15 | # we return false 16 | return False 17 | 18 | graph = { 19 | 1: [2, 3], 20 | 2: [4], 21 | 3: [5, 6], 22 | 4: [], 23 | 5: [], 24 | 6: [], 25 | 7: [8, 9, 10], 26 | 8: [], 27 | 9: [6], 28 | 10: [], 29 | } 30 | print(has_path(graph, starting_node = 7, destination = 6)) 31 | # True -------------------------------------------------------------------------------- /Has path/DFS Iterative Solution.py: -------------------------------------------------------------------------------- 1 | def has_path(graph, starting_node, destination): 2 | # The first think we do is to ask if the starting node and destination are the same 3 | if starting_node == destination: 4 | return True 5 | stack = [starting_node] # the only difference with a BFS is the stack 6 | while len(stack) > 0: 7 | current_node = stack.pop() 8 | if current_node == destination: 9 | # We keep asking if they are the same with every new node in the queue 10 | return True 11 | for neighbor in graph[current_node]: 12 | stack.append(neighbor) 13 | 14 | # If we couldn't find the destination node from the starting node 15 | # we return false 16 | return False 17 | 18 | graph = { 19 | 1: [2, 3], 20 | 2: [4], 21 | 3: [5, 6], 22 | 4: [], 23 | 5: [], 24 | 6: [], 25 | 7: [8, 9, 10], 26 | 8: [], 27 | 9: [6], 28 | 10: [], 29 | } 30 | print(has_path(graph, starting_node = 7, destination = 6)) 31 | # True -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Find path Between node A and B in a undirected Graph/DFS Recursive Solution - Has Path - Undirected Graph - Variation # 2.py: -------------------------------------------------------------------------------- 1 | def has_path(graph, starting_node, destination, visited = set()): 2 | # The first think we do is to ask if the starting node and destination are the same 3 | if starting_node == destination: 4 | return True 5 | for neighbor in graph[starting_node]: 6 | # We ask if our neighboring node has been visited 7 | if neighbor in visited: 8 | continue 9 | # we mark our neighbor has visited, so that in the next recursion call 10 | # we ask if it is the node that we are looking for 11 | visited.add(neighbor) 12 | if has_path(graph, neighbor, destination, visited): 13 | return True 14 | 15 | return False 16 | 17 | graph = { 18 | 1: [2, 3], 19 | 2: [1, 4], 20 | 3: [1, 5, 6], 21 | 4: [2], 22 | 5: [3], 23 | 6: [3], 24 | 7: [8, 9, 10], 25 | 8: [7], 26 | 9: [7], 27 | 10: [7], 28 | } 29 | print(has_path(graph, starting_node = 10, destination = 8)) 30 | # True -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Find path Between node A and B in a undirected Graph/BFS Solution - Has Path - Undirected Graph.py: -------------------------------------------------------------------------------- 1 | def has_path(graph, starting_node, destination): 2 | visited = set() # Set to keep track our visited nodes 3 | if starting_node == destination: 4 | return True 5 | queue = [starting_node] 6 | while len(queue) > 0: 7 | current_node = queue.pop(0) 8 | 9 | # Ask if our current node has been visited 10 | # if it has been visited, we skip this node 11 | if current_node in visited: 12 | continue 13 | # add our current node to the visited set 14 | visited.add(current_node) 15 | 16 | if current_node == destination: 17 | return True 18 | for neighbor in graph[current_node]: 19 | queue.append(neighbor) 20 | 21 | return False 22 | graph = { 23 | 1: [2, 3], 24 | 2: [1, 4], 25 | 3: [1, 5, 6], 26 | 4: [2], 27 | 5: [3], 28 | 6: [3], 29 | 7: [8, 9, 10], 30 | 8: [7], 31 | 9: [7], 32 | 10: [7], 33 | } 34 | print(has_path(graph, starting_node = 2, destination = 6)) 35 | # True -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Find path Between node A and B in a undirected Graph/DFS Recursive Solution - Has Path - Undirected Graph - Variation # 1.py: -------------------------------------------------------------------------------- 1 | # We add a new parameter called visited, to keep track of our visited nodes. 2 | # We pass an empty visited set to our function for the first time. 3 | def has_path(graph, starting_node, destination, visited = set()): 4 | # The first think we do is to ask if the starting node and destination are the same 5 | if starting_node == destination: 6 | return True 7 | # we checked if our starting node has been visited, if it has 8 | # we return False 9 | if starting_node in visited: 10 | return False 11 | visited.add(starting_node) 12 | for neighbor in graph[starting_node]: 13 | if has_path(graph, neighbor, destination, visited): 14 | return True 15 | 16 | return False 17 | 18 | graph = { 19 | 1: [2, 3], 20 | 2: [1, 4], 21 | 3: [1, 5, 6], 22 | 4: [2], 23 | 5: [3], 24 | 6: [3], 25 | 7: [8, 9, 10], 26 | 8: [7], 27 | 9: [7], 28 | 10: [7], 29 | } 30 | print(has_path(graph, starting_node = 10, destination = 8)) 31 | # True -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Largest Component/BFS Solution - Largest Component.py: -------------------------------------------------------------------------------- 1 | def get_largests_component(graph): 2 | visited = set() 3 | # We use the this variale to store the size of the maximum component 4 | # its first value es - infinity. 5 | max_size = float("-inf") 6 | for node in graph: 7 | queue = [node] 8 | size = 0 9 | while len(queue) > 0: 10 | current_node = queue.pop(0) 11 | if current_node in visited: 12 | continue 13 | size += 1 14 | visited.add(current_node) 15 | for neighbor in graph[current_node]: 16 | queue.append(neighbor) 17 | # when we stop iterating throug a component 18 | # we need to compare its maximum value with the 19 | # max_size variable. 20 | max_size = max(max_size, size) 21 | return max_size 22 | 23 | 24 | graph = { 25 | 1: [2, 3], 26 | 2: [1, 4], 27 | 3: [1, 5, 6], 28 | 4: [2], 29 | 5: [3], 30 | 6: [3], 31 | 7: [8, 9, 10], 32 | 8: [7], 33 | 9: [7], 34 | 10: [7], 35 | 11: [12], 36 | 12: [11] 37 | } 38 | print(get_largests_component(graph)) 39 | # 6 -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Find path Between node A and B in a undirected Graph/DFS Iterative Solution - Has Path - Undirected Graph.py: -------------------------------------------------------------------------------- 1 | def has_path(graph, starting_node, destination): 2 | visited = set() # Set to keep track our visited nodes 3 | if starting_node == destination: 4 | return True 5 | stack = [starting_node] #We use a stack instead of a queue for the DFS algorithm 6 | while len(stack) > 0: 7 | current_node = stack.pop() 8 | 9 | # Ask if our current node has been visited 10 | # if it has been visited, we skip this node 11 | if current_node in visited: 12 | continue 13 | # add our current node to the visited set 14 | visited.add(current_node) 15 | 16 | if current_node == destination: 17 | return True 18 | for neighbor in graph[current_node]: 19 | stack.append(neighbor) 20 | 21 | return False 22 | graph = { 23 | 1: [2, 3], 24 | 2: [1, 4], 25 | 3: [1, 5, 6], 26 | 4: [2], 27 | 5: [3], 28 | 6: [3], 29 | 7: [8, 9, 10], 30 | 8: [7], 31 | 9: [7], 32 | 10: [7], 33 | } 34 | print(has_path(graph, starting_node = 2, destination = 6)) 35 | # True -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Count Components/DFS Recursive Solution - Count Components.py: -------------------------------------------------------------------------------- 1 | def connected_components_counts(graph): 2 | visited = set() 3 | count = 0 4 | # we iterate through our graph and calling the traverse function 5 | # on each element 6 | for node, neighbors in graph.items(): 7 | if traverse_graph(node, visited, graph): 8 | count += 1 9 | return count 10 | 11 | def traverse_graph(currentNode, visited, graph): 12 | if currentNode in visited: 13 | return False 14 | visited.add(currentNode) 15 | 16 | for neighbor in graph[currentNode]: 17 | traverse_graph(neighbor, visited, graph) 18 | 19 | # When the recursive function reach this statement it means that it has found out component 20 | # and all its nodes have been marked as visited, so that we don't count them in the next 21 | # recursion call 22 | return True 23 | 24 | graph = { 25 | 1: [2, 3], 26 | 2: [1, 4], 27 | 3: [1, 5, 6], 28 | 4: [2], 29 | 5: [3], 30 | 6: [3], 31 | 7: [8, 9, 10], 32 | 8: [7], 33 | 9: [7], 34 | 10: [7], 35 | 11: [12], 36 | 12: [11] 37 | } 38 | print(connected_components_counts(graph)) 39 | # 3 -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Largest Component/DFS Recursive Solution - Largest Component - Variation #2.py: -------------------------------------------------------------------------------- 1 | def get_largests_component(graph): 2 | visited = set() 3 | max_size = float("-inf") 4 | for node, edges in graph.items(): 5 | size = traverse(node, graph, visited) 6 | # we compare the size of the recursive call, with the max_size, 7 | # and then we return it 8 | max_size = max(max_size, size) 9 | return max_size 10 | def traverse(node, graph,visited): 11 | # If the node es visited it means we counted already 12 | if node in visited: 13 | return 0 14 | visited.add(node) 15 | # if the node is not visited it means at least the size is one 16 | size = 1 17 | 18 | for neighbor in graph[node]: 19 | # we sume +1 to the sum for every new non visited node 20 | size += traverse(neighbor, graph,visited) 21 | return size 22 | 23 | graph = { 24 | 1: [2, 3], 25 | 2: [1, 4], 26 | 3: [1, 5, 6], 27 | 4: [2], 28 | 5: [3], 29 | 6: [3], 30 | 7: [8, 9, 10], 31 | 8: [7], 32 | 9: [7], 33 | 10: [7], 34 | 11: [12], 35 | 12: [11] 36 | } 37 | 38 | print(get_largests_component(graph)) 39 | # 6 -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Largest Component/DFS Recursive Solution - Largest Component - Variation #1.py: -------------------------------------------------------------------------------- 1 | def get_largests_component(graph): 2 | visited = set() 3 | max_size = float("-inf") 4 | for node, neighbors in graph.items(): 5 | # Inicializamos el size en 0, y luego lo pasamos a la funcion traverse_graph 6 | size = 0 7 | max_size = max(max_size , traverse_graph(node, visited, graph, size)) 8 | return max_size 9 | 10 | def traverse_graph(currentNode, visited, graph, size): 11 | if currentNode in visited: 12 | return size 13 | visited.add(currentNode) 14 | # If the node has not been visited 15 | # that means that the size must be at least 1 + previous size. 16 | size += 1 17 | for neighbor in graph[currentNode]: 18 | # calls to the neighbouring nodes may have different sizes, 19 | # but we are interested in the maximum, 20 | # so we use the max() function to update the size variable. 21 | size = max(size, traverse_graph(neighbor, visited, graph, size)) 22 | 23 | return size 24 | 25 | graph = { 26 | 1: [2, 3], 27 | 2: [1, 4], 28 | 3: [1, 5, 6], 29 | 4: [2], 30 | 5: [3], 31 | 6: [3], 32 | 7: [8, 9, 10], 33 | 8: [7], 34 | 9: [7], 35 | 10: [7], 36 | 11: [12], 37 | 12: [11] 38 | } 39 | print(get_largests_component(graph)) 40 | # 6 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Pértile Franco Giuliano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. -------------------------------------------------------------------------------- /Problems with islands/Largest Island/DFS Recursive Solution - Largest Island.py: -------------------------------------------------------------------------------- 1 | WATER = "W" 2 | LAND = "L" 3 | def largest_island(grid): 4 | visited = set() 5 | max_size = float("-inf") 6 | for row in range(len(grid)): 7 | for col in range(len(grid[0])): 8 | current_size = explore_land(grid, row, col, visited) 9 | if current_size > 0: 10 | max_size = max(max_size, current_size) 11 | return max_size 12 | 13 | def explore_land(grid, row, col, visited): 14 | is_row_in_bounds = row >= 0 and row < len(grid) 15 | is_col_in_bounds = col >= 0 and col < len(grid[0]) 16 | if is_row_in_bounds is not True or is_col_in_bounds is not True: 17 | return 0 18 | if grid[row][col] == WATER: 19 | return 0 20 | 21 | position = (row, col) 22 | if position in visited: 23 | return 0 24 | visited.add(position) 25 | 26 | size = 1 27 | # Here we start with a size of 1 for the first piece of land, 28 | # then add 1 if the recursive function calls return 1 for its neigbors, 29 | # then return total size 30 | size += explore_land(grid, row - 1, col, visited) 31 | size += explore_land(grid, row + 1, col, visited) 32 | size += explore_land(grid, row, col - 1, visited) 33 | size += explore_land(grid, row, col + 1, visited) 34 | 35 | return size 36 | 37 | 38 | grid = [ 39 | ["W", "L", "W", "W", "W"], 40 | ["W", "L", "W", "W", "W"], 41 | ["W", "W", "W", "L", "W"], 42 | ["W", "W", "L", "L", "W"], 43 | ["L", "W", "W", "L", "L"], 44 | ["L", "L", "W", "W", "W"], 45 | ] 46 | print(largest_island(grid)) 47 | # 5 -------------------------------------------------------------------------------- /Graph with cycles/Has a cycle solution.py: -------------------------------------------------------------------------------- 1 | def cycle_in_graph(graph): 2 | visited = set() 3 | currently_in_stack = set() 4 | 5 | for node in graph: 6 | # If the node was visited, we can skip it. 7 | if node in visited: 8 | continue 9 | # We call the function below, 10 | # to determine if the node we are scanning has a cycle, 11 | # if true, we return True. 12 | contains_cycle = is_node_in_cycle(graph, node, visited, currently_in_stack) 13 | if contains_cycle: 14 | return True 15 | return False 16 | 17 | 18 | def is_node_in_cycle(graph, node, visited, currently_in_stack): 19 | visited.add(node) 20 | # We add the current node to our stack, 21 | # if we explore it again and it is in the stack, it means we found a cycle. 22 | currently_in_stack.add(node) 23 | 24 | neighbors = graph[node] 25 | # We explore the neighbours of our current node 26 | for neighbor in neighbors: 27 | if neighbor not in visited: 28 | # For each of the neighbouring nodes we call the function below again, 29 | # to continue exploring, 30 | # and return True if a cycle is found. 31 | contains_cycle = is_node_in_cycle(graph, neighbor, visited, currently_in_stack) 32 | if contains_cycle: 33 | return True 34 | # If the node we are exploring has been visited and is in the stack, 35 | # it means that there is a cycle. 36 | elif neighbor in currently_in_stack: 37 | return True 38 | currently_in_stack.remove(node) 39 | 40 | return False 41 | 42 | graph = { 43 | 1: [2, 4], 44 | 2: [3], 45 | 3: [3], 46 | 4: [5], 47 | 5: [2] 48 | } 49 | print(cycle_in_graph(graph)) 50 | # True -------------------------------------------------------------------------------- /Undirected Graph and the Visited Pattern/Count Components/BFS Solution - Count Components.py: -------------------------------------------------------------------------------- 1 | def connected_components_counts(graph): 2 | visited = set() 3 | count = 0 # we use this variable to counts our components 4 | 5 | # This new for loop is important, because we need to make sure that we traverse through the entire graph 6 | # to ensure that we visit all nodes. 7 | for node, neighbors in graph.items(): 8 | queue = [node] 9 | if node in visited: 10 | continue 11 | while len(queue) > 0: 12 | currentNode = queue.pop(0) 13 | if currentNode in visited: 14 | continue 15 | visited.add(currentNode) 16 | for neighbor in graph[currentNode]: 17 | queue.append(neighbor) 18 | 19 | # We count as + 1 component, when our queue is empty, 20 | #because when we're traversing on the first node of a component 21 | # and we add an element to the queue, and when our queue happened 22 | # to be empty that means that we have visited all the nodes 23 | # related to the first node of our for loop, on the next element 24 | # of our for loop, if that element has been visited we skip it, 25 | # if the next element of our for loop is a node that has not 26 | # been visited, that means that we found a new component and 27 | # we need to search for its neighbors and add them to the queue. 28 | count += 1 29 | return count 30 | 31 | graph = { 32 | 1: [2, 3], 33 | 2: [1, 4], 34 | 3: [1, 5, 6], 35 | 4: [2], 36 | 5: [3], 37 | 6: [3], 38 | 7: [8, 9, 10], 39 | 8: [7], 40 | 9: [7], 41 | 10: [7], 42 | 11: [12], 43 | 12: [11] 44 | } 45 | print(connected_components_counts(graph)) 46 | # 3 -------------------------------------------------------------------------------- /Problems with islands/Count the islands/DFS Recursive Solution - Count the islands.py: -------------------------------------------------------------------------------- 1 | WATER = "W" 2 | LAND = "L" 3 | def island_count(grid): 4 | visited = set() 5 | island = 0 6 | for row in range(len(grid)): 7 | for col in range(len(grid[0])): 8 | # We iterate the grid with a double 9 | # for loop and call the explore_land 10 | # function with each node. 11 | # This function returns true or false, 12 | # true means it found an island and 13 | # we can add it to our variable "island". 14 | if explore_land(grid, row, col, visited): 15 | island += 1 16 | return island 17 | 18 | def explore_land(grid, row, col, visited): 19 | # we check if the node is out of bounds or not, 20 | # if true, we return true 21 | is_row_in_bounds = row >= 0 and row < len(grid) 22 | is_col_in_bounds = col >= 0 and col < len(grid[0]) 23 | if is_row_in_bounds is not True or is_col_in_bounds is not True: 24 | return False 25 | #If the current node is water we return false 26 | if grid[row][col] == WATER: 27 | return False 28 | 29 | # we grab the current position and check if the node 30 | # has been visited 31 | position = (row, col) 32 | if position in visited: 33 | return False 34 | visited.add(position) 35 | 36 | # We call the recursive function, 37 | # passing as parameter the possible positions of the neighboring nodes. 38 | explore_land(grid, row - 1, col, visited) 39 | explore_land(grid, row + 1, col, visited) 40 | explore_land(grid, row, col - 1, visited) 41 | explore_land(grid, row, col + 1, visited) 42 | 43 | # when we reach this statement, it means we found an island 44 | return True 45 | 46 | grid = [ 47 | ["W", "L", "W", "W", "L", "L"], 48 | ["W", "W", "W", "W", "W", "W"], 49 | ["W", "W", "W", "W", "L", "W"], 50 | ["W", "L", "L", "W", "L", "L"], 51 | ["W", "L", "L", "W", "L", "W"], 52 | ["W", "W", "W", "W", "W", "W"], 53 | ] 54 | print(island_count(grid)) 55 | # 4 -------------------------------------------------------------------------------- /Problems with islands/Largest Island/BFS Iterative Solution - Largest Island.py: -------------------------------------------------------------------------------- 1 | WATER = "W" 2 | LAND = "L" 3 | def largest_island(grid): 4 | visited = set() 5 | # Variable to keep track of the largest island 6 | max_island = float("-inf") 7 | for row in range(len(grid)): 8 | for col in range(len(grid[0])): 9 | if grid[row][col] == WATER: 10 | continue 11 | if (row, col) in visited: 12 | continue 13 | # Iterate the grid and call the explore_land function, 14 | # which will return the size of the island found, 15 | # and then compare it with our max_island variable. 16 | max_island = max(max_island, explore_land(grid, row, col, visited)) 17 | 18 | return max_island 19 | 20 | def explore_land(grid, row, col, visited): 21 | max_island = 0 22 | queue = [(row, col)] 23 | while len(queue) > 0: 24 | row, col = queue.pop(0) 25 | if (row, col) in visited: 26 | continue 27 | visited.add((row, col)) 28 | if grid[row][col] == WATER: 29 | continue 30 | # We add 1 to our variable for each land node we find, 31 | # then look for the neighbours of the current land node. 32 | max_island +=1 33 | neighbors = get_neigbors(grid, row, col) 34 | for neighbor in neighbors: 35 | queue.append(neighbor) 36 | return max_island 37 | 38 | def get_neigbors(grid, row, col): 39 | neighbors = [] 40 | # Up 41 | if row - 1 >= 0: 42 | neighbors.append((row - 1, col)) 43 | # Down 44 | if row + 1 < len(grid): 45 | neighbors.append((row + 1, col)) 46 | # Left 47 | if col - 1 >= 0: 48 | neighbors.append((row, col - 1)) 49 | # Rigth 50 | if col + 1 < len(grid[0]): 51 | neighbors.append((row, col + 1)) 52 | 53 | return neighbors 54 | 55 | grid = [ 56 | ["W", "L", "W", "W", "W"], 57 | ["W", "L", "W", "W", "W"], 58 | ["W", "W", "W", "L", "W"], 59 | ["W", "W", "L", "L", "W"], 60 | ["L", "W", "W", "L", "L"], 61 | ["L", "L", "W", "W", "W"], 62 | ] 63 | print(largest_island(grid)) 64 | # 5 -------------------------------------------------------------------------------- /Problems with islands/Count the islands/BFS Iterative Solution - Count the islands.py: -------------------------------------------------------------------------------- 1 | WATER = "W" 2 | LAND = "L" 3 | def island_count(grid): 4 | visited = set() 5 | islands = 0 6 | # With this nested for loop, we iterate through our grid 7 | for row in range(len(grid)): 8 | for col in range(len(grid[0])): 9 | # We continue if the cell is watter 10 | if grid[row][col] == WATER: 11 | continue 12 | # we also continue if the cell has been visited 13 | if (row, col) in visited: 14 | continue 15 | # If we reach this line, it means that we found an island, 16 | # our next step is to keep exploring this island, 17 | # and mark of its adjancent cells as visited, so we can 18 | # skip them in the next iteration of the foor loop 19 | islands += 1 20 | explore_land(grid, row, col, visited) 21 | 22 | return islands 23 | 24 | def explore_land(grid, row, col, visited): 25 | 26 | queue = [(row, col)] 27 | while len(queue) > 0: 28 | row, col = queue.pop(0) 29 | #We check if the node has been visited, 30 | # if so we skip it. 31 | if (row, col) in visited: 32 | continue 33 | # We mark the node as visited, and check if 34 | # the current node is water, if so we skip it. 35 | visited.add((row, col)) 36 | if grid[row][col] == WATER: 37 | continue 38 | # We get the neighboring nodes of 39 | # the current node, and then add them to the queue. 40 | neighbors = get_neigbors(grid, row, col) 41 | for neighbor in neighbors: 42 | queue.append(neighbor) 43 | 44 | def get_neigbors(grid, row, col): 45 | # We get the nodes around the current node. 46 | neighbors = [] 47 | # Up 48 | if row - 1 >= 0: 49 | neighbors.append((row - 1, col)) 50 | # Down 51 | if row + 1 < len(grid): 52 | neighbors.append((row + 1, col)) 53 | # Left 54 | if col - 1 >= 0: 55 | neighbors.append((row, col - 1)) 56 | # Rigth 57 | if col + 1 < len(grid[0]): 58 | neighbors.append((row, col + 1)) 59 | 60 | return neighbors 61 | 62 | grid = [ 63 | ["W", "L", "W", "W", "L", "L"], 64 | ["W", "W", "W", "W", "W", "W"], 65 | ["W", "W", "W", "W", "L", "W"], 66 | ["W", "L", "L", "W", "L", "L"], 67 | ["W", "L", "L", "W", "L", "W"], 68 | ["W", "W", "W", "W", "W", "W"], 69 | ] 70 | print(island_count(grid)) 71 | # 4 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Definite Guide to Graph Problems 2 | 3 |
4 | Logo The Definite Guide to Graph Problems 5 | 6 | ![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) 7 | ![Licence](https://img.shields.io/github/license/Ileriayo/markdown-badges?style=for-the-badge) 8 | 9 |
10 | 11 | This repository contains exercises from [The Definite Guide to Graph Problems](https://www.giulianopertile.com/blog/the-definitive-guide-to-graph-problems/). 12 | 13 | ## Table of Contents 14 | 15 | - [The Definite Guide to Graph Problems](#the-definite-guide-to-graph-problems) 16 | - [Table of Contents](#table-of-contents) 17 | - [Purpose of this Project](#purpose-of-this-project) 18 | - [Run the Problems](#run-the-problems) 19 | - [How to Contribute](#how-to-contribute) 20 | - [License](#license) 21 | 22 | ## Purpose of this Project 23 | 24 | [The Definite Guide to Graph Problems](https://www.giulianopertile.com/blog/the-definitive-guide-to-graph-problems/) was created to help people solve graph problems in order to succeed in coding interviews, as well as to improve problem solving skills. 25 | 26 | ## Run the Problems 27 | 28 | 1. Clone the repo 29 | ```bash 30 | git clone https://github.com/francofgp/The-Definite-Guide-to-Graph-Problems 31 | cd The-Definite-Guide-to-Graph-Problems/ 32 | ``` 33 | 34 | 2. This project requieres no external dependencies. To run the files just `cd` into the folder that you want and run the following 35 | 36 | ```bash 37 | python '.\Breadth-First-Search (BFS).py' 38 | ``` 39 | 40 | ## How to Contribute 41 | 42 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 43 | 44 | Currently the solutions are written in python, feel free to include solutions in other programming languages of your interest. 45 | 46 | ## [License](#license) 47 | 48 | Closures is provided under the [MIT License](https://github.com/vhesener/Closures/blob/master/LICENSE). 49 | 50 | ```text 51 | MIT License 52 | 53 | Copyright (c) 2022 Pértile Franco Giuliano 54 | 55 | Permission is hereby granted, free of charge, to any person obtaining a copy 56 | of this software and associated documentation files (the "Software"), to deal 57 | in the Software without restriction, including without limitation the rights 58 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 59 | copies of the Software, and to permit persons to whom the Software is 60 | furnished to do so, subject to the following conditions: 61 | 62 | The above copyright notice and this permission notice shall be included in all 63 | copies or substantial portions of the Software. 64 | 65 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 66 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 67 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 68 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 69 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 70 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 71 | SOFTWARE. 72 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 73 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 74 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 75 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 76 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 77 | SOFTWARE. 78 | ``` 79 | 80 | 81 | [MIT](https://choosealicense.com/licenses/mit/) 82 | 83 | 84 | 85 | --------------------------------------------------------------------------------