问题描述
给定一系列城市坐标,以及城市之间的路径和路径的权值,找到从初始地点 Arad到 目的地点 Bucharest 的一条路径。初始节点和目标节点可更改。
算法原理
前言
需要使用到 open 表和 close 表, 其中 open 表存储的是还未遍历的节点,close 表存储的是已经遍历过的节点。
每一次都需要从 open 表中选择一个节点进行遍历,之后将这一个已经遍历过的节点从 open 表中移出,并将其加入到 close 表中,再将可以从这个节点扩展的新的未遍历过的节点加入 open 表中,不断重复这个过程直到找到目标节点或者将图的所有节点都遍历完毕。
不同遍历方法的不同,其实就是如何从open表中选择下一个节点来进行遍历的这种选择方法的不同。
深度优先算法
它的基本思想是尽可能深入图的分支,直到无法继续深入时,再回溯到上一层,继续访问其他分支。
宽度优先算法
它的基本思想是逐层访问节点,从根节点开始,先访问所有相邻节点,再访问这些相邻节点的相邻节点,依此类推。
A*算法
它的基本思想是存储每个待遍历节点的当前已经产生的代价 g(n),以及预期未来将要产生的代价 h(n),然后预期的实际代价就是
f
(
n
)
=
g
(
n
)
+
h
(
n
)
f(n)=g(n)+h(n)
f(n)=g(n)+h(n)
g(n):初始节点到节点n的实际代价,也就是当前代价。
h(n):节点n到目标节点的最优路径代价估计值,也就是未来代价。
f(n):初始节点到目标节点的最优路径代价估计值,也就是预期总代价。
然后根据 g(n) 的大小来判断下一次应该选择哪一个节点来进行访问。
实现结果
深度优先
输出描述
open表中 [[[15,0],[16,0]]] 表示未遍历的节点是15号和16号,它们的前驱节点是0号。
closed表中表示已经遍历过的节点序号。
search_path中展示的是搜索路径
city_path中展示的是得到的城市路径结果
['Arad', 'Bucharest', 'Craiova', 'Drobeta', 'Eforie', 'Fagaras', 'Giurgiu', 'Hirsova', 'lasi', 'Lugoj', 'Mehadia', 'Neamt', 'Oradea', 'Pitesti', 'Rimnicu', 'Sibiu', 'Timisoara', 'Urziceni', 'Vaslui', 'Zerind']
[[0, '3', 19, '75', 15, '140', 16, '118'], [1, '4', 17, '85', 13, '101', 6, '90', 5, '211'], [2, '3', 3, '120', 14, '146', 13, '138'], [3, '2', 10, '75', 2, '120'], [4, '1', 7, '86'], [5, '2', 15, '99', 1, '211'], [6, '1', 1, '90'], [7, '2', 17, '98', 4, '86'], [8, '2', 18, '92', 11, '87'], [9, '2', 16, '111', 10, '70'], [10, '2', 9, '70', 3, '75'], [11, '1', 8, '87'], [12, '2', 19, '71', 15, '151'], [13, '3', 14, '97', 1, '101', 2, '138'], [14, '3', 15, '80', 13, '97', 2, '146'], [15, '4', 14, '80', 5, '99', 0, '140', 12, '151'], [16, '2', 9, '111', 0, '118'], [17, '3', 18, '142', 1, '85', 7, '98'], [18, '2', 8, '92', 17, '142'], [19, '2', 12, '71', 0, '75']]
start_index 0
end_index 4
start_time 9.7544861
下面是深度优先算法
open [[[15, 0], [16, 0]]]
close [0]
open [[[15, 0], [16, 0]]]
close [0, 19]
open [[[15, 0], [16, 0]]]
close [0, 19, 12]
open [[[15, 0], [16, 0]], [[5, 15]]]
close [0, 19, 12, 15]
open [[[15, 0], [16, 0]], [[5, 15]], [[2, 14]]]
close [0, 19, 12, 15, 14]
open [[[15, 0], [16, 0]], [[5, 15]], [[2, 14]], [[2, 13]]]
close [0, 19, 12, 15, 14, 13]
open [[[15, 0], [16, 0]], [[5, 15]], [[2, 14]], [[2, 13]], [[6, 1], [5, 1]]]
close [0, 19, 12, 15, 14, 13, 1]
open [[[15, 0], [16, 0]], [[5, 15]], [[2, 14]], [[2, 13]], [[6, 1], [5, 1]], [[7, 17]]]
close [0, 19, 12, 15, 14, 13, 1, 17]
open [[[15, 0], [16, 0]], [[5, 15]], [[2, 14]], [[2, 13]], [[6, 1], [5, 1]], [[7, 17]]]
close [0, 19, 12, 15, 14, 13, 1, 17, 18]
open [[[15, 0], [16, 0]], [[5, 15]], [[2, 14]], [[2, 13]], [[6, 1], [5, 1]], [[7, 17]]]
close [0, 19, 12, 15, 14, 13, 1, 17, 18, 8]
open [[[15, 0], [16, 0]], [[5, 15]], [[2, 14]], [[2, 13]], [[6, 1], [5, 1]]]
close [0, 19, 12, 15, 14, 13, 1, 17, 18, 8, 11]
open [[[15, 0], [16, 0]], [[5, 15]], [[2, 14]], [[2, 13]], [[6, 1], [5, 1]]]
close [0, 19, 12, 15, 14, 13, 1, 17, 18, 8, 11, 7]
扩展结点格数 13
end_time 9.7548489
search_path [[0, -1], [19, 0], [12, 19], [15, 12], [14, 15], [13, 14], [1, 13], [17, 1], [18, 17], [8, 18], [11, 8], [7, 17], [4, 7]]
city_path [0, 19, 12, 15, 14, 13, 1, 17, 7, 4]
路径结点个数 10
计算时间 0.0003628000000013287 秒
权重 844
宽度优先
输出描述
start_index 0
end_index 4
start_time 679.3864589
下面是宽度优先算法
open [[15, 0], [16, 0]]
close [0]
open [[16, 0], [12, 19]]
close [0, 19]
open [[12, 19], [14, 15], [5, 15], [12, 15]]
close [0, 19, 15]
open [[14, 15], [5, 15], [12, 15], [9, 16]]
close [0, 19, 15, 16]
open [[5, 15], [12, 15], [9, 16]]
close [0, 19, 15, 16, 12]
open [[12, 15], [9, 16], [13, 14], [2, 14]]
close [0, 19, 15, 16, 12, 14]
open [[9, 16], [13, 14], [2, 14], [1, 5]]
close [0, 19, 15, 16, 12, 14, 5]
open [[13, 14], [2, 14], [1, 5]]
close [0, 19, 15, 16, 12, 14, 5, 12]
open [[2, 14], [1, 5], [10, 9]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9]
open [[1, 5], [10, 9], [1, 13], [2, 13]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13]
open [[10, 9], [1, 13], [2, 13], [3, 2]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2]
open [[1, 13], [2, 13], [3, 2], [17, 1], [6, 1]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1]
open [[2, 13], [3, 2], [17, 1], [6, 1], [3, 10]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10]
open [[3, 2], [17, 1], [6, 1], [3, 10], [17, 1], [6, 1]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1]
open [[17, 1], [6, 1], [3, 10], [17, 1], [6, 1], [3, 2]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2]
open [[6, 1], [3, 10], [17, 1], [6, 1], [3, 2]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3]
open [[3, 10], [17, 1], [6, 1], [3, 2], [18, 17], [7, 17]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3, 17]
open [[17, 1], [6, 1], [3, 2], [18, 17], [7, 17]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3, 17, 6]
open [[6, 1], [3, 2], [18, 17], [7, 17]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3, 17, 6, 3]
open [[3, 2], [18, 17], [7, 17], [18, 17], [7, 17]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3, 17, 6, 3, 17]
open [[18, 17], [7, 17], [18, 17], [7, 17]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3, 17, 6, 3, 17, 6]
open [[7, 17], [18, 17], [7, 17]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3, 17, 6, 3, 17, 6, 3]
open [[18, 17], [7, 17], [8, 18]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3, 17, 6, 3, 17, 6, 3, 18]
open [[7, 17], [8, 18], [4, 7]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3, 17, 6, 3, 17, 6, 3, 18, 7]
open [[8, 18], [4, 7], [8, 18]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3, 17, 6, 3, 17, 6, 3, 18, 7, 18]
open [[4, 7], [8, 18], [4, 7]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3, 17, 6, 3, 17, 6, 3, 18, 7, 18, 7]
open [[8, 18], [4, 7], [11, 8]]
close [0, 19, 15, 16, 12, 14, 5, 12, 9, 13, 2, 1, 10, 1, 2, 3, 17, 6, 3, 17, 6, 3, 18, 7, 18, 7, 8]
扩展结点格数 28
end_time 679.3870654
search_path [[0, -1], [19, 0], [15, 0], [16, 0], [12, 19], [14, 15], [5, 15], [12, 15], [9, 16], [13, 14], [2, 14], [1, 5], [10, 9], [1, 13], [2, 13], [3, 2], [17, 1], [6, 1], [3, 10], [17, 1], [6, 1], [3, 2], [18, 17], [7, 17], [18, 17], [7, 17], [8, 18], [4, 7]]
city_path [0, 15, 5, 1, 17, 7, 4]
路径结点个数 7
计算时间 0.000606500000003507 秒
权重 719
A* 算法
输出描述
open 表中 [19, 0, 75, 512, 587] 表示未遍历的节点是19号节点,它的前驱节点是0号节点,已经产生的代价是75,预期还要产生的代价是512,预期实际会产生的总代价是75+512=587。
start_index 0
end_index 4
start_time 1251.4140457
下面是A*算法
open_nodes [[19, 0, 75, 512, 587], [16, 0, 118, 482, 600]]
close_nodes [0]
open_nodes [[19, 0, 75, 512, 587], [16, 0, 118, 482, 600], [14, 15, 220, 349, 569], [12, 15, 291, 512, 803]]
close_nodes [0, 15]
open_nodes [[19, 0, 75, 512, 587], [16, 0, 118, 482, 600], [12, 15, 291, 512, 803], [1, 5, 450, 165, 615]]
close_nodes [0, 15, 5]
open_nodes [[19, 0, 75, 512, 587], [16, 0, 118, 482, 600], [12, 15, 291, 512, 803], [1, 5, 450, 165, 615], [2, 14, 366, 309, 675]]
close_nodes [0, 15, 5, 14]
open_nodes [[19, 0, 75, 512, 587], [16, 0, 118, 482, 600], [12, 15, 291, 512, 803], [1, 5, 450, 165, 615], [2, 14, 366, 309, 675], [2, 13, 455, 309, 764]]
close_nodes [0, 15, 5, 14, 13]
open_nodes [[16, 0, 118, 482, 600], [12, 15, 291, 512, 803], [1, 5, 450, 165, 615], [2, 14, 366, 309, 675], [2, 13, 455, 309, 764], [17, 1, 503, 120, 623], [6, 1, 508, 188, 696]]
close_nodes [0, 15, 5, 14, 13, 1]
open_nodes [[12, 15, 291, 512, 803], [1, 5, 450, 165, 615], [2, 14, 366, 309, 675], [2, 13, 455, 309, 764], [17, 1, 503, 120, 623], [6, 1, 508, 188, 696], [12, 19, 146, 512, 658]]
close_nodes [0, 15, 5, 14, 13, 1, 19]
open_nodes [[12, 15, 291, 512, 803], [2, 14, 366, 309, 675], [2, 13, 455, 309, 764], [17, 1, 503, 120, 623], [6, 1, 508, 188, 696], [12, 19, 146, 512, 658], [9, 16, 229, 406, 635]]
close_nodes [0, 15, 5, 14, 13, 1, 19, 16]
open_nodes [[12, 15, 291, 512, 803], [2, 14, 366, 309, 675], [2, 13, 455, 309, 764], [6, 1, 508, 188, 696], [12, 19, 146, 512, 658], [9, 16, 229, 406, 635], [17, 1, 503, 120, 623], [6, 1, 508, 188, 696]]
close_nodes [0, 15, 5, 14, 13, 1, 19, 16, 1]
open_nodes [[12, 15, 291, 512, 803], [2, 14, 366, 309, 675], [2, 13, 455, 309, 764], [6, 1, 508, 188, 696], [12, 19, 146, 512, 658], [9, 16, 229, 406, 635], [6, 1, 508, 188, 696], [18, 17, 645, 160, 805], [7, 17, 601, 63, 664]]
close_nodes [0, 15, 5, 14, 13, 1, 19, 16, 1, 17]
open_nodes [[12, 15, 291, 512, 803], [2, 14, 366, 309, 675], [2, 13, 455, 309, 764], [6, 1, 508, 188, 696], [12, 19, 146, 512, 658], [6, 1, 508, 188, 696], [18, 17, 645, 160, 805], [7, 17, 601, 63, 664], [18, 17, 645, 160, 805], [7, 17, 601, 63, 664]]
close_nodes [0, 15, 5, 14, 13, 1, 19, 16, 1, 17, 17]
open_nodes [[12, 15, 291, 512, 803], [2, 14, 366, 309, 675], [2, 13, 455, 309, 764], [6, 1, 508, 188, 696], [6, 1, 508, 188, 696], [18, 17, 645, 160, 805], [7, 17, 601, 63, 664], [18, 17, 645, 160, 805], [7, 17, 601, 63, 664], [10, 9, 299, 396, 695]]
close_nodes [0, 15, 5, 14, 13, 1, 19, 16, 1, 17, 17, 9]
open_nodes [[12, 15, 291, 512, 803], [2, 14, 366, 309, 675], [2, 13, 455, 309, 764], [6, 1, 508, 188, 696], [6, 1, 508, 188, 696], [18, 17, 645, 160, 805], [18, 17, 645, 160, 805], [7, 17, 601, 63, 664], [10, 9, 299, 396, 695]]
close_nodes [0, 15, 5, 14, 13, 1, 19, 16, 1, 17, 17, 9, 12]
open_nodes [[12, 15, 291, 512, 803], [2, 14, 366, 309, 675], [2, 13, 455, 309, 764], [6, 1, 508, 188, 696], [6, 1, 508, 188, 696], [18, 17, 645, 160, 805], [18, 17, 645, 160, 805], [10, 9, 299, 396, 695], [4, 7, 687, 0, 687]]
close_nodes [0, 15, 5, 14, 13, 1, 19, 16, 1, 17, 17, 9, 12, 7]
open_nodes [[12, 15, 291, 512, 803], [2, 13, 455, 309, 764], [6, 1, 508, 188, 696], [6, 1, 508, 188, 696], [18, 17, 645, 160, 805], [18, 17, 645, 160, 805], [10, 9, 299, 396, 695], [4, 7, 687, 0, 687], [4, 7, 687, 0, 687]]
close_nodes [0, 15, 5, 14, 13, 1, 19, 16, 1, 17, 17, 9, 12, 7, 7]
open_nodes [[12, 15, 291, 512, 803], [2, 13, 455, 309, 764], [6, 1, 508, 188, 696], [6, 1, 508, 188, 696], [18, 17, 645, 160, 805], [18, 17, 645, 160, 805], [10, 9, 299, 396, 695], [4, 7, 687, 0, 687], [3, 2, 486, 397, 883]]
close_nodes [0, 15, 5, 14, 13, 1, 19, 16, 1, 17, 17, 9, 12, 7, 7, 2]
扩展结点格数 17
end_time 1251.4148604
search_path [[0, -1, 0, 511, 511], [15, 0, 140, 391, 531], [5, 15, 239, 300, 539], [14, 15, 220, 349, 569], [13, 14, 317, 253, 570], [1, 13, 418, 165, 583], [19, 0, 75, 512, 587], [16, 0, 118, 482, 600], [1, 5, 450, 165, 615], [17, 1, 503, 120, 623], [17, 1, 503, 120, 623], [9, 16, 229, 406, 635], [12, 19, 146, 512, 658], [7, 17, 601, 63, 664], [7, 17, 601, 63, 664], [2, 14, 366, 309, 675], [4, 7, 687, 0, 687]]
city_path [0, 15, 14, 13, 1, 17, 7, 4]
路径结点个数 8
计算时间 0.0008146999998643878 秒
权重 687
数据文件
./cityinfo/cityposi.txt
Arad 91 492
Bucharest 400 327
Craiova 253 288
Drobeta 165 299
Eforie 562 293
Fagaras 305 449
Giurgiu 375 270
Hirsova 534 350
lasi 473 506
Lugoj 165 379
Mehadia 168 339
Neamt 406 537
Oradea 131 571
Pitesti 320 368
Rimnicu 233 410
Sibiu 207 457
Timisoara 94 410
Urziceni 456 350
Vaslui 509 444
Zerind 108 531
./cityinfo/cityname.txt
Arad
Bucharest
Craiova
Drobeta
Eforie
Fagaras
Giurgiu
Hirsova
lasi
Lugoj
Mehadia
Neamt
Oradea
Pitesti
Rimnicu
Sibiu
Timisoara
Urziceni
Vaslui
Zerind
./cityinfo/cityinfo.txt
Arad 3 Zerind 75 Sibiu 140 Timisoara 118
Bucharest 4 Urziceni 85 Pitesti 101 Giurgiu 90 Fagaras 211
Craiova 3 Drobeta 120 Rimnicu 146 Pitesti 138
Drobeta 2 Mehadia 75 Craiova 120
Eforie 1 Hirsova 86
Fagaras 2 Sibiu 99 Bucharest 211
Giurgiu 1 Bucharest 90
Hirsova 2 Urziceni 98 Eforie 86
lasi 2 Vaslui 92 Neamt 87
Lugoj 2 Timisoara 111 Mehadia 70
Mehadia 2 Lugoj 70 Drobeta 75
Neamt 1 lasi 87
Oradea 2 Zerind 71 Sibiu 151
Pitesti 3 Rimnicu 97 Bucharest 101 Craiova 138
Rimnicu 3 Sibiu 80 Pitesti 97 Craiova 146
Sibiu 4 Rimnicu 80 Fagaras 99 Arad 140 Oradea 151
Timisoara 2 Lugoj 111 Arad 118
Urziceni 3 Vaslui 142 Bucharest 85 Hirsova 98
Vaslui 2 lasi 92 Urziceni 142
Zerind 2 Oradea 71 Arad 75
完整代码
#!usr/bin/python
# -*- coding: utf-8 -*-
"""
基于三种搜索算法
解决罗马尼亚度假问题
"""
import math
import tkinter as tk
import numpy as np
import timeit
# 创建root窗口
root = tk.Tk()
root.title("A*Problem")
root.geometry("1200x700")
# 状态转移变量
curr_choose = "" # 当前的选择状态,有start和end两种,初始状态为空
start_city = "" # 当前选择的起始城市
end_city = "" # 当前选择的目标城市
start_city_button = None # 起始城市按钮
end_city_button = None # 目标城市按钮
how_search = tk.IntVar() # 选择search的算法的种类 0:深度优先 1:宽度优先 2:A*算法
# 搜索时的记录变量
start_index = 0 # 起始城市的序号
end_index = 1 # 结束城市的序号
curr_index = [0, -1] # 当前遍历城市的序号,第一位表示当前序号,第二位表示父节点的序号
open_nodes = [] # open表,记录下一次可扩展的结点
close_nodes = [] # close表,记录已经遍历过的结点
search_path = [] # 记录遍历城市的路径
city_path = [] # 记录最终城市的路径
pre_city_path = [] # 记录前一次搜索结果的路径
start_time = 0 # 搜索算法开始的时间
end_time = 0 # 搜索算法结束的时间
def get_position():
# 获取城市位置信息
with open('./cityinfo/cityposi.txt', 'r') as f1:
cityposi = [row.strip().split(' ') for row in f1.readlines()]
npcityposi1 = np.array(cityposi)
return npcityposi1, np.int32(npcityposi1[:, 1:]) # 存储城市的位置
def get_city_connection_info():
# 获取城市之间相连的信息
cityconnection0 = [] # 存储城市之间的连接情况
with open('./cityinfo/cityinfo.txt', 'r') as f:
cityconnect = [row.strip().split(' ') for row in f.readlines()]
# 将数据中的无用数据去除
for m_i in range(len(cityconnect)):
cityconnection0.append([])
for m_j in range(len(cityconnect[m_i])):
if cityconnect[m_i][m_j] != '':
cityconnection0[m_i].append(cityconnect[m_i][m_j])
citynames0 = [cityconnection0[m_i][0] for m_i in range(len(cityconnection0))]
# 将数据中城市名字转化为对应的城市序号
for m_i in range(len(cityconnection0)):
for m_j in range(len(cityconnection0[m_i])):
for z in range(len(citynames0)):
if cityconnection0[m_i][m_j] == citynames0[z]:
cityconnection0[m_i][m_j] = z
return citynames0, cityconnection0
def start_button_click():
global start_label1
global end_label1
global curr_choose
start_label1.config(bg="red")
end_label1.config(bg="white")
curr_choose = "start"
def end_button_click():
global start_label1
global end_label1
global curr_choose
start_label1.config(bg="white")
end_label1.config(bg="green")
curr_choose = "end"
def choose_citys(name, m_button):
global curr_choose
global start_city
global end_city
global start_city_button
global end_city_button
global start_index
global end_index
global citynames
if curr_choose == "start":
if start_city_button is not None:
start_city_button.config(bg="#808080")
start_city = name
m_button.config(bg="red")
start_city_button = m_button
for m_i in range(len(citynames)):
if citynames[m_i] == start_city:
start_index = m_i
break
elif curr_choose == "end":
if end_city_button is not None:
end_city_button.config(bg="#808080")
end_city = name
m_button.config(bg="green")
end_city_button = m_button
for m_i in range(len(citynames)):
if citynames[m_i] == end_city:
end_index = m_i
break
# print("end_city", end_city)
# print("end_indew", end_index)
# print("name", name)
# print("cityname", citynames[end_index])
def citys_distance(index1, index2):
global cityposition
cityx1, cityy1 = cityposition[index1]
cityx2, cityy2 = cityposition[index2]
c_distance = math.sqrt((cityx2 - cityx1) ** 2 + (cityy2 - cityy1) ** 2)
return int(c_distance)
def start_to_curr_distance(index1, index2):
"""
:param index1: 当前扩展结点的序号
:param index2: 当前扩展结点父节点的序号
:return:当前扩展结点的当前代价
"""
global search_path
global cityconnection
sc_distance = 0 # 当前结点与其父节点的权重
for m_i in range(len(cityconnection[index2])):
if cityconnection[index2][m_i] == index1:
sc_distance = cityconnection[index2][m_i + 1]
break
s_distance = 0 # 当前扩展结点父节点的当前代价
for m_i in range(len(search_path)):
if search_path[m_i][0] == index2:
s_distance = search_path[m_i][2]
break
return int(sc_distance) + int(s_distance)
def begin_search():
"""
用来查找两座城市之间的路径
:return: None
"""
global start_index # 起始城市的序号
global end_index # 结束城市的序号
global curr_index # 当前遍历城市的序号,第一位表示当前序号,第二位表示父节点的序号
global open_nodes # open表,记录下一次可扩展的结点
global close_nodes # close表,记录已经遍历过的结点
global search_path # 记录遍历城市的路径
global city_path # 记录最终城市的路径
global how_search # 选择search的算法的种类 0:深度优先 1:宽度优先 2:A*算法
global pre_city_path # 记录前一次搜索结果的路径
global canvas # 用于改变线的颜色
global citys # 用于改变城市的颜色
global start_time
global end_time
print("start_index", start_index)
print("end_index", end_index)
# 开始搜索
# 将数据重新初始化
curr_index = [start_index, -1]
open_nodes = []
close_nodes = []
search_path = []
city_path = []
# 将之前的搜索结果清空
if len(pre_city_path) != 0:
# 将线恢复为正常色
for m_i in range(len(pre_city_path) - 1):
mm_x1 = citys[pre_city_path[m_i]][2]
mm_y1 = citys[pre_city_path[m_i]][3]
mm_x2 = citys[pre_city_path[m_i + 1]][2]
mm_y2 = citys[pre_city_path[m_i + 1]][3]
canvas.create_line(mm_x1, mm_y1, mm_x2, mm_y2) # 绘制城市连接线
# 将按钮恢复为正常色
for m_i in range(1, len(pre_city_path) - 1):
m_button = citys[pre_city_path[m_i]][0]
m_button.config(bg="#808080")
start_time = timeit.default_timer()
print("start_time", start_time)
if how_search.get() == 0:
print("下面是深度优先算法")
# 未找到未找到目标城市就继续查询(深度优先)
search_path.append(curr_index)
while curr_index[0] != end_index:
# visit curr_index结点
open_nodes.append([])
for m_i in range(2, len(cityconnection[curr_index[0]]), 2):
is_add = 1
for m_j in range(len(close_nodes)):
if close_nodes[m_j] == cityconnection[curr_index[0]][m_i]:
is_add = 0 # 如果当前的结点已经遍历过了,那么就不将该结点加入到open表里
if is_add == 1:
open_nodes[-1].append([cityconnection[curr_index[0]][m_i], curr_index[0]])
if len(open_nodes) != 0:
if len(open_nodes[-1]) == 0:
open_nodes.pop(-1)
close_nodes.append(curr_index[0]) # 将该轮已经遍历的结点存入close表中
curr_index = open_nodes[-1][0] # 选取下一个要遍历的结点
open_nodes[-1].pop(0) # 将下一个要遍历的结点从open表里删除
if len(open_nodes[-1]) == 0:
open_nodes.pop(-1)
else:
print("have no path")
break
print("open", open_nodes)
print("close", close_nodes)
search_path.append(curr_index)
# 深度优先算法结束
elif how_search.get() == 1:
print("下面是宽度优先算法")
# 未找到目标城市就继续查询(宽度优先)
search_path.append(curr_index)
while curr_index[0] != end_index:
# visit curr_index结点
for m_i in range(2, len(cityconnection[curr_index[0]]), 2):
is_add = 1
for m_j in range(len(close_nodes)):
if close_nodes[m_j] == cityconnection[curr_index[0]][m_i]:
is_add = 0 # 如果当前的结点已经遍历过了,那么就不将该结点加入到open表里
if is_add == 1:
open_nodes.append([cityconnection[curr_index[0]][m_i], curr_index[0]])
if len(open_nodes) != 0:
close_nodes.append(curr_index[0]) # 将该轮已经遍历的结点存入close表中
curr_index = open_nodes[0] # 选取下一个要遍历的结点
open_nodes.pop(0) # 将下一个要遍历的结点从open表里删除
else:
print("have no path")
break
print("open", open_nodes)
print("close", close_nodes)
search_path.append(curr_index)
# 宽度优先算法结束
elif how_search.get() == 2:
print("下面是A*算法")
# 未找到目标城市就继续查询(A*算法)
# print("cityposition", cityposition)
curr_index.append(0) # 当前代价
curr_index.append(citys_distance(start_index, end_index)) # 未来代价
curr_index.append(curr_index[2] + curr_index[3]) # 总代价
search_path.append(curr_index)
while curr_index[0] != end_index:
# visit curr_index结点
for m_i in range(2, len(cityconnection[curr_index[0]]), 2):
is_add = 1
for m_j in range(len(close_nodes)):
if close_nodes[m_j] == cityconnection[curr_index[0]][m_i]:
is_add = 0 # 如果当前的结点已经遍历过了,那么就不将该结点加入到open表里
if is_add == 1:
third_number = start_to_curr_distance(cityconnection[curr_index[0]][m_i], curr_index[0])
fourth_number = citys_distance(cityconnection[curr_index[0]][m_i], end_index)
open_nodes.append([cityconnection[curr_index[0]][m_i], # 扩展结点的序号
curr_index[0], # 扩展结点的父结点的序号
third_number, # 扩展结点的当前代价
fourth_number, # 扩展结点的未来代价
third_number + fourth_number]) # 扩展结点的总代价
if len(open_nodes) != 0:
close_nodes.append(curr_index[0]) # 将该轮已经遍历的结点存入close表中
fifth_numbers = [node[4] for node in open_nodes] # 提取每个元素的第5位数字
max_number = min(fifth_numbers) # 找到最小的数字
min_index = fifth_numbers.index(max_number) # 返回该数字对应的元素在open_nodes中的序号
curr_index = open_nodes[min_index] # 选取下一个要遍历的结点
open_nodes.pop(min_index) # 将下一个要遍历的结点从open表里删除
else:
print("have no path")
break
print("open_nodes", open_nodes)
print("close_nodes", close_nodes)
search_path.append(curr_index)
# A*算法结束
# 得到最终的路径
print("扩展结点格数", len(search_path))
city_path.insert(0, curr_index[0]) # 添加目标城市
while curr_index[0] != start_index:
city_path.insert(0, curr_index[1]) # 添加目标城市的前一个城市
for m_i in range(len(search_path)):
if search_path[m_i][0] == curr_index[1]:
curr_index = search_path[m_i]
if curr_index[1] == -1:
break
pre_city_path = city_path
end_time = timeit.default_timer()
print("end_time", end_time)
print("search_path", search_path)
print("city_path", city_path)
print("路径结点个数", len(city_path))
print("计算时间", float(end_time) - float(start_time), "秒")
# print("end_index", end_index)
# print("start_index", start_index)
quzhong = 0
# print("city_path", city_path)
# print("city_connection", cityconnection)
for m_i in range(len(city_path)-1):
# print("city_path[m_i]", city_path[m_i])
for m_j in range(2, len(cityconnection[city_path[m_i]]), 2):
# print(city_path[m_i+1], cityconnection[city_path[m_i]][m_j])
if city_path[m_i+1] == cityconnection[city_path[m_i]][m_j]:
# print("city_path[m_i+1]", city_path[m_i+1])
# print(cityconnection[city_path[m_i]][m_j], cityconnection[city_path[m_i]][m_j+1])
quzhong += int(cityconnection[city_path[m_i]][m_j+1])
print("权重", quzhong)
# 将路径用颜色标出
for m_i in range(len(city_path)-1):
mm_x1 = citys[city_path[m_i]][2]
mm_y1 = citys[city_path[m_i]][3]
mm_x2 = citys[city_path[m_i+1]][2]
mm_y2 = citys[city_path[m_i+1]][3]
m_line = canvas.create_line(mm_x1, mm_y1, mm_x2, mm_y2) # 绘制城市连接线
canvas.itemconfig(m_line, fill="blue")
for m_i in range(1, len(city_path)-1):
m_button = citys[city_path[m_i]][0]
m_button.config(bg="blue")
# 获得数据
npcityposi, cityposition = get_position()
citynames, cityconnection = get_city_connection_info()
print(citynames)
print(cityconnection)
canvas = tk.Canvas(root, width=1000, height=700)
canvas.place(x=0, y=0)
# 将城市的位置画在root窗口内
xmin = np.min(cityposition[:, 0])
xmax = np.max(cityposition[:, 0])
ymin = np.min(cityposition[:, 1])
ymax = np.max(cityposition[:, 1])
citys = []
for i in range(len(npcityposi)):
win_x = int((int(npcityposi[i][1]) - xmin) * 800 / (xmax - xmin) + 10)
win_y = int((int(npcityposi[i][2]) - ymin) * 600 / (ymax - ymin) + 10)
button = tk.Button(canvas) # 绘制城市
button.config(width=2, height=1, relief='solid', bg="#808080",
command=lambda name=npcityposi[i][0], b=button: choose_citys(name, b))
button.place(x=win_x, y=win_y)
label = tk.Label(canvas) # 绘制城市名字
label.config(text=npcityposi[i][0])
label.place(x=(win_x + 25), y=(win_y + 10))
citys.append([button, label, win_x + 10, win_y + 10])
for i in range(len(cityconnection)):
for j in range(2, len(cityconnection[i]), 2):
m_x1 = citys[cityconnection[i][0]][2]
m_y1 = citys[cityconnection[i][0]][3]
m_x2 = citys[cityconnection[i][j]][2]
m_y2 = citys[cityconnection[i][j]][3]
canvas.create_line(m_x1, m_y1, m_x2, m_y2) # 绘制城市连接线
canvas.create_text((m_x1 + m_x2) / 2 + 10, (m_y1 + m_y2) / 2 + 10, text=cityconnection[i][j + 1]) # 绘制城市间的距离
# 设置搜索的起始城市
start_button = tk.Button(root)
start_button.config(width=10, height=1, relief='solid', text="选择起始城市", command=start_button_click)
start_button.place(x=900, y=500)
start_label1 = tk.Label(root)
start_label1.config(width=10, height=1, relief='solid', text="", bg="white")
start_label1.place(x=1000, y=500)
# 设置搜索的目标城市
end_button = tk.Button(root)
end_button.config(width=10, height=1, relief='solid', text="选择目标城市", command=end_button_click)
end_button.place(x=900, y=550)
end_label1 = tk.Label(root)
end_label1.config(width=10, height=1, relief='solid', text="", bg="white")
end_label1.place(x=1000, y=550)
# 设置选择的算法
rb1 = tk.Radiobutton(root, text="深度优先", variable=how_search, value=0)
rb2 = tk.Radiobutton(root, text="宽度优先", variable=how_search, value=1)
rb3 = tk.Radiobutton(root, text="A*算法", variable=how_search, value=2)
rb1.place(x=900, y=300)
rb2.place(x=900, y=350)
rb3.place(x=900, y=400)
# 设置开始选择按钮
search_path_button = tk.Button(root)
search_path_button.config(width=10, height=1, relief='solid', text="开始搜索路径", command=begin_search)
search_path_button.place(x=900, y=600)
# 记录搜索的时间
root.mainloop()
备注
更好的代码实现应该使用栈和队列来实现深度优先算法和宽度优先算法,A*算法中的求预期代价的最小值可以用优先队列来实现,若读者感兴趣可以自己实现。