广度优先搜索算法②-优先级队列实现

目录

一、什么是优先级队列

1. 使用heapq模块

2. 使用queue.PriorityQueue类

二、优先级队列的实现方式及具体实现方式

1.使用heapq模块

2.使用queue.PriorityQueue类

三、什么是广度优先搜索

1.概念解释

2.广搜详细案例

四、在广度优先搜索思想指导下使用优先级队列查找最短路径

1.定义一个类来表示图

2.使用广度优先搜索算法寻找从起点到其他顶点的最短路径

3.调用过程

4.完整代码


一、什么是优先级队列

        在Python中,优先级队列是一种数据结构,其中元素按照它们的优先级被排序和处理。优先级队列通常允许我们添加(或插入)元素、删除(或获取)具有最高优先级的元素以及检查队列中的元素。

Python提供了两种方式来实现优先级队列:

1. 使用heapq模块

        这是Python标准库中的一部分,它提供了一种简单的堆实现。堆是一种特殊的树形数据结构,可以用来实现优先级队列。在这个模块中,最小堆是默认的实现,这意味着根节点总是最小元素。要创建最大堆,你需要对你的元素进行适当的调整,例如使用负数表示它们的优先级。

2. 使用queue.PriorityQueue类

        这个类位于`queue`模块中,它是线程安全的,并且底层使用了`heapq`模块。当你使用这个类时,只需要将元素作为元组传递,其中元组的第一个元素代表优先级。

二、优先级队列的实现方式及具体实现方式

1.使用heapq模块

        Python的内置heapq模块提供了对堆数据结构的支持。堆是一种特殊的树形数据结构,可以用来实现优先级队列。

        其中,最小堆(默认)中的每个父节点都比其子节点小,这意味着根节点总是最小元素。而最大堆则相反,根节点总是最大元素。

        使用heapq.heappush()heapq.heappop()函数来添加和删除元素。heappush()将元素添加到堆中,保持堆的特性;heappop()则返回并移除堆中的最小元素(对于最小堆)。如果需要获得最大元素的优先级队列,需要手动调整元素,比如用负数表示它们的优先级。

使用Python引用heapq模块并实现优先级队列

import heapq

# 创建一个空的优先级队列
pq = []

# 添加元素到队列
for item in [(5, 'write code'), (1, 'eat'), (8, 'sleep')]:
    # 使用heappush将元素添加到堆中,保持堆的特性
    heapq.heappush(pq, item)

# 删除并返回最小优先级的元素
while pq:
    # 使用heappop返回并移除堆中的最小元素(对于最小堆)
    current_task = heapq.heappop(pq)
    print(f'Doing {current_task[1]} ({current_task[0]})')

解释:

  • heapq模块提供了对堆数据结构的支持。堆是一种特殊的树形数据结构,可以用来实现优先级队列。
  • 在这个例子中,创建了一个空的优先级队列pq
  • 使用一个循环来向优先级队列中添加元素。每个元素是一个包含优先级和任务名的元组。这些元素被添加到堆中,并按照优先级进行排序。
  • 然后,在一个循环中删除并返回优先级队列中的最小优先级元素。在每次迭代中,使用heappop()函数从堆中获取并移除最小元素,并打印出该元素的任务名和优先级。

2.使用queue.PriorityQueue

        Python的queue模块也提供了一个名为PriorityQueue的类,它是线程安全的,并且底层使用了heapq模块。这个类直接支持优先级队列的操作,只需要传递元组作为元素,元组的第一个元素代表优先级。

使用Python引用PriorityQueue模块并实现优先级队列

from queue import PriorityQueue

# 创建一个空的优先级队列
pq = PriorityQueue()

# 添加元素到队列
for priority, item in [(5, 'write code'), (1, 'eat'), (8, 'sleep')]:
    # 使用put方法将元素添加到队列中
    pq.put((priority, item))

