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 clock
v.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.