(8-2-02)优先级遍历(Priority-based Search)算法:Best-First Search (最佳优先搜索)算法(2)寻找两个城市之间的最短路径

8.2.3  寻找两个城市之间的最短路径

本实例展示了如何使用最佳优先搜索算法在给定城市之间的距离信息的情况下,找到两个指定城市之间的最短路径的过程。程序首先读取城市的坐标和邻接关系信息,然后通过用户输入的起始城市和目标城市,利用最佳优先搜索算法计算出最短路径,并将结果打印出来。用户可以选择是否继续查找其他路径。

实例8-2寻找两个城市之间的最短路径codes/8/tu.py和juli.py

1. 绘制地图

(1)文件Adjacencies.txt 中包含了城市之间的相邻关系。每一行表示一个城市及其相邻的城市列表。例如,第一行 "Anthony Bluff_City_City Kiowa Attica Harper" 表示 Anthony 这个城市与 Bluff_City、Kiowa、Attica 和 Harper 这些城市相邻。在程序中,根据这些相邻关系构建了城市对象,并计算了城市之间的距离,用于最佳优先搜索算法中。

Anthony Bluff_City Kiowa Attica Harper
Attica Medicine_Lodge
Augusta Winfield Andover Leon Wichita
Caldwell South_Haven Bluff_City Mayfield
El_Dorado Towanda Andover Augusta Emporia
Florence McPherson Hillsboro El_Dorado
Greensburg Coldwater Pratt
Harper Anthony Argonia Rago
Hutchinson Newton Haven
Junction_City Abilene Marion Manhattan Topeka
Kingman Cheney Pratt Hutchinson
Marion McPherson Newton Emporia
Mayfield Wellington Caldwell Argonia
McPherson Salina Lyons Hillsboro
Medicine_Lodge Attica Kiowa Coldwater
Newton McPherson Hutchinson Florence
Rago Viola Sawyer
Salina Abilene
Sawyer Pratt Zenda
Wellington Oxford Mayfield Mulvane South_Haven
Wichita Derby Clearwater Cheney Mulvane Andover Newton El_Dorado

(2)文件coordinates.txt中包含了城市的名称以及它们的经纬度坐标信息,每一行表示一个城市,其中包含城市名称、纬度和经度。例如,"Abilene 38.9220277 -97.2666667" 表示 Abilene 这个城市的纬度为 38.9220277,经度为 -97.2666667。在程序中,根据这些坐标信息构建了城市对象,并根据城市之间的距离关系构建了相邻城市的信息,用于最佳优先搜索算法中。

Abilene 38.9220277 -97.2666667
Andover 37.6868403 -97.1657752
Anthony 37.1575168 -98.0728946
Argonia 37.2670166 -97.7726807
Attica 37.2421271 -98.2351967
Augusta 37.6913277 -97.0537108
Bluff_City 37.0760844 -97.8793212
Caldwell 37.0346731 -97.6179436
Cheney 37.632882 -97.789442
Clearwater 37.5166968 -97.5325458
Coldwater 37.2574937 -99.3549149
Derby 37.5517122 -97.2867892
El_Dorado 37.8098997 -96.8943313
Emporia 38.3792991 -96.2615044
Florence 38.2434672 -96.9378672
Greensburg 37.6050677 -99.3005641
Harper 37.2852232 -98.0368352
Haven 37.9020486 -97.7926952
Hillsboro 38.3494571 -97.2156415
Hutchinson 38.0572062 -97.9414547
Junction_City 39.0379342 -96.8799338
Kingman 37.6480942 -98.1693967
Kiowa 37.0190996 -98.4940572
Leon 37.6892622 -96.7916587
Lyons 38.3477177 -98.22167
Manhattan 39.1682049 -96.6901159
Marion 38.3589767 -97.0267385
Mayfield 37.2618658 -97.5508871
McPherson 38.3704302 -97.6917722
Medicine_Lodge 37.2855616 -98.5888462
Mulvane 37.4868677 -97.2575929
Newton 38.0353742 -97.4239353
Oxford 37.2734026 -97.1777267
Pratt 37.6753423 -98.7769217
Rago 37.4527946 -98.0905053
Salina 38.8254325 -97.702327
Sawyer 37.4972558 -98.6880621
South_Haven 7.0497874 -97.4052061
Topeka 39.0130335 -95.7782425
Towanda 37.7971427 -97.0063152
Viola 37.4827584 -97.6496081
Wellington 37.2691963 -97.5199806
Wichita 37.6645257 -97.5841207
Winfield 37.2844228 -96.999848
Zenda 37.4447194 -98.2849356

