1. 什么是队列
·FIFO 先进先出,一端进,另一端出。没有位置的概念,保证在任何时候可访问,删除的元素都是最近存入队列的元素
·数据集:一个有 0 个或多个元素的有穷线性表
·操作集
- 创建空队列 Queue()
- 判断队列为空 is_empty()
- 入队 enqueue()
- 出队 dequeue()
- 查看队列里最早进入的元素 peek()
2. 队列的实现
2.1 链表实现
队列要求首端插入,尾端删除,所以需要两个指针域分别指向头结点和尾结点,以保证O(1)的访问和删除。
对于单向链表,只能以尾结点为队尾,头结点为队首。(因为出队后,指针要沿着next的方向指向后一个元素)
class QueueUnderflow(ValueError):
pass
class LNode():
def __init__(self,elem,next_=None):
self.elem = elem
self.next = next_
def __str__(self):
return str(self.elem)
class Queue():
def __init__(self):
self._head = None
self._rear = None
self._length = 0
def is_empty(self):
return self._head is None
def enqueue(self,elem):
n_Node = LNode(elem)
if self._head is None:
self._head = n_Node
self._rear = n_Node
else:
n_Node = LNode(elem)
self._rear.next = n_Node
self._rear = n_Node
def dequeue(self):
if self._head is None:
raise QueueUnderflow("in dequeue")
else:
p = self._head
self._head = self._head.next
return p.elem
def peek(self):
if self._head is None:
raise QueueUnderflow("in peak")
else:
return self._head.elem
2.2 队列的顺序表实现
困难1:反复入队,出队操作,队尾最终会达到表存储区末端。所以采用循环顺序表
循环顺序表
- 队头变量q.head记录队列第一个元素的位置,队尾变量q.rear记录最后一个元素之后的第一个空位
- 队列元素保存在顺序表的一段连续单元里[q.head:q.rear]
- 入队 q.rear = (q.rear+1)%q.len 出队 q.head = (q.head+1)%q.len 队满的条件定义为(q.rear+1)%q.len == q.head
困难2:考虑定义一个可以自动扩充存储的队列类,不能直接用list的自动存储扩充机制,因为队列的元素可能是表里的任意一段
class QueueUnderflow(ValueError):
pass
class SQueue():
def __init__(self,init_len=8):
self._len = init_len # 存储区长度
self._elems = [0]*init_len # 元素存储
self._head = 0 # 队头元素下标
self._num = 0 # 元素个数,队尾的rear指针用self._head+self._num
def is_empty(self):
return self._num == 0
def peek(self):
if self.is_empty():
raise QueueUnderflow
return self._elems[self._head]
def dequeue(self):
if self.is_empty():
raise QueueUnderflow
e = self._elems[self._head]
self._head = (self._head+1)%self._len
self._num -= 1
return e
def enqueue(self,elem):
if self._num == self._len:
self._extend()
self._elems[(self._head+self._num)%self._len] = elem
self._num += 1
def _extend(self):
old_len = self._len
self._len *= 2
new_elems = [0]*self._len
for i in range(old_len):
new_elems[i] = self._elems[(self._head+i)%old_len]
self._elems, self._head = new_elems, 0
3.队列的应用:迷宫问题求解
# 递归求解,通过函数递归的方式向前探查,通过函数返回的方式回溯
dirs = [(0,1),(1,0),(0,-1),(-1,0)] # 四个相邻位置
def mark(maze,pos): # 给迷宫走过的位置标记为2
maze[pos[0]][pos[1]] = 2
def passable(maze,pos): # 检查迷宫的位置是否可行
return maze[pos[0]][pos[1]] == 0
def find_path(maze,pos,exit):
mark(maze,pos)
if pos == exit:
print(pos, end=' ')
return True
for i in range(4):
nextp = pos[0]+dirs[i][0],pos[1]+dirs[i][1]
# 考虑下一个可能方向
if passable(maze,nextp):
if find_path(maze,nextp,exit):
print(nextp,end=' ')
return True
return False
# 栈和回溯法,用栈来保存中间信息。 深度优先搜索
def maze_solver(maze,start,end):
if start == end:
print(start,end =' ')
return True
st = SStack()
mark(maze,start)
st.push((start,0)) # 入口位置和方向入栈
while not st.is_empty():
pos, nxt = st.pop() # 取栈顶及探索方向
for i in range(nxt,4):
nextp = (pos[0]+dirs[i][0],pos[1]+dirs[i][1]) # 下一步的探测方向
if nextp == end: # 是否到了出口
print_path(start,pos,end) # 打印路径,即弹出栈中所有元素,以及当前位置
return True
if passable(maze,nextp): # 遇到未探测的新位置
st.push((pos,i+1)) # 该位置的下一方向入栈
mark(maze,nextp) # 栈里保存的元素都是标记过的
st.push((nextp,0)) # 新位置入栈
break # 退出内存循环,下次迭代将以新栈顶为当前位置继续
print("No path found.")
# 队列,逐步扩展,无回溯。 宽度优先搜索
def maze_solver(maze,start,end):
if start == end:
print(start,end =' ')
return
q = SQueue()
mark(maze,start)
q.enqueue(start)
path = dict{}
while not q.is_empty():
pos = q.dequeue()
for i in range(4):
nextp = (pos[0]+dirs[i][0],pos[1]+dirs[i][1])
if nextp == end:
print_path(start,nextp,end)
return True
if passable(maze,nextp):
path[nextp] = pos
mark(maze,nextp)
q.enqueue(nextp)
print("No path.")
path ={}
pos = (3,2)
t = ((4,2),(4,3),(4,4),(5,4))
for i in t:
nextp = i
path[nextp] = pos
pos = i
def print_path(path,pos):
formerp = path[pos]
while formerp in path:
print(formerp,end=' ')
formerp = path[formerp]
print(formerp)
print_path(path,(5,4))