Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • cdis/cs/courses/cs320/s24
  • EBBARTELS/s24
  • kenninger/s24
  • hbartle/s24
  • jvoegeli/s24
  • chin6/s24
  • lallo/s24
  • cbjensen/s24
  • bjhicks/s24
  • JPERLOFF/s24
  • RMILLER56/s24
  • sswain2/s24
  • SHINEGEORGE/s24
  • SKALMAZROUEI/s24
  • nkempf2/s24
  • kmalovrh/s24
  • alagiriswamy/s24
  • SWEINGARTEN2/s24
  • SKALMAZROUEI/s-24-fork
  • jchasco/s24
20 results
Show changes
Showing
with 5663 additions and 0 deletions
File added
File added
%% Cell type:markdown id:d684d88e-e96d-4392-b4d6-92d3f1669b32 tags:
# Binary Search Trees
%% Cell type:markdown id:8da93b35 tags:
### Review **tree** terminology:
- **Tree**: DAG (directed acyclic graph) with exactly one **root** (has no parents) and all other nodes have exactly one parent
- **root**: any node with no parents
- **leaf**: any node with no children
%% Cell type:code id:66ba808a tags:
``` python
from graphviz import Graph, Digraph
```
%% Cell type:markdown id:8b758d75 tags:
### Is this a tree? If not, how do we make it into a tree?
%% Cell type:code id:d62b95dc-9f28-4e6e-a3a0-e0aac6e6eb3b tags:
``` python
g = Digraph()
g.edge("1", "2", label="left")
g.edge("1", "3", label="right")
g
```
%% Output
<graphviz.graphs.Digraph at 0x7f4eb821ab90>
%% Cell type:markdown id:aa0f43f8 tags:
### Special cases of trees
- **Linked list**: a tree, where each node has *at most* one child
- **Binary tree**: a tree, where each has *at most* two children
%% Cell type:markdown id:61015756 tags:
### Review: recursive functions
1. *Category 1*: functions that return some computation
2. *Category 2*: functions that do some action (for example: printing, appending, etc.,)
%% Cell type:markdown id:a6b914b2 tags:
## Binary tree
%% Cell type:code id:847dc240-ca01-4f1a-a4d7-13fed4fe3995 tags:
``` python
# TODO: define Node class
class Node:
def __init__(self, label):
self.label = label
self.left = None
self.right = None
# Category 2: functions that do some action
def dump(self, prefix="", suffix=""):
"""
prints out name of every node in the tree with some basic formatting
"""
# TODO: what is the simplest example in this case?
print(prefix, self.label, suffix)
# recurse left
if self.left != None:
self.left.dump(prefix+"\t", "(LEFT)")
# recurse right
if self.right != None:
self.right.dump(prefix+"\t", "(RIGHT)")
# Category 1: functions that return some computation
def search(self, target):
"""
returns True/False, if target is somewhere in the tree
"""
if target == self.label:
return True
if self.left != None:
if self.left.search(target):
return True
if self.right != None:
if self.right.search(target):
return True
return False
node1 = Node(1)
node2 = Node(2)
node3 = Node(3)
node4 = Node(4)
node1.left = node2
node1.right = node3
node2.left = node4
node1.dump()
```
%% Output
1
2 (LEFT)
4 (LEFT)
3 (RIGHT)
%% Cell type:markdown id:e81ed3ef tags:
### Let's come up with testcases for `search(...)`
%% Cell type:code id:c5248d0a tags:
``` python
print(node1.search(1)) # should be True
print(node1.search(2)) # should be True
print(node1.search(3)) # should be True
print(node1.search(4)) # should be True
print(node1.search(5)) # should be False
```
%% Output
True
True
True
True
False
%% Cell type:markdown id:c05e82e7 tags:
#### How many times is search(...) called, in the worst case?
- Assume tree has *N* nodes.
- Complexity is: **O(N)**
%% Cell type:markdown id:3c372194 tags:
## Binary Search Tree
- special case of *Binary trees*
- **BST rule**: any node's value is bigger than every value in its left subtree, and and smaller than every value in its right subtree
- TODO: write an efficient search for a BST (better complexity than O(N)
- TODO: write a method to add values to a BST, while preserving the BST rule
%% Cell type:code id:894a39d2-5e3b-4178-bc1b-dedf0b5a86c0 tags:
``` python
class BSTNode:
def __init__(self, label):
self.label = label
self.left = None
self.right = None
# Category 2: functions that do some action
def dump(self, prefix="", suffix=""):
"""
prints out name of every node in the tree with some basic formatting
"""
print(prefix, self.label, suffix)
if self.left != None:
self.left.dump(prefix+"\t", "(LEFT)")
if self.right != None:
self.right.dump(prefix+"\t", "(RIGHT)")
# Category 1: functions that return some computation
def search(self, target):
"""
returns True/False, if target is somewhere in the tree
"""
if target == self.label:
return True
elif target < self.label:
if self.left != None:
if self.left.search(target):
return True
elif target > self.label:
if self.right != None:
if self.right.search(target):
return True
return False
```
%% Cell type:markdown id:1d6935d8 tags:
### Does this tree satisfy BST rule? If not, which node violates it and how can we fix its position?
- Let's not displace other children node to find a new spot for the node in violation of BST rule.
%% Cell type:code id:7047d184 tags:
``` python
root = BSTNode(10)
root.left = BSTNode(2)
root.left.left = BSTNode(1)
root.left.right = BSTNode(4)
root.left.right.left = BSTNode(3)
root.right = BSTNode(15)
root.right.left = BSTNode(12)
root.right.right = BSTNode(19)
root.right.left.left = BSTNode(8)
root.dump()
```
%% Output
10
2 (LEFT)
1 (LEFT)
4 (RIGHT)
3 (LEFT)
15 (RIGHT)
12 (LEFT)
8 (LEFT)
19 (RIGHT)
%% Cell type:markdown id:1d56266b tags:
### BST after fix
%% Cell type:code id:dfbfc6e8 tags:
``` python
root = BSTNode(10)
root.left = BSTNode(2)
root.left.left = BSTNode(1)
root.left.right = BSTNode(4)
root.left.right.right = BSTNode(8)
root.left.right.left = BSTNode(3)
root.right = BSTNode(15)
root.right.left = BSTNode(12)
root.right.right = BSTNode(19)
#root.right.left.left = Node(8)
root.dump()
```
%% Output
10
2 (LEFT)
1 (LEFT)
4 (RIGHT)
3 (LEFT)
8 (RIGHT)
15 (RIGHT)
12 (LEFT)
19 (RIGHT)
%% Cell type:code id:61b93235-2702-474d-a601-e8fcbe5c3bcb tags:
``` python
# TODO: update "search(...)" definition for BSTNode
```
%% Cell type:markdown id:77d45804 tags:
### Testcases for BST `search(...)`
%% Cell type:code id:e2ef8e73 tags:
``` python
print(root.search(10)) # should be True
print(root.search(11)) # should be False
print(root.search(19)) # should be True
print(root.search(5)) # should be False
```
%% Output
True
False
True
False
%% Cell type:markdown id:6ae7786b tags:
#### How many times is BST search(...) called, in the worst case?
- Assume tree has *N* nodes.
- Complexity is: **O(h)**, where **h** is the height of the tree.
%% Cell type:code id:bd5aa50f tags:
``` python
```
%% Cell type:markdown id:d684d88e-e96d-4392-b4d6-92d3f1669b32 tags:
# Binary Search Trees
%% Cell type:markdown id:8da93b35 tags:
### Review **tree** terminology:
- **Tree**: DAG (directed acyclic graph) with exactly one **root** (has no parents) and all other nodes have exactly one parent
- **root**: any node with no parents
- **leaf**: any node with no children
%% Cell type:code id:66ba808a tags:
``` python
from graphviz import Graph
```
%% Cell type:markdown id:8b758d75 tags:
### Is this a tree? If not, how do we make it into a tree?
%% Cell type:code id:d62b95dc-9f28-4e6e-a3a0-e0aac6e6eb3b tags:
``` python
g = Graph()
g.edge("1", "2", label="left")
g.edge("1", "3", label="right")
g
```
%% Cell type:markdown id:aa0f43f8 tags:
### Special cases of trees
- **Linked list**: a tree, where each node has *at most* one child
- **Binary tree**: a tree, where each has *at most* two children
%% Cell type:markdown id:61015756 tags:
### Review: recursive functions
1. *Category 1*: functions that return some computation
2. *Category 2*: functions that do some action (for example: printing, appending, etc.,)
%% Cell type:markdown id:a6b914b2 tags:
## Binary tree
%% Cell type:code id:847dc240-ca01-4f1a-a4d7-13fed4fe3995 tags:
``` python
# TODO: define Node class
# Category 2: functions that do some action
def dump(self):
"""
prints out name of every node in the tree with some basic formatting
"""
pass
# Category 1: functions that return some computation
def search(self, target):
"""
returns True/False, if target is somewhere in the tree
"""
pass
# TODO: what is the simplest example in this case?
```
%% Cell type:markdown id:e81ed3ef tags:
### Let's come up with testcases for `search(...)`
%% Cell type:code id:c5248d0a tags:
``` python
print() # should be
print(node1.search()) # should be
print(node1.search()) # should be
print(node1.search()) # should be
print(node1.search()) # should be
```
%% Cell type:markdown id:c05e82e7 tags:
#### How many times is search(...) called, in the worst case?
- Assume tree has *N* nodes.
- Complexity is: ???
%% Cell type:markdown id:3c372194 tags:
## Binary Search Tree
- special case of *Binary trees*
- **BST rule**: any node's value is bigger than every value in its left subtree, and and smaller than every value in its right subtree
- TODO: write an efficient search for a BST (better complexity than O(N)
- TODO: write a method to add values to a BST, while preserving the BST rule
%% Cell type:code id:894a39d2-5e3b-4178-bc1b-dedf0b5a86c0 tags:
``` python
class BSTNode:
def __init__(self, label):
self.label = label
self.left = None
self.right = None
# Category 2: functions that do some action
def dump(self, prefix="", suffix=""):
"""
prints out name of every node in the tree with some basic formatting
"""
print(prefix, self.label, suffix)
# recurse left
if self.left != None:
self.left.dump(prefix+"\t", "(LEFT)")
# recurse right
if self.right != None:
self.right.dump(prefix+"\t", "(RIGHT)")
# Category 1: functions that return some computation
def search(self, target):
"""
returns True/False, if target is somewhere in the tree
"""
if target == self.label:
return True
if self.left != None:
if self.left.search(target):
return True
if self.right != None:
if self.right.search(target):
return True
return False
```
%% Cell type:markdown id:1d6935d8 tags:
### Does this tree satisfy BST rule? If not, which node violates it and how can we fix its position?
- Let's not displace other children node to find a new spot for the node in violation of BST rule.
%% Cell type:code id:7047d184 tags:
``` python
root = BSTNode(10)
root.left = BSTNode(2)
root.left.left = BSTNode(1)
root.left.right = BSTNode(4)
root.left.right.left = BSTNode(3)
root.right = BSTNode(15)
root.right.left = BSTNode(12)
root.right.right = BSTNode(19)
root.right.left.left = BSTNode(8)
root.dump()
```
%% Cell type:markdown id:1d56266b tags:
### BST after fix
%% Cell type:code id:dfbfc6e8 tags:
``` python
```
%% Cell type:code id:61b93235-2702-474d-a601-e8fcbe5c3bcb tags:
``` python
# TODO: update "search(...)" definition for BSTNode
```
%% Cell type:markdown id:77d45804 tags:
### Testcases for BST `search(...)`
%% Cell type:code id:e2ef8e73 tags:
``` python
print(root.search(10)) # should be
print(root.search(11)) # should be
print(root.search(19)) # should be
print(root.search(5)) # should be
```
%% Cell type:markdown id:6ae7786b tags:
#### How many times is BST search(...) called, in the worst case?
- Assume tree has *N* nodes.
- Complexity is: ???
%% Cell type:code id:bd5aa50f tags:
``` python
```
%% Cell type:markdown id:d684d88e-e96d-4392-b4d6-92d3f1669b32 tags:
# Binary Search Trees
%% Cell type:markdown id:8da93b35 tags:
### Review **tree** terminology:
- **Tree**: DAG (directed acyclic graph) with exactly one **root** (has no parents) and all other nodes have exactly one parent
- **root**: any node with no parents
- **leaf**: any node with no children
%% Cell type:code id:66ba808a tags:
``` python
from graphviz import Graph
```
%% Cell type:markdown id:8b758d75 tags:
### Is this a tree? If not, how do we make it into a tree?
%% Cell type:code id:d62b95dc-9f28-4e6e-a3a0-e0aac6e6eb3b tags:
``` python
g = Graph()
g.edge("1", "2", label="left")
g.edge("1", "3", label="right")
g
```
%% Cell type:markdown id:aa0f43f8 tags:
### Special cases of trees
- **Linked list**: a tree, where each node has *at most* one child
- **Binary tree**: a tree, where each has *at most* two children
%% Cell type:markdown id:61015756 tags:
### Review: recursive functions
1. *Category 1*: functions that return some computation
2. *Category 2*: functions that do some action (for example: printing, appending, etc.,)
%% Cell type:markdown id:a6b914b2 tags:
## Binary tree
%% Cell type:code id:847dc240-ca01-4f1a-a4d7-13fed4fe3995 tags:
``` python
# TODO: define Node class
# Category 2: functions that do some action
def dump(self):
"""
prints out name of every node in the tree with some basic formatting
"""
pass
# Category 1: functions that return some computation
def search(self, target):
"""
returns True/False, if target is somewhere in the tree
"""
pass
# TODO: what is the simplest example in this case?
```
%% Cell type:markdown id:e81ed3ef tags:
### Let's come up with testcases for `search(...)`
%% Cell type:code id:c5248d0a tags:
``` python
print() # should be
print(node1.search()) # should be
print(node1.search()) # should be
print(node1.search()) # should be
print(node1.search()) # should be
```
%% Cell type:markdown id:c05e82e7 tags:
#### How many times is search(...) called, in the worst case?
- Assume tree has *N* nodes.
- Complexity is: ???
%% Cell type:markdown id:3c372194 tags:
## Binary Search Tree
- special case of *Binary trees*
- **BST rule**: any node's value is bigger than every value in its left subtree, and and smaller than every value in its right subtree
- TODO: write an efficient search for a BST (better complexity than O(N)
- TODO: write a method to add values to a BST, while preserving the BST rule
%% Cell type:code id:894a39d2-5e3b-4178-bc1b-dedf0b5a86c0 tags:
``` python
class BSTNode:
def __init__(self, label):
self.label = label
self.left = None
self.right = None
# Category 2: functions that do some action
def dump(self, prefix="", suffix=""):
"""
prints out name of every node in the tree with some basic formatting
"""
print(prefix, self.label, suffix)
# recurse left
if self.left != None:
self.left.dump(prefix+"\t", "(LEFT)")
# recurse right
if self.right != None:
self.right.dump(prefix+"\t", "(RIGHT)")
# Category 1: functions that return some computation
def search(self, target):
"""
returns True/False, if target is somewhere in the tree
"""
if target == self.label:
return True
if self.left != None:
if self.left.search(target):
return True
if self.right != None:
if self.right.search(target):
return True
return False
```
%% Cell type:markdown id:1d6935d8 tags:
### Does this tree satisfy BST rule? If not, which node violates it and how can we fix its position?
- Let's not displace other children node to find a new spot for the node in violation of BST rule.
%% Cell type:code id:7047d184 tags:
``` python
root = BSTNode(10)
root.left = BSTNode(2)
root.left.left = BSTNode(1)
root.left.right = BSTNode(4)
root.left.right.left = BSTNode(3)
root.right = BSTNode(15)
root.right.left = BSTNode(12)
root.right.right = BSTNode(19)
root.right.left.left = BSTNode(8)
root.dump()
```
%% Cell type:markdown id:1d56266b tags:
### BST after fix
%% Cell type:code id:dfbfc6e8 tags:
``` python
```
%% Cell type:code id:61b93235-2702-474d-a601-e8fcbe5c3bcb tags:
``` python
# TODO: update "search(...)" definition for BSTNode
```
%% Cell type:markdown id:77d45804 tags:
### Testcases for BST `search(...)`
%% Cell type:code id:e2ef8e73 tags:
``` python
print(root.search(10)) # should be
print(root.search(11)) # should be
print(root.search(19)) # should be
print(root.search(5)) # should be
```
%% Cell type:markdown id:6ae7786b tags:
#### How many times is BST search(...) called, in the worst case?
- Assume tree has *N* nodes.
- Complexity is: ???
%% Cell type:code id:bd5aa50f tags:
``` python
```
File added
File added
%% Cell type:markdown id:d684d88e-e96d-4392-b4d6-92d3f1669b32 tags:
# Binary Search Trees
- Recursive `add()` method
- Recursive `height()` method
%% Cell type:code id:66ba808a tags:
``` python
from graphviz import Graph, Digraph
import random
import math
```
%% Cell type:markdown id:3c372194 tags:
## Binary Search Tree
- special case of *Binary trees*
- **BST rule**: any node's value is bigger than every value in its left subtree, and and smaller than every value in its right subtree
- TODO: write an efficient search for a BST (better complexity than O(N)
- TODO: write a method to add values to a BST, while preserving the BST rule
%% Cell type:code id:894a39d2-5e3b-4178-bc1b-dedf0b5a86c0 tags:
``` python
class BSTNode:
def __init__(self, label):
self.label = label
self.left = None
self.right = None
# Category 2: functions that do some action
def dump(self, prefix="", suffix=""):
"""
prints out name of every node in the tree with some basic formatting
"""
print(prefix, self.label, suffix)
if self.left != None:
self.left.dump(prefix+"\t", "(LEFT)")
if self.right != None:
self.right.dump(prefix+"\t", "(RIGHT)")
# Category 1: functions that return some computation
def search(self, target):
"""
returns True/False, if target is somewhere in the tree
"""
if target == self.label:
return True
elif target < self.label:
if self.left != None:
if self.left.search(target):
return True
elif target > self.label:
if self.right != None:
if self.right.search(target):
return True
return False
def add(self, label):
"""
Finds the correct spot for label and adds a new node with it.
Assumes that tree already contains at least one node -> TODO: discuss why?
Raises ValueError if label is already on the tree.
"""
if label < self.label:
# go left
if self.left == None:
self.left = BSTNode(label)
else:
# recurse left
self.left.add(label)
elif label > self.label:
# go right
if self.right == None:
self.right = BSTNode(label)
else:
# recurse right
self.right.add(label)
else:
raise ValueError(f"{label} is already a node on the tree!")
def height(self):
"""
Calculates height of the BST.
Height: the number of nodes on the longest root-to-leaf path (including the root)
"""
if self.left == None:
l = 0
else:
# recurse left
l = self.left.height()
if self.right == None:
r = 0
else:
# recurse right
r = self.right.height()
return max(l, r)+1
```
%% Cell type:markdown id:d22c3684 tags:
### Code folding nbextension
- Go to "jupyterlab" > "Settings" > "Advanced Settings Editor" > "Notebook" > "Rulers" > enable "Code Folding" (there should be three such settings).
%% Cell type:markdown id:1d6935d8 tags:
### Recursive `add` method
- Manually creating a tree is cumbersome and subject to mistakes (violations of BST rule)
%% Cell type:code id:7047d184 tags:
``` python
root = BSTNode(10)
root.left = BSTNode(2)
root.left.left = BSTNode(1)
root.left.right = BSTNode(4)
root.left.right.right = BSTNode(8)
root.left.right.left = BSTNode(3)
root.right = BSTNode(15)
root.right.left = BSTNode(12)
root.right.right = BSTNode(19)
root.dump("", "(ROOT)")
```
%% Output
10 (ROOT)
2 (LEFT)
1 (LEFT)
4 (RIGHT)
3 (LEFT)
8 (RIGHT)
15 (RIGHT)
12 (LEFT)
19 (RIGHT)
%% Cell type:code id:0cd51cf2 tags:
``` python
values = [10, 2, 1, 4, 8, 3, 15, 12, 19]
root = BSTNode(values[0])
for val in values[1:]:
root.add(val)
root.dump("", "(ROOT)")
```
%% Output
10 (ROOT)
2 (LEFT)
1 (LEFT)
4 (RIGHT)
3 (LEFT)
8 (RIGHT)
15 (RIGHT)
12 (LEFT)
19 (RIGHT)
%% Cell type:markdown id:f9324526 tags:
### Recursive `height` method
- **Height**: the number of nodes on the longest root-to-leaf path (including the root)
- left subtree has height 4, right subtree has height 6, my height = 7
- left subtree has height 4, right subtree has height 4, my height = 5
- left subtree has height 10, right subtree has height 0, my height = 11
- left subtree has height of l, right subtree has height of r, my height = max(l, r)+1
- What is the simplest case for height calculation? Tree containing just root node
- What are the values of l and r in that case? l = 0 and r = 0
%% Cell type:code id:18d8de1d tags:
``` python
# TODO: Let's implement and invoke the height method
root.height()
```
%% Output
4
%% Cell type:markdown id:bb2057f2 tags:
### Tree containing 100 values
- let's use range(...) to produce a sequence of 100 integers
- recall that range(...) returns a sequence in increasing order
- what will be the height of this tree? **100**
%% Cell type:code id:820f3596 tags:
``` python
values = list(range(100))
# Q: Is this tree balanced?
# A: No, it is the worst possible BST for these numbers, that is
# it is a linked list!
root = BSTNode(values[0])
for val in values[1:]:
root.add(val)
print(root.height())
# root.dump("", "(ROOT)")
```
%% Output
100
%% Cell type:markdown id:af9dd1b3 tags:
#### Let's use `random` module `shuffle` function to randomly order the sequence of 100 numbers.
- in-place re-ordering of numbers (just like `sort` method)
%% Cell type:code id:c07664be tags:
``` python
values = list(range(100))
random.shuffle(values)
# Q: Is this tree balanced?
# A: depends on the shuffling, you can check using math.log2(N)
root = BSTNode(values[0])
for val in values[1:]:
root.add(val)
print(root.height())
root.dump("", "(ROOT)")
```
%% Output
11
21 (ROOT)
13 (LEFT)
8 (LEFT)
6 (LEFT)
4 (LEFT)
2 (LEFT)
0 (LEFT)
1 (RIGHT)
3 (RIGHT)
5 (RIGHT)
7 (RIGHT)
10 (RIGHT)
9 (LEFT)
11 (RIGHT)
12 (RIGHT)
16 (RIGHT)
14 (LEFT)
15 (RIGHT)
20 (RIGHT)
19 (LEFT)
17 (LEFT)
18 (RIGHT)
45 (RIGHT)
34 (LEFT)
32 (LEFT)
28 (LEFT)
27 (LEFT)
22 (LEFT)
25 (RIGHT)
24 (LEFT)
23 (LEFT)
26 (RIGHT)
30 (RIGHT)
29 (LEFT)
31 (RIGHT)
33 (RIGHT)
38 (RIGHT)
37 (LEFT)
36 (LEFT)
35 (LEFT)
44 (RIGHT)
43 (LEFT)
42 (LEFT)
39 (LEFT)
41 (RIGHT)
40 (LEFT)
60 (RIGHT)
50 (LEFT)
46 (LEFT)
48 (RIGHT)
47 (LEFT)
49 (RIGHT)
52 (RIGHT)
51 (LEFT)
53 (RIGHT)
56 (RIGHT)
54 (LEFT)
55 (RIGHT)
57 (RIGHT)
58 (RIGHT)
59 (RIGHT)
74 (RIGHT)
66 (LEFT)
65 (LEFT)
64 (LEFT)
62 (LEFT)
61 (LEFT)
63 (RIGHT)
72 (RIGHT)
67 (LEFT)
71 (RIGHT)
68 (LEFT)
69 (RIGHT)
70 (RIGHT)
73 (RIGHT)
78 (RIGHT)
75 (LEFT)
76 (RIGHT)
77 (RIGHT)
91 (RIGHT)
84 (LEFT)
81 (LEFT)
79 (LEFT)
80 (RIGHT)
82 (RIGHT)
83 (RIGHT)
88 (RIGHT)
87 (LEFT)
85 (LEFT)
86 (RIGHT)
89 (RIGHT)
90 (RIGHT)
94 (RIGHT)
92 (LEFT)
93 (RIGHT)
97 (RIGHT)
95 (LEFT)
96 (RIGHT)
98 (RIGHT)
99 (RIGHT)
%% Cell type:code id:4d87a7e7 tags:
``` python
math.log2(100)
```
%% Output
6.643856189774724
%% Cell type:markdown id:cf919d84 tags:
### Balanced BSTs / Self-balancing BSTs
- not a covered topic for the purpose of this course
- you can explore the below recursive function definition if you are interested
- you are **not required** to know how to do this
%% Cell type:code id:bd5aa50f tags:
``` python
# Recrusive function that
def sorted_array_to_bst(nums, bst_nums):
"""
Produces best ordering nums (a list of sorted numbers),
for the purpose of creating a balanced BST.
Writes new ordering of numbers into bst_nums.
"""
if len(nums) == 0:
return None
elif len(nums) == 1:
bst_nums.append(nums[0])
else:
mid_index = len(nums)//2
bst_nums.append(nums[mid_index])
# recurse left
left_val = sorted_array_to_bst(nums[:mid_index], bst_nums)
if left_val != None:
bst_nums.append(left_val)
# recurse right
right_val = sorted_array_to_bst(nums[mid_index+1:], bst_nums)
if right_val != None:
bst_nums.append(right_val)
```
%% Cell type:code id:98b9148d tags:
``` python
bst_nums = []
sorted_array_to_bst(list(range(5)), bst_nums)
bst_nums
```
%% Output
[2, 1, 0, 4, 3]
%% Cell type:code id:f1288713 tags:
``` python
bst_nums = []
sorted_array_to_bst(list(range(100)), bst_nums)
root = BSTNode(bst_nums[0])
for val in bst_nums[1:]:
root.add(val)
print(root.height())
```
%% Output
7
%% Cell type:code id:399fe31a tags:
``` python
bst_nums = []
sorted_array_to_bst(list(range(5)), bst_nums)
root = BSTNode(bst_nums[0])
for val in bst_nums[1:]:
root.add(val)
print(root.height())
root.dump("", "(ROOT)")
```
%% Output
3
2 (ROOT)
1 (LEFT)
0 (LEFT)
4 (RIGHT)
3 (LEFT)
%% Cell type:markdown id:e042962c tags:
### Depth First Search (DFS)
- Last lecture: BST search with complexity **O(logN)**
- Finds a path from one node to another -- works on any directed graph
%% 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_v1(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
self.visited.clear()
return self.nodes[src_name].dfs_search_v1(self.nodes[dst_name])
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
self.visited.clear()
return self.nodes[src_name].dfs_search(self.nodes[dst_name])
class Node:
def __init__(self, name):
self.name = name
self.children = []
self.graph = None # back reference
def __repr__(self):
return self.name
def dfs_search_v1(self, dst):
"""
Returns True / False when path to dst is found / not found.
Try using this method by commenting out the dfs_search method below.
"""
# TODO: what is the simplest case? current node is the dst
if self in self.graph.visited:
return False
self.graph.visited.add(self)
if self == dst:
return True
for child in self.children:
if child.dfs_search_v1(dst):
return True
return False
def dfs_search(self, dst):
"""
Returns the actual path to the dst as a tuple or None otherwise
"""
# TODO: what is the simplest case? current node is the dst
if self in self.graph.visited:
return None
self.graph.visited.add(self)
if self == dst:
return (self,)
for child in self.children:
child_path = child.dfs_search(dst)
if child_path != None:
return (self,) + child_path
return None
g = example(1)
g
```
%% Output
<__main__.Graph at 0x7ff53065d420>
%% Cell type:markdown id:c83e9993-765c-42a0-97f6-6277627acf95 tags:
### Testcases for DFS with True/False
%% Cell type:code id:15edd0d2 tags:
``` python
print(g.dfs_search_v1("B", "A")) # should return False
print(g.dfs_search_v1("B", "D")) # should return True
```
%% Output
False
True
%% Cell type:code id:0330e0b6 tags:
``` python
# DFS search
# TODO: give the actual path, not just True/False
# TODO: use a different algorithm to find the shortest path
```
%% Cell type:markdown id:59aee028 tags:
### Testcases for DFS with path
%% Cell type:code id:6de4c8b1 tags:
``` python
print(g.dfs_search("B", "A")) # should return None
print(g.dfs_search("B", "D")) # should return (B, C, D)
```
%% Output
None
(B, C, D)
%% Cell type:markdown id:a54b6599 tags:
### `tuple` review
- similar to lists, but immutable
- defined using `()`
- `*` operator represents replication and not multiplication for lists and tuples
- `+` operator represents concatenation and not additional for lists and tuples
%% Cell type:code id:7da7bccc tags:
``` python
(3+2,) # this is a tuple containing 5
```
%% Output
(5,)
%% Cell type:code id:b05a563a tags:
``` python
(3+2) # order precedence
```
%% Output
5
%% Cell type:code id:778a76d7 tags:
``` python
# replicates item 5 three times and returns a new tuple
(3+2,) * 3
```
%% Output
(5, 5, 5)
%% Cell type:code id:b8cc1b36 tags:
``` python
(3+2) * 3 # gives us 15
```
%% Output
15
%% Cell type:code id:a9e31d22 tags:
``` python
# returns a new tuple containing all items in the first tuple and
# the second tuple
(3, ) + (5, )
```
%% Output
(3, 5)
%% Cell type:markdown id:43c566a5 tags:
### DFS search
- return the actual path rather than just returning True / False
- for example, path between B and D should be (B, C, D)
%% Cell type:markdown id:e7cb5fc1 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
- might not give us the shortest possible path
%% Cell type:code id:4480be3c tags:
``` python
g = example(2)
g
```
%% Output
<__main__.Graph at 0x7ff5306d9f00>
%% Cell type:code id:9467f6cf tags:
``` python
print(g.dfs_search("A", "E")) # should return (A, B, C, D, E)
print(g.dfs_search("E", "A")) # should return None
```
%% Output
(A, B, C, D, E)
None
%% Cell type:markdown id:d684d88e-e96d-4392-b4d6-92d3f1669b32 tags:
# Binary Search Trees
- Recursive `add()` method
- Recursive `height()` method
%% Cell type:code id:66ba808a tags:
``` python
from graphviz import Graph, Digraph
```
%% Cell type:markdown id:3c372194 tags:
## Binary Search Tree
- special case of *Binary trees*
- **BST rule**: any node's value is bigger than every value in its left subtree, and and smaller than every value in its right subtree
- TODO: write an efficient search for a BST (better complexity than O(N)
- TODO: write a method to add values to a BST, while preserving the BST rule
%% Cell type:code id:894a39d2-5e3b-4178-bc1b-dedf0b5a86c0 tags:
``` python
class BSTNode:
def __init__(self, label):
self.label = label
self.left = None
self.right = None
# Category 2: functions that do some action
def dump(self, prefix="", suffix=""):
"""
prints out name of every node in the tree with some basic formatting
"""
print(prefix, self.label, suffix)
if self.left != None:
self.left.dump(prefix+"\t", "(LEFT)")
if self.right != None:
self.right.dump(prefix+"\t", "(RIGHT)")
# Category 1: functions that return some computation
def search(self, target):
"""
returns True/False, if target is somewhere in the tree
"""
if target == self.label:
return True
elif target < self.label:
if self.left != None:
if self.left.search(target):
return True
elif target > self.label:
if self.right != None:
if self.right.search(target):
return True
return False
def add(self, label):
"""
Finds the correct spot for label and adds a new node with it.
Assumes that tree already contains at least one node -> TODO: discuss why?
Raises ValueError if label is already on the tree.
"""
pass
def height(self):
"""
Calculates height of the BST.
Height: the number of nodes on the longest root-to-leaf path (including the root)
"""
pass
```
%% Cell type:markdown id:6104a9e7 tags:
### Code folding nbextension
- Go to "jupyterlab" > "Settings" > "Advanced Settings Editor" > "Notebook" > "Rulers" > enable "Code Folding" (there should be three such settings).
%% Cell type:markdown id:1d6935d8 tags:
### Recursive `add` method
- Manually creating a tree is cumbersome and subject to mistakes (violations of BST rule)
%% Cell type:code id:7047d184 tags:
``` python
root = BSTNode(10)
root.left = BSTNode(2)
root.left.left = BSTNode(1)
root.left.right = BSTNode(4)
root.left.right.right = BSTNode(8)
root.left.right.left = BSTNode(3)
root.right = BSTNode(15)
root.right.left = BSTNode(12)
root.right.right = BSTNode(19)
root.dump("", "(ROOT)")
```
%% Cell type:code id:0cd51cf2 tags:
``` python
values = [10, 2, 1, 4, 8, 3, 15, 12, 19]
root = BSTNode(values[0])
for val in values[1:]:
root.add(val)
root.dump("", "(ROOT)")
```
%% Cell type:markdown id:f9324526 tags:
### Recursive `height` method
- **Height**: the number of nodes on the longest root-to-leaf path (including the root)
- left subtree has height 4, right subtree has height 6, my height = ?
- left subtree has height 4, right subtree has height 4, my height = ?
- left subtree has height 10, right subtree has height 0, my height = ?
- left subtree has height of l, right subtree has height of r, my height = ?
- What is the simplest case for height calculation?
- What are the values of l and r in that case?
%% Cell type:code id:18d8de1d tags:
``` python
# TODO: Let's implement and invoke the height method
```
%% Cell type:markdown id:bb2057f2 tags:
### Tree containing 100 values
- let's use range(...) to produce a sequence of 100 integers
- recall that range(...) returns a sequence in increasing order
- what will be the height of this tree? **100**
%% Cell type:code id:820f3596 tags:
``` python
values = list(range(100))
# Q: Is this tree balanced?
# A:
```
%% Cell type:markdown id:af9dd1b3 tags:
#### Let's use `random` module `shuffle` function to randomly order the sequence of 100 numbers.
- in-place re-ordering of numbers (just like `sort` method)
%% Cell type:code id:c07664be tags:
``` python
values = list(range(100))
random.shuffle(values)
# Q: Is this tree balanced?
# A: depends on the shuffling, you can check using math.log2(N)
root = BSTNode(values[0])
for val in values[1:]:
root.add(val)
print(root.height())
root.dump("", "(ROOT)")
```
%% Cell type:code id:4d87a7e7 tags:
``` python
math.log2(100)
```
%% Cell type:markdown id:cf919d84 tags:
### Balanced BSTs / Self-balancing BSTs
- not a covered topic for the purpose of this course
- you can explore the below recursive function definition if you are interested
- you are **not required** to know how to do this
%% Cell type:code id:bd5aa50f tags:
``` python
# Recrusive function that
def sorted_array_to_bst(nums, bst_nums):
"""
Produces best ordering nums (a list of sorted numbers),
for the purpose of creating a balanced BST.
Writes new ordering of numbers into bst_nums.
"""
if len(nums) == 0:
return None
elif len(nums) == 1:
bst_nums.append(nums[0])
else:
mid_index = len(nums)//2
bst_nums.append(nums[mid_index])
# recurse left
left_val = sorted_array_to_bst(nums[:mid_index], bst_nums)
if left_val != None:
bst_nums.append(left_val)
# recurse right
right_val = sorted_array_to_bst(nums[mid_index+1:], bst_nums)
if right_val != None:
bst_nums.append(right_val)
```
%% Cell type:code id:98b9148d tags:
``` python
bst_nums = []
sorted_array_to_bst(list(range(5)), bst_nums)
bst_nums
```
%% Cell type:code id:f1288713 tags:
``` python
bst_nums = []
sorted_array_to_bst(list(range(100)), bst_nums)
root = BSTNode(bst_nums[0])
for val in bst_nums[1:]:
root.add(val)
print(root.height())
```
%% Cell type:code id:399fe31a tags:
``` python
bst_nums = []
sorted_array_to_bst(list(range(5)), bst_nums)
root = BSTNode(bst_nums[0])
for val in bst_nums[1:]:
root.add(val)
print(root.height())
root.dump("", "(ROOT)")
```
%% Cell type:markdown id:e042962c tags:
### Depth First Search (DFS)
- Last lecture: BST search with complexity **O(logN)**
- Finds a path from one node to another -- works on any directed graph
%% 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 = {}
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()
class Node:
def __init__(self, name):
self.name = name
self.children = []
self.graph = None # back reference
def __repr__(self):
return self.name
g = example(1)
g
```
%% Cell type:markdown id:c83e9993-765c-42a0-97f6-6277627acf95 tags:
### Testcases for DFS
%% Cell type:code id:15edd0d2 tags:
``` python
print(g.dfs_search("B", "A")) # should return False
print(g.dfs_search("B", "D")) # should return True
```
%% Cell type:code id:bbc7eba9 tags:
``` python
# DFS search
# TODO: give the actual path, not just True/False
# TODO: use a different algorithm to find the shortest path
```
%% Cell type:code id:d2b5e4a3 tags:
``` python
print(g.nodes["B"].dfs_search(g.nodes["D"]))
```
%% Cell type:code id:c5d9affc tags:
``` python
print(g.nodes["B"].dfs_search(g.nodes["A"]))
# what is wrong?
```
%% Cell type:markdown id:da658c4d tags:
### Testcases for DFS
%% Cell type:code id:32d1fa4f tags:
``` python
g
```
%% Cell type:code id:a9dfbc1e tags:
``` python
print(g.dfs_search("B", "A")) # should return
print(g.dfs_search("B", "D")) # should return
```
%% Cell type:markdown id:f521b9da tags:
### `tuple` review
- similar to lists, but immutable
- `*` operator represents replication and not multiplication for lists and tuples
- `+` operator represents concatenation and not additional for lists and tuples
%% Cell type:code id:6802d98e tags:
``` python
# this is a tuple containing 5
(3+2,)
```
%% Cell type:code id:696d0755 tags:
``` python
(3+2) # order precedence
```
%% Cell type:code id:3046fe4a tags:
``` python
# replicates item 5 three times and returns a new tuple
(3+2,) * 3
```
%% Cell type:code id:aed44ea1 tags:
``` python
(3+2) * 3 # gives us 15
```
%% Cell type:code id:d8806544 tags:
``` python
# returns a new tuple containing all items in the first tuple and
# the second tuple
(3, ) + (5, )
```
%% Cell type:markdown id:9aabb4fb tags:
### DFS search
- return the actual path rather than just returning True / False
- for example, path between B and D should be (B, C, D)
%% Cell type:markdown id:eb9a6595 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
- might not give us the shortest possible path
%% Cell type:code id:c934d86d tags:
``` python
g = example(2)
g
```
%% Cell type:code id:8c8dc14a tags:
``` python
print(g.dfs_search("A", "E")) # should return
print(g.dfs_search("E", "A")) # should return
```
%% Cell type:markdown id:d684d88e-e96d-4392-b4d6-92d3f1669b32 tags:
# Binary Search Trees
- Recursive `add()` method
- Recursive `height()` method
%% Cell type:code id:66ba808a tags:
``` python
from graphviz import Graph, Digraph
```
%% Cell type:markdown id:3c372194 tags:
## Binary Search Tree
- special case of *Binary trees*
- **BST rule**: any node's value is bigger than every value in its left subtree, and and smaller than every value in its right subtree
- TODO: write an efficient search for a BST (better complexity than O(N)
- TODO: write a method to add values to a BST, while preserving the BST rule
%% Cell type:code id:894a39d2-5e3b-4178-bc1b-dedf0b5a86c0 tags:
``` python
class BSTNode:
def __init__(self, label):
self.label = label
self.left = None
self.right = None
# Category 2: functions that do some action
def dump(self, prefix="", suffix=""):
"""
prints out name of every node in the tree with some basic formatting
"""
print(prefix, self.label, suffix)
if self.left != None:
self.left.dump(prefix+"\t", "(LEFT)")
if self.right != None:
self.right.dump(prefix+"\t", "(RIGHT)")
# Category 1: functions that return some computation
def search(self, target):
"""
returns True/False, if target is somewhere in the tree
"""
if target == self.label:
return True
elif target < self.label:
if self.left != None:
if self.left.search(target):
return True
elif target > self.label:
if self.right != None:
if self.right.search(target):
return True
return False
def add(self, label):
"""
Finds the correct spot for label and adds a new node with it.
Assumes that tree already contains at least one node -> TODO: discuss why?
Raises ValueError if label is already on the tree.
"""
pass
def height(self):
"""
Calculates height of the BST.
Height: the number of nodes on the longest root-to-leaf path (including the root)
"""
pass
```
%% Cell type:markdown id:6104a9e7 tags:
### Code folding nbextension
- Go to "jupyterlab" > "Settings" > "Advanced Settings Editor" > "Notebook" > "Rulers" > enable "Code Folding" (there should be three such settings).
%% Cell type:markdown id:1d6935d8 tags:
### Recursive `add` method
- Manually creating a tree is cumbersome and subject to mistakes (violations of BST rule)
%% Cell type:code id:7047d184 tags:
``` python
root = BSTNode(10)
root.left = BSTNode(2)
root.left.left = BSTNode(1)
root.left.right = BSTNode(4)
root.left.right.right = BSTNode(8)
root.left.right.left = BSTNode(3)
root.right = BSTNode(15)
root.right.left = BSTNode(12)
root.right.right = BSTNode(19)
root.dump("", "(ROOT)")
```
%% Cell type:code id:0cd51cf2 tags:
``` python
values = [10, 2, 1, 4, 8, 3, 15, 12, 19]
root = BSTNode(values[0])
for val in values[1:]:
root.add(val)
root.dump("", "(ROOT)")
```
%% Cell type:markdown id:f9324526 tags:
### Recursive `height` method
- **Height**: the number of nodes on the longest root-to-leaf path (including the root)
- left subtree has height 4, right subtree has height 6, my height = ?
- left subtree has height 4, right subtree has height 4, my height = ?
- left subtree has height 10, right subtree has height 0, my height = ?
- left subtree has height of l, right subtree has height of r, my height = ?
- What is the simplest case for height calculation?
- What are the values of l and r in that case?
%% Cell type:code id:18d8de1d tags:
``` python
# TODO: Let's implement and invoke the height method
```
%% Cell type:markdown id:bb2057f2 tags:
### Tree containing 100 values
- let's use range(...) to produce a sequence of 100 integers
- recall that range(...) returns a sequence in increasing order
- what will be the height of this tree? **100**
%% Cell type:code id:820f3596 tags:
``` python
values = list(range(100))
# Q: Is this tree balanced?
# A:
```
%% Cell type:markdown id:af9dd1b3 tags:
#### Let's use `random` module `shuffle` function to randomly order the sequence of 100 numbers.
- in-place re-ordering of numbers (just like `sort` method)
%% Cell type:code id:c07664be tags:
``` python
values = list(range(100))
random.shuffle(values)
# Q: Is this tree balanced?
# A: depends on the shuffling, you can check using math.log2(N)
root = BSTNode(values[0])
for val in values[1:]:
root.add(val)
print(root.height())
root.dump("", "(ROOT)")
```
%% Cell type:code id:4d87a7e7 tags:
``` python
math.log2(100)
```
%% Cell type:markdown id:cf919d84 tags:
### Balanced BSTs / Self-balancing BSTs
- not a covered topic for the purpose of this course
- you can explore the below recursive function definition if you are interested
- you are **not required** to know how to do this
%% Cell type:code id:bd5aa50f tags:
``` python
# Recrusive function that
def sorted_array_to_bst(nums, bst_nums):
"""
Produces best ordering nums (a list of sorted numbers),
for the purpose of creating a balanced BST.
Writes new ordering of numbers into bst_nums.
"""
if len(nums) == 0:
return None
elif len(nums) == 1:
bst_nums.append(nums[0])
else:
mid_index = len(nums)//2
bst_nums.append(nums[mid_index])
# recurse left
left_val = sorted_array_to_bst(nums[:mid_index], bst_nums)
if left_val != None:
bst_nums.append(left_val)
# recurse right
right_val = sorted_array_to_bst(nums[mid_index+1:], bst_nums)
if right_val != None:
bst_nums.append(right_val)
```
%% Cell type:code id:98b9148d tags:
``` python
bst_nums = []
sorted_array_to_bst(list(range(5)), bst_nums)
bst_nums
```
%% Cell type:code id:f1288713 tags:
``` python
bst_nums = []
sorted_array_to_bst(list(range(100)), bst_nums)
root = BSTNode(bst_nums[0])
for val in bst_nums[1:]:
root.add(val)
print(root.height())
```
%% Cell type:code id:399fe31a tags:
``` python
bst_nums = []
sorted_array_to_bst(list(range(5)), bst_nums)
root = BSTNode(bst_nums[0])
for val in bst_nums[1:]:
root.add(val)
print(root.height())
root.dump("", "(ROOT)")
```
%% Cell type:markdown id:e042962c tags:
### Depth First Search (DFS)
- Last lecture: BST search with complexity **O(logN)**
- Finds a path from one node to another -- works on any directed graph
%% 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 = {}
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()
class Node:
def __init__(self, name):
self.name = name
self.children = []
self.graph = None # back reference
def __repr__(self):
return self.name
g = example(1)
g
```
%% Cell type:markdown id:c83e9993-765c-42a0-97f6-6277627acf95 tags:
### Testcases for DFS
%% Cell type:code id:15edd0d2 tags:
``` python
print(g.dfs_search("B", "A")) # should return False
print(g.dfs_search("B", "D")) # should return True
```
%% Cell type:code id:bbc7eba9 tags:
``` python
# DFS search
# TODO: give the actual path, not just True/False
# TODO: use a different algorithm to find the shortest path
```
%% Cell type:code id:d2b5e4a3 tags:
``` python
print(g.nodes["B"].dfs_search(g.nodes["D"]))
```
%% Cell type:code id:c5d9affc tags:
``` python
print(g.nodes["B"].dfs_search(g.nodes["A"]))
# what is wrong?
```
%% Cell type:markdown id:da658c4d tags:
### Testcases for DFS
%% Cell type:code id:32d1fa4f tags:
``` python
g
```
%% Cell type:code id:a9dfbc1e tags:
``` python
print(g.dfs_search("B", "A")) # should return
print(g.dfs_search("B", "D")) # should return
```
%% Cell type:markdown id:f521b9da tags:
### `tuple` review
- similar to lists, but immutable
- `*` operator represents replication and not multiplication for lists and tuples
- `+` operator represents concatenation and not additional for lists and tuples
%% Cell type:code id:6802d98e tags:
``` python
# this is a tuple containing 5
(3+2,)
```
%% Cell type:code id:696d0755 tags:
``` python
(3+2) # order precedence
```
%% Cell type:code id:3046fe4a tags:
``` python
# replicates item 5 three times and returns a new tuple
(3+2,) * 3
```
%% Cell type:code id:aed44ea1 tags:
``` python
(3+2) * 3 # gives us 15
```
%% Cell type:code id:d8806544 tags:
``` python
# returns a new tuple containing all items in the first tuple and
# the second tuple
(3, ) + (5, )
```
%% Cell type:markdown id:9aabb4fb tags:
### DFS search
- return the actual path rather than just returning True / False
- for example, path between B and D should be (B, C, D)
%% Cell type:markdown id:eb9a6595 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
- might not give us the shortest possible path
%% Cell type:code id:c934d86d tags:
``` python
g = example(2)
g
```
%% Cell type:code id:8c8dc14a tags:
``` python
print(g.dfs_search("A", "E")) # should return
print(g.dfs_search("E", "A")) # should return
```
File added
File added
lecture_material/10-Graph-Search-2/Priority_queue.png

32.5 KiB

lecture_material/10-Graph-Search-2/perf.png

16.4 KiB

lecture_material/10-Graph-Search-2/shortest_path.png

63.3 KiB

Source diff could not be displayed: it is too large. Options to address this: view the blob.
Source diff could not be displayed: it is too large. Options to address this: view the blob.
Source diff could not be displayed: it is too large. Options to address this: view the blob.
lecture_material/10-Graph-Search-2/work_queue_slow_operations.png

42.9 KiB

File added