(3)编写文件tu.p,使用库NetworkX来创建图结构,并使用了Spring Layout 布局算法来调整节点的位置。

import matplotlib.pyplot as plt
import networkx as nx

# 读取坐标文件
coordinates_file = "coordinates.txt"
coordinates = {}
with open(coordinates_file, "r") as f:
    for line in f:
        city, lat, lon = line.strip().split()
        coordinates[city] = (float(lat), float(lon))

# 读取相邻关系文件
adjacencies_file = "Adjacencies.txt"
adjacencies = {}
with open(adjacencies_file, "r") as f:
    for line in f:
        cities = line.strip().split()
        adjacencies[cities[0]] = cities[1:]

# 创建无向图
G = nx.Graph()

# 添加节点和边
for city, coord in coordinates.items():
    G.add_node(city, pos=coord)
    if city in adjacencies:
        for adj_city in adjacencies[city]:
            G.add_edge(city, adj_city)

# 绘制地图
pos = nx.spring_layout(G, seed=42)  # 使用 spring layout 布局算法调整节点位置
plt.figure(figsize=(12, 10))
nx.draw_networkx_nodes(G, pos, node_size=400, node_color='skyblue', alpha=0.8)
nx.draw_networkx_edges(G, pos, edge_color='gray', width=0.5, alpha=0.5)
nx.draw_networkx_labels(G, pos, font_size=8, font_weight='bold')
plt.title('City Map')
plt.axis('off')  # 关闭坐标轴
plt.show()

执行后绘制的地图如图8-1所示。

图8-1  绘制的地图

2. 实现Best-First Search算法

文件juli.py通过最佳优先搜索算法找到两个城市之间的最短路径。首先读取城市的坐标信息和相邻关系,然后根据用户输入的起始城市和目标城市,在城市之间进行搜索,找到一条最短路径,并输出路径上经过的城市序列。用户可以重复使用该程序来查找不同起点和终点之间的最短路径。

(1)下面的代码定义了类 city ,用于表示城市对象,并提供了一些方法来管理城市对象的访问状态。类 city中的成员如下所示。

  1. 初始化方法 __init__:接受城市的 x 坐标和 y 坐标作为参数,并将其存储在对象的属性中。同时,初始化一个空的字典 adjacentCities 用于存储相邻城市及其距离,以及一个布尔型属性 visited,用于标记该城市是否已经被访问过。
  2. 方法setVisited:将城市对象的 visited 属性设置为 True,表示该城市已经被访问过。
  3. 方法setUnvisited:将城市对象的 visited 属性设置为 False,表示该城市未被访问过。
  4. 方法getCords:返回城市的坐标信息,但实际上该方法没有实现具体功能。
class city:
    def __init__(self, xCord, yCord):
        self.xCord = xCord
        self.yCord = yCord
        self.adjacentCities = {}
        self.visited = False

    def setVisited(self):
        self.visited = True

    def setUnvisited(self):
        self.visited = False

    def getCords(self):
        return

(2)下面的代码片段定义了两个全局变量 cityDict 和 allCities,以及一个用于计算两个坐标之间距离的函数 calculateDistance。

  1. cityDict:是一个空字典,用于存储城市名称与对应城市对象之间的映射关系。
  2. allCities:是一个空列表,用于存储所有城市的名称。
  3. 函数calculateDistance:接受4个参数,分别是两个点的 x 和 y 坐标,函数将这四个坐标用于计算两点之间的欧式距离,并返回计算结果。
cityDict = {}
allCities = []
def calculateDistance(x1, y1, x2, y2):
    x1 = float(x1)
    y1 = float(y1)
    x2 = float(x2)
    y2 = float(y2)
    dist = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
    return dist

(3)下面的代码用于读取文件coordinates.txt中的数据,为每个城市创建一个包含名称、x 坐标和 y 坐标的对象,并将其存储在 cityDict 字典和 allCities 列表中。

# 从文件中读取数据,为每个城市创建包含名称、x坐标和y坐标的对象

# 从文件中读取数据,为每个城市创建包含名称、x坐标和y坐标的对象
with open(r"coordinates.txt") as f:
    linesFromFile = []
    for line in f:
        # 去除换行符并将行添加到列表中
        linesFromFile.append(line.rstrip('\n'))
        stringFromFile = ""
        # 将列表中的所有元素连接成一个字符串
        for element in linesFromFile:
            stringFromFile += element
        # 以空格分割字符串
        splitString = stringFromFile.split(' ')
        # 将城市名称添加到列表中
        allCities.append(splitString[0])
        # 创建城市对象并将其与城市名称关联
        cityDict[splitString[0]] = city(splitString[1], splitString[2])
        # 清理临时变量
        linesFromFile.clear()
        stringFromFile = None
        splitString.clear()
    f.close()

上述代码的实现流程如下所示:

  1. 首先,打开名为 "coordinates.txt" 的文件,并将其内容逐行读取到 linesFromFile 列表中。
  2. 然后,对于每一行数据,去除换行符并将其添加到 linesFromFile 列表中。
  3. 接下来,将列表中的所有元素拼接成一个字符串 stringFromFile。
  4. 将 stringFromFile 按空格分割为一个字符串列表 splitString。
  5. 将分割后的列表的第一个元素(城市名称)添加到 allCities 列表中。
  6. 创建一个城市对象,并将其与城市名称关联起来,将城市对象添加到 cityDict 字典中。
  7. 清空 linesFromFile、stringFromFile 和 splitString,以备下一次循环使用。
  8. 最后,关闭文件。

(4)下面的代码用于从文件Adjacencies.txt中读取数据,通过解析文件中的内容,计算并存储了相邻城市之间的距离,并将结果存储在名为cityDict的字典中的adjacentCities属性中,以便后续的路径搜索算法使用。

# 从文件中读取数据,计算并存储相邻城市之间的距离
with open(r"Adjacencies.txt") as f:
    linesFromFile = []
    for line in f:
        # 去除换行符并将行添加到列表中
        linesFromFile.append(line.rstrip('\n'))
        stringFromFile = ""
        # 将列表中的所有元素连接成一个字符串
        for element in linesFromFile:
            stringFromFile += element
        # 以空格分割字符串
        splitString = stringFromFile.split(' ')
        # 计算并存储相邻城市之间的距离
        for key, value in cityDict.items():
            if splitString[0] == key:
                for i in range(1, len(splitString)):
                    d = calculateDistance(cityDict[splitString[0]].xCord, cityDict[splitString[0]].yCord,
                                          cityDict[splitString[i]].xCord, cityDict[splitString[i]].yCord)
                    cityDict[key].adjacentCities[splitString[i]] = d
                    if splitString[0] not in cityDict[splitString[i]].adjacentCities:
                        d = calculateDistance(cityDict[splitString[i]].xCord, cityDict[splitString[i]].yCord,
                                              cityDict[splitString[0]].xCord, cityDict[splitString[0]].yCord)
                        cityDict[splitString[i]].adjacentCities[key] = d
        # 清理临时变量
        linesFromFile.clear()
        stringFromFile = None
        splitString.clear()
    f.close()

(5)函数bestFirstSearch的功能是,使用最佳优先搜索算法搜索从起始城市到目标城市的路径。它首先建立了优先队列、未访问城市列表和已访问城市列表,然后将起始城市标记为已访问并添加到已访问城市列表中。在主循环中,它持续进行直到当前城市等于目标城市为止。在每次循环迭代中,它首先将当前城市的相邻城市添加到未访问城市列表中,并将它们按照距离排序后放入优先队列中。然后,它检查优先队列中最近的城市是否已经访问过,如果已经访问过,则丢弃并继续检查下一个城市。如果优先队列为空,则从已访问城市列表中删除最后一个城市,并将当前城市更新为新的最后一个城市。否则,它将当前城市更新为优先队列中的最近相邻城市,并将其标记为已访问,并将其添加到已访问城市列表中。在循环结束时,它打印出可能的路径,并清除已访问状态,以便进行下一次搜索。

