# CSPT13 Graphs II ## Traversals ``` from collections import deque class Graph: """Represent a graph as a dictionary of vertices mapping labels to edges.""" def __init__(self): self.vertices = {} def add_vertex(self, vertex_id): """ Add a vertex to the graph. """ if vertex_id not in self.vertices: self.vertices[vertex_id] = set() def add_edge(self, v1, v2): """ Add a directed edge to the graph. """ if v1 in self.vertices and v2 in self.vertices: self.vertices[v1].add(v2) def get_neighbors(self, vertex_id): """ Get all neighbors (edges) of a vertex. """ return self.vertices[vertex_id] if vertex_id in self.vertices else set() def bft(self, starting_vertex): """ Print each vertex in breadth-first order beginning from starting_vertex. """ pass # TODO def dft(self, starting_vertex): """ Print each vertex in depth-first order beginning from starting_vertex. """ visited = set() stack = deque() stack.append(starting_vertex) while len(stack) > 0: currNode = stack.pop() if currNode not in visited: visited.add(currNode) print(currNode) for neighbor in self.vertices[currNode]: stack.append(neighbor) def dft_recursive(self, starting_vertex): """ Print each vertex in depth-first order beginning from starting_vertex. This should be done using recursion. """ visited = set() self.dft_recursive_helper(starting_vertex, visited) def dft_recursive_helper(self, curr_vertex, visited): visited.add(curr_vertex) print(curr_vertex) for neighbor in self.vertices[curr_vertex]: if neighbor not in visited: self.dft_recursive_helper(neighbor, visited) def bfs(self, starting_vertex, destination_vertex): """ Return a list containing the shortest path from starting_vertex to destination_vertex in breath-first order. """ visited = set() queue = deque() queue.append([starting_vertex]) while len(queue) > 0: currPath = queue.popleft() currNode = currPath[-1] if currNode == destination_vertex: return currPath if currNode not in visited: visited.add(currNode) for neighbor in self.vertices[currNode]: newPath = list(currPath) newPath.append(neighbor) queue.append(newPath) return [] def dfs(self, starting_vertex, destination_vertex): """ Return a list containing a path from starting_vertex to destination_vertex in depth-first order. """ stack = deque() stack.append([starting_vertex]) visited = set() while len(stack) > 0: currPath = stack.pop() currNode = currPath[-1] if currNode == destination_vertex: return currPath if currNode not in visited: visited.add(currNode) for neighbor in self.vertices[currNode]: newPath = list(currPath) newPath.append(neighbor) stack.append(newPath) def dfs_recursive(self, starting_vertex, destination_vertex): visited = set() return self.dfs_recursive_helper([starting_vertex], destination_vertex, visited) def dfs_recursive_helper(self, curr_path, destination_vertex, visited): curr_vertex = curr_path[-1] if curr_vertex == destination_vertex: return curr_path visited.add(curr_vertex) for neighbor in self.vertices[curr_vertex]: if neighbor not in visited: newPath = list(curr_path) newPath.append(neighbor) res = self.dfs_recursive_helper(newPath, destination_vertex, visited) if len(res) > 0: return res return [] ``` ## [Destination City](https://leetcode.com/problems/destination-city) ``` """ https://leetcode.com/problems/destination-city/ Understand Input: paths = [["London","New York"],["New York","Lima"],["Lima","Sao Paulo"]] Output: "Sao Paulo" Explanation: Starting at "London" city you will reach "Sao Paulo" city which is the destination city. Your trip consist of: "London" -> "New York" -> "Lima" -> "Sao Paulo". Plan 1. Translate the problem into graph terminology Vertex = a city Edge = route from city to another city Weights not needed 2. Build your graph We can easily build a graph using adjacency lists from the input 3. Traverse your graph Either BFT/DFT will work. You can start from any vertices. If the current node we're at is not a key in the dictionary (graph), then it means it has no outbound edges. That's the destination city """ from collections import deque class Solution: def destCity(self, paths: List[List[str]]) -> str: if len(paths) == 0: return '' graph = self.createGraph(paths) stack = deque() stack.append(paths[0][0]) visited = set() while len(stack) > 0: curr = stack.pop() visited.add(curr) if curr not in graph: return curr else: for neighbor in graph[curr]: if neighbor not in visited: stack.append(neighbor) return '' def createGraph(self, paths): graph = {} for edge in paths: origin, destination = edge[0], edge[1] if origin in graph: graph[origin].add(destination) else: graph[origin] = { destination } return graph ``` ## Word Ladder ``` """ Given two words (begin_word and end_word), and a dictionary's word list, return the shortest transformation sequence from begin_word to end_word, such that: Only one letter can be changed at a time. Each transformed word must exist in the word list. Note that begin_word is not a transformed word. Note: Return None if there is no such transformation sequence. All words contain only lowercase alphabetic characters. You may assume no duplicates in the word list. You may assume begin_word and end_word are non-empty and are not the same. Sample: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] ["hit", "hot", "dot", "dog", "cog"] beginWord = "hit" endWord = "cog" wordList = ["hot","dot","dog","lot","log"] [] 1. Translate the problem into graph terminology vertex - a word edge - possible one letter transformation from a word to another word (undirected) path - transformations of a word weights - n/a 2. Build your graph - we can create all possible transformations of beginWord and its transformations, but that would waste a lot of memory - we can come up with how to find out the next vertex by determining if it's a valid vertex to visit - instead, we can determine which vertex to visit next if the transformation is in the wordList 3. Traverse the graph - shortest = BFS - we can traverse the graph using BFS and a queue - use a set to avoid re-visiting nodes - start from beginWord, and generate word transformations from it. enqueue nodes that are in the wordList - keep track of the path we're currently on as we're traversing via a list - once we find endWord, then we simply return the path to that node """ from collections import deque alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] def findLadders(beginWord, endWord, wordList): words = set(wordList) visited = set() queue = deque() queue.append([beginWord]) while len(queue) > 0: currPath = queue.popleft() currWord = currPath[-1] if currWord in visited: continue visited.add(currWord) if currWord == endWord: return currPath for i in range(len(currWord)): for letter in alphabet: transformedWord = currWord[:i] + letter + currWord[i + 1:] if transformedWord in words: newPath = list(currPath) newPath.append(transformedWord) queue.append(newPath) return [] a = findLadders("hit", "cog", ["hot","dot","dog","lot","log","cog"]) print(a) b = findLadders("hit", "cog", ["hot","dot","dog","lot","log"]) print(b) ```