本系列总结了python常用的数据结构和算法,以及一些编程实现。
参考书籍:《数据结构与算法 Python语言实现》 【美】Michael T.Goodrich, Roberto Tamassia, Michael H.Goldwasser
更多文章可以移步楼主个人博客:一个算法工程师的修炼金字塔
上一篇文章:Python数据结构与算法(一)列表和元组
文章目录
3 栈、队列和双端队列
关键词: 栈、队列、括号匹配、双端队列
3.1 栈
本节介绍了栈的原理、python实现栈和栈的典型应用。
栈中的对象遵循后进先出(LIFO)原则。
栈的抽象数据类型
栈是一种支持以下方法的抽象数据类型(ADT),用S表示这一ADT实例:
S.push(e)
将一个元素e添加到栈S的栈顶S.pop(e)
从栈S中移除并返回栈顶的元素,如果此时栈为空,这个操作将出错S.top()
仅返回栈顶元素;若栈为空,这个操作将出错S.is_empty()
如果栈中不包含任何元素,则返回一个布尔值"True"len(S)
返回栈S中元素数量
基于Python数组的栈实现
根据list特性,显然可以直接使用list类代替stack类,但是list包括一些不符合stack的方法,并且list类所使用的术语也不能与stack的传统命名方法精确对应。
下面代码使用list实现了一个stack:
class Empty(Exception):
"""Error attempting to access an element from an empty container."""
pass
class ArrayStack:
"""LIFO Stack implementation using a Python list as underlying storage."""
def __init__(self):
"""Create an empty stack."""
self._data = []
def __len__(self):
"""Return the number of elements in the stack."""
return len(_data)
def is_empty(self):
"""Return True if the stack is empty."""
return len(self._data)==0
def push(self,e):
"""Add element e to the top of the stack."""
self._data.append(e)
def top(self):
"""Return (but do not remove)the element at the top of the stack.
Raise Empty exception if the stack is empty.
"""
if self.is_empty():
raise Empty('Stack is empty')
return self._data[-1]
def pop(self):
"""Remove and return the element from the top of the stack(i.e.,LIFO)Raise Empty exception if the stack is empty.
"""
if self.is_empty():
raise Empty('Stack is empty')
return self._data.pop()
- 运行时间分析
基于list的栈的各种操作的摊销运行时间均为 O ( 1 ) O(1) O(1)。
栈的应用
- 括号匹配
问题:
判断一个由括号组成的字符串是否正确。
举例:- 正确:()(()){([])}
- 正确:(((()())){[]})
- 错误:)(()){[()]}
- 错误:({[])}
代码实现:
def is_matched(expr):
"""Return True if all delimiters are properly match; False otherwise."""
left = '([{'
right = ')]}'
stack = ArrayStack()
for c in expr:
if c in left:
stack.push(c)
elif c in right:
if stack.is_empty():
return False
if right.index(c)!=left.index(stack.pop()):
return False
return stack.is_empty()
- HTML标签匹配
问题:
在一个HTML文本中,部分文本是由HTML标签分隔的,比如“”相应的结束标签是“”。编程实现判断一个有HTML标签的字符串中的标签是否相匹配。
代码实现:
def is_matched_html(raw):
"""Return True if all HTML tags are properly match; False otherwise."""
stack = ArrayStack()
j = raw.find('<')
while j!=-1:
k = raw.find('>',j+1)
if k==-1:
return False
tag = raw[j+1:k]
if not tag.startswith('/'):
stack.push(tag)
else:
if stack.is_empty():
return False
if tag[1:]!=stack.pop():
return False
j = raw.find('<',k+1)
return stack.is_empty()
- 两个栈实现队列(剑指offer09)
问题:
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )。
示例:
输入:
[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[3],[],[]]
输出: [null,null,3,-1]
本题是剑指offer09题。详细题解见。
代码实现:
class CQueue:
def __init__(self):
self.__stack_one = []
self.__stack_two = []
def appendTail(self, value: int) -> None:
self.__stack_one.append(value)
def deleteHead(self) -> int:
if not len(self.__stack_two):
while self.__stack_one:
self.__stack_two.append(self.__stack_one.pop())
if len(self.__stack_two):
return self.__stack_two.pop()
return -1
3.2 队列
本节介绍了队列的原理、python实现队列和队列的典型应用。
队列与栈互为“表亲”关系。队列中的对象遵循先进先出(FIFO)原则。
队列的抽象数据类型
队列的抽象数据类型(ADT)支持以下几个方法:
Q.enqueue(e)
向队列Q的队尾添加一个元素Q.dequeue()
从队列Q中移除并返回第一个元素,如果队列为空,则触发一个错误Q.first()
在不移除的前提下返回队列的第一个元素;如果队列为空,则触发一个错误Q.is_empty()
如果队列Q没有包含任何元素则返回“True”len(Q)
返回队列Q中元素数量
基于Pyton数组的队列实现
利用list实现队列与栈的主要不同点是出队操作。
对于队列出队,可以直接使用列表的pop(0)方法,但是这个方法非常低效,耗时为
Θ
(
n
)
\Theta(n)
Θ(n)。
为了开发一种更健壮的队列实现方法,我们让队列内的元素在底层数组的尾部“循环”,即用一个首尾相连的循环数组模拟一个队列。
代码实现:
class ArrayQueue:
"""FIFO queue implementation using a Python list as underlying storage."""
DEFAULT_CAPACITY=10
def __init__(self):
"""Create an empty queue."""
self._data = [None]*ArrayQueue.DEFAULT_CAPACITY
self._size = 0
self._front = 0
def __len__(self):
"""Return the number of elements in the queue."""
return self._size
def is_empty(self):
"""Return True if the queue is empty."""
return self._size == 0
def first(self):
"""
Return (but do not remove) the element at the front of the queue.
Raise Empty exception if the queue is empty.
"""
if self.is_empty():
raise Empty('Queue is empty')
return self._data[self._front]
def dequeue(self):
"""
Remove and return the first element of the queue(i.e., FIFO).
Raise Empty exception if the queue is empty.
"""
if self.is_empty():
raise Empty('Queue is empty')
answer = self._data[self._front]
self._data[self._front] = None
self._front = (sele._front+1)%len(self._data)
self._size -= 1
return answer
def enqueue(self,e):
"""Add an element to the back of queue."""
if self._size == len(self._data):
self._resize(2*len(self._data))
avail = (self._front+self._size)%len(self._data)
self._data[avail] = e
self._size += 1
def _resize(self,cap):
"""Resize to a new list of capacity >= len(self)."""
old = self._data
self._data = [None]*cap
walk = self._front
for k in range(self._size):
self._data[k] = old[walk]
walk = (1+walk)%len(old)
self._front = 0
- 运行时间分析
基于list的队列的各种操作的摊销运行时间均为 O ( 1 ) O(1) O(1)。
3.3 双端队列
本节介绍了双端队列的原理、python的双端队列模块和队列(或双端队列)的典型应用。
双端队列支持在队列的头部和尾部都进行插入和删除操作。
双端队列的抽象数据类型
双端队列的抽象数据类型(ADT)一般包括以下方法:
D.add_first(e)
向双端队列的前面添加一个元素eD.add_last(e)
在双端队列的后面添加一个元素eD.delete_first()
从双端队列中移除并返回第一个元素。若双端队列为空,则触发一个错误D.delete_last()
从双端队列中移除并返回最后一个元素。若双端队列为空,则触发一个错误D.first()
返回(但不移除)双端队列的第一个元素。若双端队列为空,则触发一个错误D.last()
返回(但不移除)双端队列的最后一个元素。若双端队列为空,则触发一个错误D.is_empty()
如果双端队列不包含任何一个元素,则返回布尔值“True”len(D)
返回当前双端队列中的元素个数。
Python的collections模块中的双端队列
Python的标准collections模块中包含一个双端队列的实现方法。 下表给出了collections.deque类常用的方法:
collections.deque | 描述 |
---|---|
len(D) | 元素数量 |
D.appendleft() | 开头增添元素 |
D.append() | 结尾增添元素 |
D.popleft() | 从开头移除 |
D.pop() | 从结尾移除 |
D.clear() | 清除所有内容 |
D.rotate(k) | 循环右移k步 |
D.remove(e) | 移除第一个匹配的元素 |
D.count(e) | 统计对于e匹配的数量 |
python的deque保证任何一端操作的运行时间均为 O ( 1 ) O(1) O(1)。
队列的应用
- 两个队列实现栈
问题:
使用两个队列实现栈的各种方法。
代码实现:
# 作者:xilepeng
# 链接:https://leetcode-cn.com/problems/implement-stack-using-queues/solution/python3-list-dequeshi-xian-by-xilepeng/
from collections import deque
class MyStack:
def __init__(self):
"""
Initialize your data structure here.
"""
self.data = deque()
self.help = deque()
def push(self, x: int) -> None:
"""
Push element x onto stack.
"""
self.data.append(x)
def pop(self) -> int:
"""
Removes the element on top of the stack and returns that element.
"""
while len(self.data) > 1:
self.help.append(self.data.popleft())
tmp = self.data.popleft()
self.help,self.data = self.data,self.help
return tmp
def top(self) -> int:
"""
Get the top element.
"""
while len(self.data) != 1:
self.help.append(self.data.popleft())
tmp = self.data.popleft()
self.help.append(tmp)
self.help,self.data = self.data,self.help
return tmp
def empty(self) -> bool:
"""
Returns whether the stack is empty.
"""
return not bool(self.data)
- 队列的最大值(剑指offer59)
问题:
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。若队列为空,pop_front 和 max_value 需要返回 -1。
示例:
输入:
[“MaxQueue”,“push_back”,“push_back”,“max_value”,“pop_front”,“max_value”]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
代码实现:
方法1-使用两个数组
# 作者:z1m
# 链接:https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/solution/ru-he-jie-jue-o1-fu-za-du-de-api-she-ji-ti-by-z1m/
# 两个列表实现-相比双端队列更快
class MaxQueue:
"""2个数组"""
def __init__(self):
self.queue = []
self.max_stack = []
def max_value(self) -> int:
return self.max_stack[0] if self.max_stack else -1
def push_back(self, value: int) -> None:
self.queue.append(value)
while self.max_stack and self.max_stack[-1] < value:
self.max_stack.pop()
self.max_stack.append(value)
def pop_front(self) -> int:
if not self.queue: return -1
ans = self.queue.pop(0)
if ans == self.max_stack[0]:
self.max_stack.pop(0)
return ans
方法2-使用双端队列
import queue
class MaxQueue:
def __init__(self):
self.deque = queue.deque()
self.queue = queue.Queue()
def max_value(self) -> int:
return self.deque[0] if self.deque else -1
def push_back(self, value: int) -> None:
while self.deque and self.deque[-1]<value:
self.deque.pop()
self.deque.append(value)
self.queue.put(value)
def pop_front(self) -> int:
if not self.deque: return -1
ans = self.queue.get()
if ans == self.deque[0]:
self.deque.popleft()
return ans