Python数据结构与算法(二)栈和队列

本文介绍了Python中栈、队列和双端队列的基本概念、实现方式及应用,包括括号匹配、HTML标签匹配、两个栈实现队列等问题的解决方案。同时,讲解了基于Python列表的栈和队列实现以及Python collections模块中的双端队列。
摘要由CSDN通过智能技术生成

本系列总结了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)
    向双端队列的前面添加一个元素e
  • D.add_last(e)
    在双端队列的后面添加一个元素e
  • D.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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lingpy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值