# 删除并返回最小优先级的元素
while not pq.empty():
    # 使用get方法返回并移除队列中的最小优先级元素
    current_task = pq.get()
    print(f'Doing {current_task[1]} ({current_task[0]})')
  • queue.PriorityQueue类直接支持优先级队列的操作,它是一个线程安全的版本。
  • 在这个例子中,创建了一个空的优先级队列pq
  • 使用一个循环来向优先级队列中添加元素。每个元素是一个包含优先级和任务名的元组。这些元素被添加到队列中,并按照优先级进行排序。
  • 然后,在一个循环中删除并返回优先级队列中的最小优先级元素。在每次迭代中,使用get()方法从队列中获取并移除最小元素,并打印出该元素的任务名和优先级。

三、什么是广度优先搜索

1.概念解释

        广度优先搜索(Breadth-First Search, BFS)是一种用于遍历或搜索树形结构(如图)的算法。在图中,它用于寻找从一个给定起点到所有其他节点的最短路径。

        BFS的基本思想是按照层级顺序遍历整个图。从起点开始,首先访问所有的邻居节点,然后访问这些邻居节点的邻居节点,以此类推。这样做的结果是,距离起点最近的节点总是最先被访问。

以下是BFS算法的一般步骤:

        1. 创建一个队列,并将起始节点放入队列。
        2. 创建一个集合来记录已经访问过的节点。
        3. 当队列不为空时:
                   - 从队列中取出第一个节点,并将其标记为已访问。
                   - 遍历当前节点的所有未访问的邻居节点,将它们添加到队列中,并标记为已访问。
        4. 当队列变为空时,说明我们已经访问了所有可达的节点。

        BFS通常使用堆栈、队列或其他滚动数组数据结构来实现。在这个过程中,需要维护一个数据结构来存储每个节点到起点的距离。

        BFS的主要优点是简单且易于实现,而且可以找到从起点到所有其他节点的最短路径。然而,它的缺点是在稠密图中可能会消耗大量内存,是一种增加空间复杂度换取时间复杂度的算法。

2.广搜详细案例

整体解释:

首先,我们定义了一个名为 Graph 的类,它有一个构造函数和两个方法:

  1. 构造函数 __init__(self, vertices):创建一个空的二维列表(矩阵)来表示图,并初始化所有边的权重为0。
  2. 方法 add_edge(self, u, v, weight=1):添加一条从顶点 u 到顶点 v 的有向边,权重为 weight。这里默认权重为1。
  3. 方法 bfs(self, s):执行广度优先搜索,输入参数为起点 s。返回一个字典,其中键是每个节点,值是该节点到起点的距离。

bfs() 方法中,我们首先创建一个队列 queue 并将起始节点放入其中。然后,我们创建一个集合 visited 用来记录已经访问过的节点,以及一个字典 distance 来存储每个节点到起点的距离。

接下来,我们进入一个while循环,只要队列不为空,我们就继续搜索。在每次迭代中,我们从队列的第一个位置取出当前节点,并将其标记为已访问。然后我们遍历当前节点的所有邻居,如果邻居节点未被访问过,我们就将邻居节点加入队列并更新其距离信息。

当队列变为空时,说明我们已经搜索完了所有可达的节点。此时,我们可以返回 distance 字典,它包含了每个可达节点到起点的最短距离。

最后,我们创建一个 Graph 对象,添加一些边,并调用 bfs() 方法来执行广度优先搜索。我们选择顶点2作为起点,并输出结果。在这个例子中,输出的结果是 {0: 1, 1: 2, 2: 0, 3: 3},这表示顶点0、1和3到顶点2的距离分别是1、2和3。

from collections import deque

