CLRS Algos
1 Problem Solving Heuristic
Prim’s algorithm
Prim’s algorithm
1.1 DP
- State - Find a Well-founded relation (Tree, List, Etc)
- Moves -
- Validate - Check if Move fulfils constraint
2 Recursion vs Tail-recursion
We will not refer to tail-recursion as recursion.
tail-recursion can store state and is isomorphic to iterative solutions.
3 Recursion vs DP
Although we can always go from recursion TO DP, they are not the same.
Problems that require us to FIRST store local optimas work well with DP but not recursion.
Recursion has no memory.
4 Generative
- Typically problems that ask to find all possible combinations that fulfils some constraint or target
- We create a generative tree of states using a tail recursive function
= input()
mylist = []
solutionSet def func(target,buildsol,mylist...):
if target == 0: #constraint satisfied
solutionSet.append(buildsol)if target <= 0:
return #backtrack
for i in mylist:
-i,buildsol+[i],mylist...) func(target
4.0.1 Examples
- problem: Sorting => IH: Sorted
- Strengthen Problem : Quick Sort
- Strengthen problem from Sorting to placing a chosen pivot in the correct sorted position
- Must do this for all pivots
- Split : Mergesort
- Atomic: BubbleSort
- place head into correct position of IH(tail)
- Strengthen Problem : Quick Sort
- problem: Longest Common Subsequence
- Recursive, Atomic
= ["A","B","C","B","D","A","B"]
X = ["B","D","C","A","B","A"]
Y
= len(X)+1 #
m = len(Y)+1 #
n = [[ " " for row in range (0,n)] for col in range(0,m)] #
b = [[ -1 for row in range (0,n)] for col in range(0,m)] #
c for i in range(1,m):
0] = 0
c[i][for j in range(0,n):
0][j] = 0
c[
for i in range(1,m):
for j in range(1,n):
if X[i-1] == Y[j-1]: # X[i] == Y[j] in CLRS
= c[i-1][j-1]+1
c[i][j] = "D"
b[i][j] elif c[i-1][j] >= c[i][j-1]:
= c[i-1][j]
c[i][j] = "U"
b[i][j] else:
= c[i][j-1]
c[i][j] = " "
b[i][j]
def printMat(lst):
for i in lst:
print(i)
printMat(c)# [0, 0, 0, 0, 0, 0, 0]
# [0, 0, 0, 0, 1, 1, 1]
# [0, 1, 1, 1, 1, 2, 2]
# [0, 1, 1, 2, 2, 2, 2]
# [0, 1, 1, 2, 2, 3, 3]
# [0, 1, 2, 2, 2, 3, 3]
# [0, 1, 2, 2, 3, 3, 4]
# [0, 1, 2, 2, 3, 4, 4]
printMat(b)# [' ', ' ', ' ', ' ', ' ', ' ', ' ']
# [' ', 'U', 'U', 'U', 'D', ' ', 'D']
# [' ', 'D', ' ', ' ', 'U', 'D', ' ']
# [' ', 'U', 'U', 'D', ' ', 'U', 'U']
# [' ', 'D', 'U', 'U', 'U', 'D', ' ']
# [' ', 'U', 'D', 'U', 'U', 'U', 'U']
# [' ', 'U', 'U', 'U', 'D', 'U', 'D']
# [' ', 'D', 'U', 'U', 'U', 'D', 'U']
def printLCS(b,X,i,j):
if i == 0 or j == 0:
return
if b[i][j] == "D":
-1,j-1)
printLCS(b,X,iprint(X[i-1]) #X[i] in CLRS
elif b[i][j] == "U":
-1,j)
printLCS(b,X,ielse:
-1)
printLCS(b,X,i,j
len(X),len(Y))
printLCS(b,X,#> BCBA
4.0.1.0.1 Insertion Sort
- Sublist [{0..i,j}…]
- IH : [0..i] is Sorted sublist
- insert A[j] or Key into proper position in IH : [0..i]
def InsertionSort(A):
for j in range(1,len(A)):
= A[j]
key = j - 1
i while i >= 0 and A[i] > key:
+1] = A[i]
A[i= i - 1
i +1] = key
A[i=[5,2,4,6,1,3]
A
InsertionSort(A)print(A)
def trace(func):
= "| "
separate = 0
trace.recursionDepth
@wraps(func)
def trace_helper(*args, **kwargs):
print(f'{separate * trace.recursionDepth}|--> {func.__name__}({", ".join(map(str, args))})')
+= 1
trace.recursionDepth = func(*args,**kwargs)
output -= 1
trace.recursionDepth print(f'{separate * (trace.recursionDepth + 1)}|--> return {output}')
return output
return trace_helper
def insert(X,k):
= len(X)
partition for i in range(0,len(X)):
if k < X[i]:
= i
partition break
= X[:partition]
fstpart = X[partition:]
sndpart return fstpart + [k] + sndpart
@trace
def Insertsort(A,i,j):
if j == len(A)-1:
return A
else:
= A[i:j]
sublist = insert(sublist,A[j])
IH = IH + A[j+1:]
newA +1) #<-- fix this, we need to return
Insertsort(newA,i,j1,8,3,5],0,0)
Insertsort([
# |--> Insertsort([1, 8, 3, 5], 0, 0)
# | |--> Insertsort([1, 8, 3, 5], 0, 1)
# | | |--> Insertsort([1, 8, 3, 5], 0, 2)
# | | | |--> Insertsort([1, 3, 8, 5], 0, 3)
# | | | | |--> return [1, 3, 8, 5]
# | | | |--> return None
# | | |--> return None
# | |--> return None
#where is the problem?
5 Reverse Linked List
class ListNode(object):
def __init__(self, val=0, next=None):
self.val = val
self.next = None
= ListNode(val=2)
a next = ListNode(val=5)
a.next.next = ListNode(val=9)
a.
def f(x):
if x == None:
return x
if x.next == None:
return (x,x)
else:
= f(x.next)
IH_h,IH_l next = x
IH_l.next = None
x.return (IH_h,x)
print(a.next.val)
print(f(a)[0].next.next.val)
Explanation
We want to reverse the ordering of a linkedlist. access is always left to right.
INPUT
[x] ——> access
——–> ordering
GOAL
——->[x] access
<——- ordering
Arrow only symbolizes ordering NOT connection.
naive IH: simply returns head of the reversed linkedlist.
Naive Sol:
[x]
[IH]——> access
<——— ordering
What do we do with x?
We need to put x to the end of this reversedlist.
[IH]——>[x] access
We could recursively “next” down IH to the end and it would work. How about a faster sol.
Better Sol:
BetterIH: return (head,tail) of the reversed linkedlist.
[x]
[IH_h]——>[IH_l] access
<———– ordering
What do we do with [x]?
[IH_h]——>[IH_l]->[x] access
Clearly we just set the IH_l.next = x
5.1 Hashtable and Hash function Ch 11
Hash function exploits the fast look time of an array
\[ \{k_1,k_2,k_3...\} \overset{hash}{\rightarrow} h(k) \rightarrow Val \]
- Unused keys = Null Space
#a is the simplest hashtable
= [1,2,3]
a = 0
k_1 = 1
k_2 = 2
k_3 lambda hash = lambda x: x
= a[hash(k_1)]
Val_1 = a[hash(k_2)]
Val_2 = a[hash(k_3)] Val_3
- In hashtables, the Index of the Array is the output of the hash function.
- O(1) lookup is due to this