- BST (binary search tree) search: find a value; works on BST; search with complexity **O(logN)**
- DFS (depth first search): finds a path from one node to another with recursive search, it does **not** explore all the children first before the grandchildren or any of the successors -- works on any directed graph
- BFS (breadth first search): finds a path from one node to another by exploring all children first before the grandchildren or any of the successors -- works on any directed graph
%% Cell type:code id:66ba808a tags:
``` python
# known import statements
from graphviz import Digraph
import time
import random
import pandas as pd
import matplotlib.pyplot as plt
# new import statements
```
%% Cell type:code id:c00e99eb tags:
``` python
def example(num):
g = Graph()
if num == 1:
g.node("A")
g.edge("B", "C")
g.edge("C", "D")
g.edge("D", "B")
elif num == 2:
g.edge("A", "B")
g.edge("B", "C")
g.edge("C", "D")
g.edge("D", "E")
g.edge("A", "E")
elif num == 3:
g.edge("A", "B")
g.edge("A", "C")
g.edge("B", "D")
g.edge("B", "E")
g.edge("C", "F")
g.edge("C", "G")
elif num == 4:
g.edge("A", "B")
g.edge("A", "C")
g.edge("B", "D")
g.edge("B", "E")
g.edge("C", "F")
g.edge("C", "G")
g.edge("E", "Z")
g.edge("C", "Z")
g.edge("B", "A")
elif num == 5:
width = 8
height = 4
for L1 in range(height-1):
L2 = L1 + 1
for i in range(width-(height-L1-1)):
for j in range(width-(height-L2-1)):
node1 = str(L1)+"-"+str(i)
node2 = str(L2)+"-"+str(j)
g.edge(node1, node2)
else:
raise Exception("no such example")
return g
```
%% Cell type:markdown id:6690b3be tags:
### For a regular graph, you need a new class `Graph` to keep track of the whole graph.
- Why? Remember graphs need not have a "root" node, which means there is no one origin point
%% Cell type:code id:8f5e8b06 tags:
``` python
class Graph:
def __init__(self):
# name => Node
self.nodes = {}
# to keep track which nodes have already been visited
self.visited = set()
def node(self, name):
node = Node(name)
self.nodes[name] = node
node.graph = self
def edge(self, src, dst):
"""
Automatically adds missing nodes.
"""
for name in [src, dst]:
if not name in self.nodes:
self.node(name)
self.nodes[src].children.append(self.nodes[dst])
def _repr_svg_(self):
"""
Draws the graph nodes and edges iteratively.
"""
g = Digraph()
for n in self.nodes:
g.node(n)
for child in self.nodes[n].children:
g.edge(n, child.name)
return g._repr_image_svg_xml()
def dfs_search(self, src_name, dst_name):
"""
Clears the visited set and invokes dfs_search using Node object instance
with name src_name.
"""
# Q: is this method recursive?
# A: no, it is just invoking dfs_search method for Node object instance
# dfs_search method in Node class is recursive
# These methods in two different classes just happen to share the same name
print(g.dfs_search("B", "A")) # should return None
print(g.dfs_search("B", "D")) # should return (B, C, D)
```
%% Cell type:markdown id:a92903d6 tags:
### Why is it called "*Depth* First Search"?
- we start at the starting node and go as deep as possible because recursion always goes as deep as possible before coming back to the other children in the previous level
- we need a `Stack` data structure:
- Last-In-First-Out (LIFO)
- recursion naturally uses `Stack`, which is why we don't have to explicitly use a `Stack` data structure