def bestFirstSearch(startCity, endCity, cityDict):
    # 设置
    priorityQ = PriorityQueue()
    unvisitedList = []
    visitedList = []
    cityDict[startCity].setVisited()
    visitedList.append(startCity)
    currentCity = startCity
    # 循环
    while currentCity != endCity:
        def populatePriorityQ(currentCity):
            # 将当前城市的相邻城市放入unvisitedList中
            for key in cityDict[currentCity].adjacentCities.keys():
                unvisitedList.append(key)
            # 将unvisitedList中的每个城市放入priorityQ中以便按距离排序
            for eachCity in unvisitedList:
                for cityDist in cityDict[eachCity].adjacentCities.values():
                    priorityQ.put((cityDist, eachCity))

        populatePriorityQ(currentCity)
        # 检查最近的城市是否已经访问过
        def checkQForVisited(Q):
            # 也许如果Q不为空?
            if Q.qsize() > 0:
                cityToCheck = Q.queue[0]
                list(cityToCheck)
                cityToCheck = cityToCheck[1]
                if cityDict[cityToCheck].visited == True:
                    discardCity = priorityQ.get()
                    return checkQForVisited(Q)
                else:
                    pass
            else:
                pass
        checkQForVisited(priorityQ)
        if priorityQ.empty():
            del visitedList[-1]
            currentCity = visitedList[-1]
        else:
            # 更新当前城市为最近的相邻城市
            currentCity = priorityQ.queue[0]
            list(currentCity)
            currentCity = currentCity[1]
            # 将当前城市标记为已访问,添加到visitedList中
            cityDict[currentCity].setVisited()
            visitedList.append(currentCity)
        # 清空priorityQ
        priorityQ.queue.clear()
        unvisitedList.clear()

    # 打印访问过的城市列表
    print("\n你可以选择的路径是:")
    print(*visitedList, sep=" ->\n")

(6)下面这段代码使用最佳优先搜索算法在给定的城市之间找到一条路径。首先,从文件中读取城市的坐标和邻接关系,并创建相应的城市对象。然后,用户可以输入起始城市和结束城市,程序将根据输入的信息使用最佳优先搜索算法找到一条连接这两个城市的路径,并打印出来。用户可以选择是否继续寻找其他路径。

# 开始
running = True
while (running):
    print("这个程序使用最佳优先搜索算法在两个城市之间找到一条路径。")

    '''
    format_string = "{:<}{:<}{:<}"
    #print(format_string.format(*headers))
    for entry in allCities:
        print(format_string.format(*entry))
    '''
    while True:
        startCity = input('输入起始城市: ')
        if startCity in allCities:
            break
        else:
            print("请输入有效的城市。\n")
            continue

    while True:
        endCity = input('输入结束城市: ')
        if endCity in allCities:
            break
        else:
            print("请输入有效的城市。\n")
            continue

    bestFirstSearch(startCity, endCity, cityDict)

    choosing = True
    while (choosing):
        choice = input("\n你想再找一条路径吗?\n")
        if choice == "yes" or choice == "Yes" or choice == "YES" or choice == "Y" or choice == "y":
            choosing = False
        elif choice == "no" or choice == "No" or choice == "NO" or choice == "N" or choice == "n":
            running = False
            choosing = False
        else:
            print("\n请输入 yes 或 no。")

执行后先分别输入起点和终点,按下回车键后可以找到两个城市之间的路径,例如下面的执行过程。

这个程序使用最佳优先搜索算法在两个城市之间找到一条路径。
输入起始城市: Andover
输入结束城市: Coldwater

A route you could take would be:
Andover ->
Augusta ->
El_Dorado ->
Wichita ->
Cheney ->
Kingman ->
Pratt ->
Sawyer ->
Rago ->
Harper ->
Anthony ->
Attica ->
Medicine_Lodge ->
Coldwater

你想再找一条路径吗?
yes
这个程序使用最佳优先搜索算法在两个城市之间找到一条路径。
输入起始城市:

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值