class Graph:
    def __init__(self, vertices):
        self.graph = [[0 for column in range(vertices)]
                      for row in range(vertices)]

    def add_edge(self, u, v, weight=1):
        self.graph[u][v] = weight
        self.graph[v][u] = weight

    def bfs(self, s):
        visited = [False] * (len(self.graph))
        distance = [-1] * (len(self.graph))

        queue = deque()
        queue.append(s)
        visited[s] = True
        distance[s] = 0

        while queue:
            vertex = queue.popleft()

            for neighbour in range(len(self.graph[vertex])):
                if self.graph[vertex][neighbour] > 0 and not visited[neighbour]:
                    queue.append(neighbour)
                    visited[neighbour] = True
                    distance[neighbour] = distance[vertex] + 1

        return distance


g = Graph(4)
g.add_edge(0, 1)
g.add_edge(0, 2)
g.add_edge(1, 2)
g.add_edge(2, 0)
g.add_edge(2, 3)
g.add_edge(3, 3)

start = 2
result = g.bfs(start)
print(result)  # 输出:[1, 2, 0, 1]

四、在广度优先搜索思想指导下使用优先级队列查找最短路径

题目

用无向图表示一个城市的交通网络,并从一个节点出发找到能到达的所有节点的最短路径。

1.定义一个类来表示图

class Graph:
    '''
    这个Graph类有两个方法:
    __init__()
    add_edge()
    '''
    def __init__(self):
        '''
        __init__()初始化了一个空字典graph,用于存储图的边。
        '''
        self.graph = {}  # 初始化一个空字典来存储图的边

    def add_edge(self, u, v, weight):  # 添加一条从顶点u到顶点v的边,并设置其权重为weight
        '''
        add_edge()方法接受三个参数:u、v和weight,表示一条从顶点u到顶点v的边及其权重。
        :param u:顶点,表示起点
        :param v:顶点,表示目标点
        :param weight:边的权重值
        :return:None
        '''
        if u not in self.graph:  # 如果顶点u不在图中,则创建一个新的列表来表示它的邻居节点
            self.graph[u] = []
        if v not in self.graph:  # 如果顶点v不在图中,则创建一个新的列表来表示它的邻居节点
            self.graph[v] = []
        self.graph[u].append((v, weight))  # 将顶点v添加到顶点u的邻居列表中,同时保存边的权重

2.使用广度优先搜索算法寻找从起点到其他顶点的最短路径

def bfs(graph, start):  # 广度优先搜索算法,输入参数为图和起点
    '''
    bfs()函数中,首先创建了一个名为queue的堆栈,并将起始节点和它的初始距离(0)放入堆栈中。
    然后,进入一个while循环,只要堆栈不为空,就继续搜索。
    在每次迭代中,取出堆栈的第一个元素作为当前节点,并将其标记为已访问。
    接着,遍历当前节点的所有邻居,并计算到达每个邻居节点的新距离。
    如果新的距离比之前记录的距离更短,那么更新邻居节点的距离信息,并将邻居节点放入堆栈中。
    当堆栈变为空时,说明已经搜索完了所有可达的节点。此时,可以返回distance字典,它包含了每个可达节点到起点的最短距离。
    :param graph: 图
    :param start: 起点
    :return: 包含所有可达节点到起点距离的字典
    '''
    queue = [(0, start)]  # 使用堆栈作为滚动数组来实现队列功能,初始元素为起点及其距离(0)
    visited = set()  # 创建一个集合来记录已经访问过的节点
    distance = {start: 0}  # 创建一个字典来存储每个节点到起点的距离

    while queue:  # 只要队列不为空,就继续搜索
        current_distance, current_node = queue.pop(0)  # 取出队列的第一个元素作为当前节点

        visited.add(current_node)  # 将当前节点标记为已访问

        for neighbor, weight in graph[current_node]:  # 遍历当前节点的所有邻居
            if neighbor not in visited:  # 如果邻居节点未被访问过
                new_distance = current_distance + weight  # 计算到达邻居节点的新距离
                if new_distance < distance.get(neighbor, float('inf')):  # 如果新距离更短,则更新邻居节点的距离信息
                    distance[neighbor] = new_distance
                    queue.append((new_distance, neighbor))  # 将邻居节点加入队列

    return distance  # 返回包含所有可达节点到起点距离的字典

3.调用过程

# 调用过程
g = Graph()
g.add_edge(0, 1, 4)
g.add_edge(0, 2, 2)
g.add_edge(1, 3, 1)
g.add_edge(2, 3, 1)

start = 0
result = bfs(g.graph, start)
print(result)  # 输出:{0: 0, 1: 4, 2: 2, 3: 3}

4.完整代码

class Graph:
    '''
    这个Graph类有两个方法:
    __init__()
    add_edge()
    '''
    def __init__(self):
        '''
        __init__()初始化了一个空字典graph,用于存储图的边。
        '''
        self.graph = {}  # 初始化一个空字典来存储图的边

    def add_edge(self, u, v, weight):  # 添加一条从顶点u到顶点v的边,并设置其权重为weight
        '''
        add_edge()方法接受三个参数:u、v和weight,表示一条从顶点u到顶点v的边及其权重。
        :param u:顶点,表示起点
        :param v:顶点,表示目标点
        :param weight:边的权重值
        :return:None
        '''
        if u not in self.graph:  # 如果顶点u不在图中,则创建一个新的列表来表示它的邻居节点
            self.graph[u] = []
        if v not in self.graph:  # 如果顶点v不在图中,则创建一个新的列表来表示它的邻居节点
            self.graph[v] = []
        self.graph[u].append((v, weight))  # 将顶点v添加到顶点u的邻居列表中,同时保存边的权重

def bfs(graph, start):  # 广度优先搜索算法,输入参数为图和起点
    '''
    bfs()函数中,首先创建了一个名为queue的堆栈,并将起始节点和它的初始距离(0)放入堆栈中。
    然后,进入一个while循环,只要堆栈不为空,就继续搜索。
    在每次迭代中,取出堆栈的第一个元素作为当前节点,并将其标记为已访问。
    接着,遍历当前节点的所有邻居,并计算到达每个邻居节点的新距离。
    如果新的距离比之前记录的距离更短,那么更新邻居节点的距离信息,并将邻居节点放入堆栈中。
    当堆栈变为空时,说明已经搜索完了所有可达的节点。此时,可以返回distance字典,它包含了每个可达节点到起点的最短距离。
    :param graph: 图
    :param start: 起点
    :return: 包含所有可达节点到起点距离的字典
    '''
    queue = [(0, start)]  # 使用堆栈作为滚动数组来实现队列功能,初始元素为起点及其距离(0)
    visited = set()  # 创建一个集合来记录已经访问过的节点
    distance = {start: 0}  # 创建一个字典来存储每个节点到起点的距离

    while queue:  # 只要队列不为空,就继续搜索
        current_distance, current_node = queue.pop(0)  # 取出队列的第一个元素作为当前节点

        visited.add(current_node)  # 将当前节点标记为已访问

        for neighbor, weight in graph[current_node]:  # 遍历当前节点的所有邻居
            if neighbor not in visited:  # 如果邻居节点未被访问过
                new_distance = current_distance + weight  # 计算到达邻居节点的新距离
                if new_distance < distance.get(neighbor, float('inf')):  # 如果新距离更短,则更新邻居节点的距离信息
                    distance[neighbor] = new_distance
                    queue.append((new_distance, neighbor))  # 将邻居节点加入队列

    return distance  # 返回包含所有可达节点到起点距离的字典

# 调用过程
g = Graph()
g.add_edge(0, 1, 4)
g.add_edge(0, 2, 2)
g.add_edge(1, 3, 1)
g.add_edge(2, 3, 1)

start = 0
result = bfs(g.graph, start)
print(result)  # 输出:{0: 0, 1: 4, 2: 2, 3: 3}

  • 23
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

灰灰老师

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

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

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

打赏作者

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

抵扣说明:

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

余额充值