NumberMaze(i, j, M):
initialize M[1..n, 1..n].moves to -1
M[i,j].moves <- 0
Enqueue((i,j))
while the queue is not empty:
(x,y) <- Dequeue
if 1 <= x <= n and 1 <= y <= n:
ret <- (x,y).moves + 1
if (x,y) = (n,n):
return ret
checkIfMarked(x, y + M[x,y], ret)
checkIfMarked(x, y - M[x,y], ret)
checkIfMarked(x + M[x,y], y, ret)
checkIfMarked(x - M[x,y], y, ret)
return NO SOLUTION
checkIfMarked(x, y, moves):
if M[x,y].moves = -1:
M[x,y].moves <- ret
Enqueue(x, y)Keep a queue of counters (or a stack of triples (i, j, counter)).
MazeSolution(M[1...n, 1...n]){
Initialize queues Coordinates and Counter
Initialize Marked[1 .. n, 1 .. n] to hold all 0s
Enqueue(1,1) on Coordinates
Enqueue(0) on Counter
while Coordinates is not empty
(i,j) = Dequeue Coordinates
v = M[i,j]
c = (Dequeue Counter) + 1
if Marked[i,j] = 0
Marked[i,j] <- 1
if i=n and j=n
return c
if 0 < i+v < n+1 and Marked[i+v, j] = 0
Enqueue(i+v,j) on Coordinates, Enqueue(c) on Counter
if 0 < i-v < n+1 and Marked[i-v, j] = 0
Enqueue(i-v,j) on Coordinates, Enqueue(c) on Counter
if 0 < j+v < n+1 and Marked[i, j+v] = 0
Enqueue(i,j+v) on Coordinates, Enqueue(c) on Counter
if 0 < j-v < n+1 and Marked[i, j-v] = 0
Enqueue(i,j-v) on Coordinates, Enqueue(c) on Counter
return NO SOLUTION
}Combine the counter and the marker:
NumberMaze(M[1..n,1..n]):
Enqueue((1,1), 0)
initialize C[1..n,1..n] where every value is n^2
while the queue is not empty
(i,j), count <- Dequeue
count <- count + 1
if count < C[i,j]
C[i,j] <- count
val <- M[i,j]
if i+val <= n
Enqueue((i+val,j), count)
if i-val >= 1
Enqueue((i-val,j), count)
if j+val <= n
Enqueue((i,j+val), count)
if j-val >= 1
Enqueue((i,j-val), count)
if C[n,n]==n^2
return NO SOLUTION
else
return C[n,n]Corollary: In an undirected graph, the sum of the degrees is twice the number of edges.
library(dequer)
Mbook <- matrix(c(3,5,7,4,6,
5,3,1,5,3,
2,8,3,1,4,
4,5,7,2,3,
3,1,3,2,0), byrow = TRUE, nrow = 5)
minMoves <- function(s1,s2,M) {
n <- nrow(M)
marked <- matrix(rep(FALSE, n^2), nrow = n)
q <- queue()
pushback(q, c(s1,s2,0)) # third number is depth
while(length(q) != 0) {
v <- pop(q)
i <- v[1]
j <- v[2]
depth <- v[3]
if(!marked[i,j]) {
marked[i,j] <- TRUE
if(i==n && j==n)
return(depth)
x <- M[i,j]
if(i+x <= n) pushback(q, c(i+x,j,depth+1))
if(i-x >= 1) pushback(q, c(i-x,j,depth+1))
if(j+x <= n) pushback(q, c(i,j+x,depth+1))
if(j-x >= 1) pushback(q, c(i,j-x,depth+1))
}
}
return("NO SOLUTION")
}
minMoves(1,1,Mbook)[1] 8
import queue
import numpy as np
def ShortestPathCount(arr):
q = queue.Queue()
q.put((0,0))
n = arr.shape[0] -1
visited = np.zeros((n+1,n+1), dtype=bool)
count = 0
children = 0
children_remaining = 1
while q.qsize() > 0:
val = q.get()
first = val[0]
second = val[1]
if visited[first,second] == False:
k = arr[first,second].copy()
visited[first, second] = True
if visited[n,n] == True:
break
if (first+k <= n):
q.put((first+k,second))
children += 1
if (first-k >= 1):
q.put((first-k,second))
children += 1
if (second+k <= n):
q.put((first,second+k))
children += 1
if (second-k >= 1):
q.put((first,second-k))
children += 1
children_remaining -= 1
if children_remaining == 0:
children_remaining = children
children = 0
count += 1
if visited[n,n] == True:
print(f"{count}")
else:
print("No possible path found")
path = np.array([[3,5,7,4,6],
[5,3,1,5,3],
[2,8,3,1,4],
[4,5,7,2,3],
[3,1,3,2,0]])
ShortestPathCount(path)8
In many applications, the extent of the graph is unknown a priori.
We can operate as if we have an adjacency list, but it doesn’t get discovered until we run a WFS.
Not “exactly” the same as WFS with a stack:
v is pushed onto the recursion stack).Add a clock and two vertex attributes:
DFS(v, clock):
mark v
clock <- clock + 1; v.pre <- clock
for each edge vw
if w is unmarked
w.parent <- v
clock <- DFS(w, clock)
clock <- clock + 1; v.post <- clock
return clockv.pre is the clock value when the vertex v is first discovered and marked.
v.post is the clock value when we are done exploring v.
DFS algorithm is called on the following graph as DFS(v1, 0). When a vertex v has more than one outgoing edge vw, assume that the for-loop considers the vertices w \(=v_i\) in order of subscript. For each vertex v, compute v.pre and v.post.| Table #1 | Table #2 | Table #3 | Table #4 | Table #5 | Table #6 | Table #7 |
|---|---|---|---|---|---|---|
| Bri | Trevor | Grace | Josiah | Jack | Logan | Isaac |
| Ethan | Kristen | Jordan | Nathan | Graham | Drake | Claire |
| James | Levi | Kevin | John | Blake | Talia | Andrew |
DFSAll is a “wrapper” function that ensures that we get a spanning forest containing all the vertices of a graph \(G\).Suppose we run DFS on a graph \(G\).
v.pre.v.post.If \(G\) is a tree, these notions are the same as preorder/postorder traversals.
For any two vertices u and v, exactly one of the following must hold:
[u.pre..u.post] and [v.pre..v.post] do not overlap.
[u.pre..u.post] is contained in [v.pre..v.post].
u is a descendant of v in a depth-first tree.[v.pre..v.post] is contained in [u.pre..u.post].
v is a descendant of u in a depth-first tree.Notice: .pre and .post must be nested like parentheses:
Every edge uv in the graph is one of the following.
uv is in a/the depth-first tree.
DFS(u) calls DFS(v) directly, so u = v.parent.v is a descendant of u, but not its child.
u.pre \(<\) v.pre \(<\) v.post \(<\) u.post
uv goes backwards up a depth-first tree.
v.pre \(<\) u.pre \(<\) u.post \(<\) v.post
uv connects different branches in the depth-first forest.
v.post \(<\) u.pre
DFS. In addition, make your graph complicated enough to have at least one of each type of edge: tree, forward, back, and cross.