2-25《啊哈算法》第5章,图的遍历原理。(2-26)
第一节,深度和广度优先指啥
针对图的遍历而言的。
常用的储存方法是图的二维数组e,图的邻接矩阵储存法。
还有很多种储存方法,如邻接表。
深度优先遍历的主要思想:沿着图的某一分支遍历直到末端,然后回朔,然后再沿着另一条进行同样的遍历,直到所有顶点都被访问过为止。
广度优先遍历的主要思想:首先以一个未被访问过的顶点作为起始顶点,访问其所有相邻的顶点,然后对每个相邻的顶点,再访问它们相邻的未被访问过的顶点,直到所有顶点都被访问过,遍历结束。
无论是深度优先变量还是广度优先遍历,本节的题目都会产生图的生成树。
什么是生成树?
如果一个无向图不包含回路,就是一棵树。
# 深度优先遍历
# 直接输入 图的邻接矩阵。
$e = [
nil,
[nil,0, 1, 1, 99, 1],
[nil,1, 0, 99, 1, 99],
[nil,1, 99, 0, 99, 1],
[nil,99, 1, 99, 0, 99],
[nil,1, 99, 1, 99, 0]
]
# 代表每个点都没有被访问,如果访问则改为1.
$book = [nil,0,0,0,0,0]
$sum = 0
$n = 5
def dfs(current)
# 输出经过的点。
p current
# 每访问一个点,sum加1
$sum += 1
# 边界,如果所有点都访问到了,退出。
if $sum == $n
return
end
# 尝试current和各个点比较,是否连接,并判断i点是否已经访问过了
i=1
for i in 1..5 do
if $e[current][i] == 1 && $book[i] == 0
$book[i] = 1
dfs(i) #从顶点i出发继续遍历。
end
end
end
$book[1] = 1 #第1个点已经访问
dfs(1)
广度优先遍历
# 直接输入 图的邻接矩阵。
$e = [
[0, 1, 1, 99, 1],
[1, 0, 99, 1, 99],
[1, 99, 0, 99, 1],
[99, 1, 99, 0, 99],
[1, 99, 1, 99, 0]
]
# 标记走访记录,0未走访,1已走访
$book = [0,0,0,0,0]
# queue队列
x = []
head = 0 #指针
tail = 1
# 当前点,第一个点矩阵中是0
head = 0
# 第一个点入队列
x << head
# 第一个点 设置为已访问
$book[head] = 1
while head < tail
i = 0
while i <= 4
if $e[head][i] == 1 && $book[i] == 0
$book[i] = 1
x << i
tail += 1
end
i += 1
end
head += 1
end
#排列输出搜索顺序。
x.each do |n|
p n + 1
end
第二节, 城市地图 -图的深度优先遍历
图分为,有向图,无方图,边也就分有无。
和一个有向边连接的点分起始点和终点。和一个点相关的边分为出边和入边。
❌:设立一个二维数组的时候,用双层嵌套。但要⚠️数据声明结构的问题
- 在循环前,先声明数组
- 在第一个循环后,第一行代码,是给二层数组声明,之后才能在内层循环中赋值。
问题:从任意1个城市到另一个城市的最短路径?hint:用深度优先搜索
# # 矩阵5*5 手输入数据。
# # 城市5个,n = 5.
# $map = []
# i = j = 1
# n = 5
# for i in 1..n do
# # 声明二层结构是数组。
# $map[i] = []
# # 给二层数组赋值。
# for j in 1..n do
# if i == j
# $map[i][j] = 0
# else
# $map[i][j] = 999
# end
# end
# end
#
# # 城市间的道路m条
# m = 8
# for i in 1..m do
# print "输入3个数,a,b,r:\n"
# a = gets.to_i
# b = gets.to_i
# r = gets.to_i
# $map[a][b] = r
# end
#
# $map.each do |n|
# p n
# end
代码:有向图。
$map = [
nil,
[nil, 0, 2, 999, 999, 10],
[nil, 999, 0, 3, 999, 7],
[nil, 4, 999, 0, 4, 999],
[nil, 999, 999, 999, 0, 5],
[nil, 999, 999, 3, 999, 0]
]
# 记录是否走过。
$book = [nil,0,0,0,0,0]
# 储存最小路径
$min = 999999
# current为当前城市,destination目的地, sum为路径和
def dfs(current, destination,sum)
# 如果当前的路径所花费大于$min,则没必要再继续尝试下去了,马上返回
if sum > $min
return
end
# border 到达终点是结束的标志。
if current == destination
p "当前:#{sum}"
if sum < $min
$min = sum
end
p "最小:#{$min}"
return
end
i = 1
for i in 1..5 do
if $map[current][i] != 999 && $map[current][i] != 0 && $book[i] == 0
$book[i] = 1
sum += $map[current][i]
dfs(i, destination,sum)
#回溯的时候,之前sum加上的路径还要减掉,
#或者直接在方法参数中写: dfs(i, destionation,sum + $map[current][i])
sum -= $map[current][i]
# 之前一步探索完毕之后,取消对城市i的标记
$book[i] = 0
end
end
end
$book[1] = 1
dfs(1,0)
第三节 广度优先遍历,求最少转机次数。
广度优先适合所有边的权重一样的时候。
问:求任意2个城市直接的最少转机次数 ?
答:
$map =[
nil,
[nil, 0, 1, 1, 999, 999],
[nil, 1, 0, 1, 1, 999],
[nil, 1, 1, 0, 1, 1],
[nil, 999, 1, 1, 0, 1],
[nil, 999, 999, 1, 1, 0]
]
# 队列queue初始化,x记录经过城市号,声明指针赋值。
x = []
head = 0
tail = 1
# 标记城市的变量
book = [nil,0,0,0,0,0]
book[1] = 1
# 开始城市是start,加入队列x, 目的地destination
print "请输入出发城市编号:(1-5)\n"
start = gets.to_i
print "请输入目的地城市编号:(1-5)\n"
destination = gets.to_i
x << start
# 记录转机数
step = []
step[head] = 0
# 当队列不为空的时候♻️
while head < tail
# cur 是当前城市
cur= head + 1
i = 1
for i in 1..5 do
if $map[cur][i] != 0 && $map[cur][i] != 999 && book[i] == 0
# 途经的城市标记下。从1直接到3,标记3。再从2到3就不需要录入队列了,因为没必要增加转机次数。
book[i] = 1
x << i
# 记录cur当前城市是第几次转机。
step[tail] = step[head] + 1
tail += 1
if x.last == destination
break
end
end
end
# 外层再写一遍,是为了省去再循环。
if x.last == destination
break
end
head += 1
end
p "需要#{step.last}次转机"