目录
一、什么是优先级队列
在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
的类,它有一个构造函数和两个方法:
- 构造函数
__init__(self, vertices)
:创建一个空的二维列表(矩阵)来表示图,并初始化所有边的权重为0。 - 方法
add_edge(self, u, v, weight=1)
:添加一条从顶点u
到顶点v
的有向边,权重为weight
。这里默认权重为1。 - 方法
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}