重学数据结构之队列

时间:2022-07-22
本文章向大家介绍重学数据结构之队列,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

队列(queue)又被称为队,也是一种保存数据元素的容器。队列时一种特殊的线性表,只允许在表的前端(front)进行删除操作,只允许在表的后端(rear)进行插入操作,进行删除操作的一端叫做对头,进行插入操作的一端称为队尾。

  队列按照先进先出的原则(FIFO,First In First Out)存储数据,先存入的元素会先被取出来使用。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。

2.抽象数据描述

  队列的基本操作和栈是类似的,只不过操作名称有些许不一样,下面是一个基本队列的抽象数据描述:

ADT Queue:   Queue(self)  # 创建一个空队列   is_empty(self)  # 判断队列是否为空   enqueue(self, elem)  # 入队,向队列中插入一个元素   dequeue(self)  # 出队,从队列中删除一个元素   peek(self)  # 查看对头位置的元素

3.用 Python 实现

 1 # 自定义队列
 2 class MyQueue:
 3     def __init__(self):
 4         self.data = []
 5 
 6     def is_empty(self):
 7         return len(self.data) == 0
 8 
 9     def enqueue(self, elem):
10         self.data.append(elem)
11 
12     def dequeue(self):
13         if self.data:
14             ret, self.data = self.data[0], self.data[1:]
15             return ret
16         return None
17 
18     def peek(self):
19         return self.data[0] if self.data else None

二、队列

1.顺序队列

  顺序队列是队列的顺序存储结构,顺序队列实际上是运算受限的顺序表。和顺序表一样,顺序队列使用一个向量空间来存放当前队列中的元素。由于队列的队头和队尾的位置是变化的,设置两个指针front和rear分别指示队头元素和队尾元素的位置,它们的初值在初始化时都置为0。

2.循环队列

  循环队列将向量空间想象为一个首尾相接的回环,在循环队列中,由于入队时队尾指针向前追赶队头指针,出队时队头指针追赶队尾指针,因而队空和队满时头尾指针均相等。

3.双端队列

  双端队列是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。双端队列是限定插入和删除操作在表的两端进行的线性表。

4.优先级队列

  在优先级队列中,会给每一个元素都分配一个数字用来标记其优先级,例如给其中最小的数字以最高的优先级,这样就可以在一个集合中访问优先级最高的元素并对其进行查找和删除操作了。

  使用 Python 实现一个优先级队列,可以借助 Python 中的 heapq 模块来实现,heapq 是一个二叉堆的实现,其内部使用内置的 list 对象,对于列表中的每一个元素都满足 a[k] <= a[2 * k + 1] and a[k] <= a[2 * k + 2],因此其默认是一个最小堆,a[0] 是队列中最小的元素。下面是利用 heapq 模块实现的一个优先级队列代码示例:

 1 # 自定义优先级队列
 2 class PriorityQueue:
 3     def __init__(self):
 4         self.data = []
 5         self.index = 0
 6 
 7     def push(self, elem, priority):
 8         """
 9         入队,将元素插入到队列中
10         :param elem: 待插入元素
11         :param priority: 该元素的优先级
12         :return:
13         """
14         # priority 加上负号是因为 heapq 默认是最小堆
15         heapq.heappush(self.data, (-priority, self.index, elem))
16         self.index += 1
17 
18     def pop(self):
19         """
20         出队,从队列中取出优先级最高的元素
21         :return:
22         """
23         return heapq.heappop(self.data)[-1]
24 
25     def size(self):
26         """
27         获取队列中元素数量
28         :return:
29         """
30         return len(self.data)
31 
32     def is_empty(self):
33         """
34         判断队列是否为空
35         :return:
36         """
37         return len(self.data) == 0

三、队列的应用

1.迷宫问题

1)问题描述

将一个迷宫映射成一个由0和1组成的二维矩阵,迷宫里的空位置用0来表示,障碍和边界用1来表示,最左上角为入口,最右下角为出口,求是否能从迷宫中走出来?

2)问题分析

  首先在算法初始时,可行的位置用0标识,不可行的位置用1标识,但在搜索路径的过程中需要将已经走过的位置给标记上,这里可以用数字2来标记,在后面的搜索过程中碰到2也就不会重复搜索了。

  当到达一个位置时,需要确定该位置的可行方向,而除了边界点,每个位置都有四个可行的方向需要探索,例如对于坐标点(i,j),其四个相邻位置如下图:

  为了能够方便的计算相邻位置,可以用一个列表来记录:

DIR = [(0, 1), (1, 0), (0, -1), (-1, 0)]

  对于任何一个位置(i,j),都可以分别加上 DIR[0]、DIR[1]、DIR[2]、DIR[3],就能得到相邻位置了。

  为了使算法变得简单,这里可以先定义两个辅助用的函数,一个用于标记走过的点,一个用于判断输入的位置是否可以通行,具体代码如下:

 1 def mark(maze, pos):
 2     """
 3     将迷宫中已经走过的位置进行标记,设置为2
 4     :param maze: 迷宫
 5     :param pos: 位置
 6     :return:
 7     """
 8     maze[pos[0]][pos[1]] = 2
 9 
10 
11 def passable(maze, pos):
12     """
13     检车迷宫中指定位置是否能走
14     :param maze: 迷宫
15     :param pos: 位置
16     :return:
17     """
18     if pos[0] < len(maze) and pos[1] < len(maze[0]):
19         return maze[pos[0]][pos[1]] == 0
20     return False

3)递归算法求解

在使用递归算法求解的过程中,对于每一个位置,都有如下的算法过程:

  • 标记当前位置;
  • 检查当前位置是否为出口,如果则表明找到路径,算法结束,不是则进行下一步;
  • 遍历该位置的相邻位置,使用递归调用自身;
  • 如果相邻位置都不可行,表明无法从迷宫中走出来,算法结束。

  递归算法的核心函数代码如下: