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. 在循环前,先声明数组
  2. 在第一个循环后,第一行代码,是给二层数组声明,之后才能在内层循环中赋值。 

 

问题:从任意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}次转机"

 

posted @ 2018-02-25 20:35  Mr-chen  阅读(251)  评论(0编辑  收藏  举报