定义
队列(Queue):一种线性表数据结构,是一种只允许在表的一端进行插入操作,而在表的另一端进行删除操作的线性表
队尾(rear):队列中允许插入的一端
队首(front):队列中允许删除的一端
空队:队列中没有任何元素时
基本操作
定义的解释:
与线性表相同,队列也有着一下的两种存储方式
注意:根据算法实现方式的不同,front 和 rear 的指向位置并不完全固定。有时候算法设计上的方便以及代码简洁,也会使 front 指向队头元素所在位置的前一个位置。rear 也可能指向队尾元素在队列位置的下一个位置
下面,我们先看一下队列的基本操作,会让我们对其实现有更深的理解
class Queue: # 初始化空队列 def __init__(self. size = 100): self.size = size self.queue = [None for _ in range(size)] self.front = -1 self.rear = -1 # 判断队列是否为空 def is_empty(self): return self.front == self.rear # 判断队列是否已满 def is_full(self): return self.rear + 1 == self.size # 入队操作 def enqueue(self, val): if self.is_full(): raise Exception('Queue is full') else: self.rear += 1 self.queue[self.rear] = value # 出队操作 def dequeue(self): if self.is_empty(): raise Exception('Queue is empty') else: self.front += 1 return self.queue[self.front] # 获取队首元素 def front_value(self): if self.is_empty(): raise Exception('Queue is empty') else: return self.queue[self.front + 1] # 获取队尾元素 def rear_value(self): if self.is_empty(): raise Exception('Queue is empty') else: return self.queue[self.rear]
由于出队操作总是删除当前的队头元素,将 self.front 进行右移,而插入操作又总是在队尾进行。经过不断的出队、入队操作,队列的变化就像是使队列整体向右移动。当队尾指针 self.rear == self.size - 1 时,此时再进行入队操作就又抛出队列已满的异常。而之前因为出队操作而产生空余位置也没有利用上,这就造成了假溢出问题。
而为了解决这个问题,有两种做法:
每一次删除队头元素之后,就将整个队列往前移动 1 个位置。其代码如下所示
# 出队操作 def dequeue(self): if self.is_empty(): raise Exception('Queue is empty') else: value = self.queue[0] for i in range(self.rear): self.queue[i] = self.queue[i + 1] return value
这种情况下,队头指针似乎用不到了。因为队头指针总是在队列的第 0 个位置。但是因为删除操作涉及到整个队列元素的移动,所以每次删除操作的时间复杂度就从O(1)变为了O(n)。这种方式不太可取
将队列想象成为头尾相连的循环表,利用数学中的求模运算,使得空间得以重复利用,这样就解决了问题
这样在进行插入操作时,如果队列的第 self.size - 1 个位置被占用之后,只要队列前面还有可用空间,新的元素加入队列时就可以从第 0 个位置开始继续插入
我们约定:self.size 为循环队列的最大元素个数。队头指针 self.front 指向队头元素所在位置的前一个位置,而队尾指针 self.rear 指向队尾元素所在位置。则
注意:循环队列在一开始初始化,队列为空时,self.front 等于 self.rear。而当充满队列后,self.front 还是等于 self.rear。这种情况下就无法判断「队列为空」还是「队列为满」了
解决方式为:
整体的代码实现:
class Queue: # 初始化空队列 def __init__(self, size=100): self.size = size + 1 self.queue = [None for _ in range(size + 1)] self.front = 0 self.rear = 0 # 判断队列是否为空 def is_empty(self): return self.front == self.rear # 判断队列是否已满 def is_full(self): return (self.rear + 1) % self.size == self.front # 入队操作 def enqueue(self, value): if self.is_full(): raise Exception('Queue is full') else: self.rear = (self.rear + 1) % self.size self.queue[self.rear] = value # 出队操作 def dequeue(self): if self.is_empty(): raise Exception('Queue is empty') else: self.queue[self.front] = None self.front = (self.front + 1) % self.size return self.queue[self.front] # 获取队头元素 def front_value(self): if self.is_empty(): raise Exception('Queue is empty') else: value = self.queue[(self.front + 1) % self.size] return value # 获取队尾元素 def rear_value(self): if self.is_empty(): raise Exception('Queue is empty') else: value = self.queue[self.rear] return value
对于在使用过程中数据元素变动较大,或者说频繁进行插入和删除操作的数据结构来说,采用链式存储结构比顺序存储结构更加合适
所以我们可以采用链式存储结构来实现队列。我们用一个线性链表来表示队列,队列中的每一个元素对应链表中的一个链节点。然后把线性链表的第 1 个节点定义为队头指针 front,在链表最后的链节点建立指针 rear 作为队尾指针。并且限定只能在链表队头进行删除操作,在链表队尾进行插入操作,这样整个线性链表就构成了一个队列
class Node: def __init__(self, value): self.value = value self.next = None class Queue: # 初始化空队列 def __init__(self): head = Node(0) self.front = head self.rear = head # 判断队列是否为空 def is_empty(self): return self.front == self.rear # 入队操作 def enqueue(self, value): node = Node(value) self.rear.next = node self.rear = node # 出队操作 def dequeue(self): if self.is_empty(): raise Exception('Queue is empty') else: node = self.front.next self.front.next = node.next if self.rear == node: self.rear = self.front value = node.value del node return value # 获取队头元素 def front_value(self): if self.is_empty(): raise Exception('Queue is empty') else: return self.front.next.value # 获取队尾元素 def rear_value(self): if self.is_empty(): raise Exception('Queue is empty') else: return self.rear.value
广度优先搜索算法(Breadth First Search):简称为 BFS,又译作宽度优先搜索 / 横向优先搜索。是一种用于遍历或搜索树或图的算法。该算法从根节点开始,沿着树的宽度遍历树或图的节点。如果所有节点均被访问,则算法中止
广度优先遍历类似于树的层次遍历过程。呈现出一层一层向外扩张的特点。先看到的节点先访问,后看到的节点后访问。遍历到的节点顺序符合先进先出的特点,所以广度优先搜索可以通过队列来实现
import collections def bfs(graph, strat): visited = set(start) q = collections.deque([start]) while q: node_u = q.popleft() print(node_u) for node_v in graph[node_u]: if node_v not in visited: visited.add(node_v) q.append(node_v)
class MyStack: def __init__(self): self.queue1 = collections.deque() self.queue2 = collections.deque() def push(self, x: int) -> None: self.queue2.append(x) while self.queue1: self.queue2.append(self.queue1.popleft()) self.queue1, self.queue2 = self.queue2, self.queue1 def pop(self) -> int: return self.queue1.popleft() def top(self) -> int: return self.queue1[0] def empty(self) -> bool: return not self.queue1
思路
代码实现
class Solution: def islandPerimeter(self, grid: List[List[int]]) -> int: r, c = len(grid), len(grid[0]) ans = 0 for i in range(r): for j in range(c): if grid[i][j] == 1: ans += 4 if i - 1 >= 0 and grid[i - 1][j] == 1: ans -= 1 if i + 1 < r and grid[i + 1][j] == 1: ans -= 1 if j - 1 >= 0 and grid[i][j - 1] == 1: ans -= 1 if j + 1 < c and grid[i][j + 1] == 1: ans -= 1 return ans
class Solution: def islandPerimeter(self, grid: List[List[int]]) -> int: def bfs(grid, i, j): directs = [(0, 1), (0, -1), (1, 0), (-1, 0)] q = collections.deque([(i, j)]) ans = 0 while q: row, col = q.popleft() grid[row][col] = 2 for direct in directs: new_row = row + direct[0] new_col = col + direct[1] if new_row < 0 or new_row >= r or new_col < 0 or new_col >= c or grid[new_row][new_col] == 0: ans += 1 elif grid[new_row][new_col] == 1: grid[new_row][new_col] = 2 q.append([new_row, new_col]) return ans r, c = len(grid), len(grid[0]) for i in range(r): for j in range(c): if grid[i][j] == 1: return bfs(grid, i, j)
class Solution: def openLock(self, deadends: List[str], target: str) -> int: if target == '0000': return 0 dead = set(deadends) if '0000' in dead: return -1 def num_prev(x): return '9' if x == '0' else str(int(x) - 1) def num_succ(x): return '0' if x == '9' else str(int(x) + 1) # 枚举 status 通过一次旋转得到的数字 def get(s): s = list(s) for i in range(4): num = s[i] s[i] = num_prev(num) yield ''.join(s) s[i] = num_succ(num) yield ''.join(s) s[i] = num q = deque([('0000', 0)]) seen = {'0000'} while q: statue, step = q.popleft() for next_status in get(statue): if next_status not in seen and next_status not in dead: if next_status == target: return step + 1 q.append((next_status, step + 1)) seen.add(next_status) return -1