数据结构与算法(python) 线性结构:栈Stack

参考自 MOOC数据结构与算法Python版

什么是线性结构

  • 线性结构是一种有序数据项的集合, 其中每个数据项都有唯一的前驱和后继
  • 线性结构总有两端, 在不同的情况下, 两端的称呼也不同
    • 有时候称为“左”“右”端、 “前”“后”端、“顶”“底”端
    • 在这里插入图片描述 - 两端的称呼并不是关键, 不同线性结构的关键区别在于数据项增减的方式
    • 有的结构只允许数据项从一端添加,而有的结构则允许数据项从两端移除
  • 以下是4个最简单但功能强大的结构
  1. 栈Stack
  2. 队列Queue
  3. 双端队列Deque
  4. 列表List

栈抽象数据类型及Python实现

什么是栈
  • 一种有次序的数据项集合, 在栈中, 数据项的加入和移除都仅发生在同一端
    这一端叫栈“顶top”,另一端叫栈“底base”
  • 日常生活中有很多栈的应用
    盘子、托盘、书堆等等, 比如浏览器的“后退back”按钮,最先back的是最
    近访问的网页,比如Word文件的撤销操作,最先撤销最近的操作
  • 最新加入栈的数据项会被最先移除
  • 这种次序通常称为“后进先出LIFO”:Last in First out
    在这里插入图片描述 在这里插入图片描述
“栈”的基本操作
函数含义
Stack()创建一个空栈,不包含任何数据项
push(item)将item加入栈顶,无返回值
pop()将栈顶数据项移除,并返回,栈被修改
peek()“窥视”栈顶数据项,返回栈顶的数据项但不移除,栈不被修改
isEmpty()返回栈是否为空栈
size()返回栈中有多少个数据项
Python 实现ADT Stack

Stack的两端对应list设置
我们选用List的末端(index=-1)作为栈顶 这样栈的操作就可以通过对 list 的append 和 pop 来实现

class Stack:
    def __init__(self):
        self.items =[]
    def isEmpty(self):
        return self.items == []
    def push(self,item):
        self.items.append(item)
    def pop(self):
        return self.items.pop()
    def peek(self):
        return self.items[len(self.items)-1]
    def size(self):
        return len(self.items)
栈的简单应用
简单括号匹配及代码实现
  • 括号匹配识别算法
    从左到右扫描括号串,最新打开的左括号,应该匹配最先遇到的右括号这样,第一个左括号(最早打开),就应该匹配最后一个右括号(最后遇到)
    在这里插入图片描述
    在实际的应用里, 我们会碰到更多种括号 如python中
    • 列表所用的方括号“[]”
    • 字典所用的花括号“{}”
    • 元组和表达式所用的圆括号“()”
      这些不同的括号有可能混合在一起使用,因此就要注意各自的开闭匹配情况

通用括号匹配算法:代码

def matches(top, symbol):
    pre = "([{"
    back = ")]}"
    return pre.index(top) == back.index(symbol)
def parCheck(symbolString):
    s = Stack()
    flag = True
    index = 0
    while index < len(symbolString) and flag:
        symbol = symbolString[index]
        if symbol in "([{":  #左括号入栈
            s.push(symbol)
        else:
            if s.isEmpty():
                flag = True
            else:
                top = s.pop()
                if not matches(top, symbol): #右括号和栈顶元素匹配
                    flag = False
        index += 1
    if flag  and s.isEmpty():
        return True
    else: return False
print(parCheck('{{([][])}()}'))  #True
print(parCheck('[{()]')) #False
print(parCheck('([)]')) #False

HTML/XML文档也有类似于括号的开闭标记, 这种层次结构化文档的校验、 操作也可以通过栈来实现

十进制转换为二进制

“除以2”的过程, 得到的余数是从低到高的次序, 而输出则是从高到低, 所以需要一个栈来反转次序
在这里插入图片描述
代码如下:

def divideBy2(decNum):
    s = Stack()
    while decNum>0:
        rem = decNum % 2
        s.push(rem)
        decNum = decNum // 2  #整除
    binString = ""
    while not s.isEmpty():
        binString += str(s.pop())#从栈顶一个个取出
    return binString
print(divideBy2(42))  # 101010
十进制扩展更多进制转换
def baseConverter(decNum,base):
    digits = "0123456789ABCDEF"
    s = Stack()
    while decNum>0:
        rem = decNum % base
        s.push(rem)
        decNum = decNum // base  #整除
    binString = ""
    while not s.isEmpty():
        binString += digits[s.pop()] #从栈顶一个个取出
    return binString
print(baseConverter(25,2))  #11001
表达式转换
  • 中缀表达式
    例如BC,这种操作符(operator) 介于操作数(operand) 中间的表示法, 称为“中缀”表示法。
    但计算机处理最好是能明确规定所有的计算顺序, 这样无需处理复杂的优先规则,引入全括号表达式:在所有的表达式项两边都加上括号,如:A+B
    C+D,应表示为((A+(B*C))+D)。
  • 前缀和后缀表达式
    将操作符移到前面,变为“+AB”或者将操作符移到最后,变为“AB+”,我们就得到了表达式的另外两种表示法:“前缀”和“后缀”表示法。

总结:以操作符相对于操作数的位置来定义,这样A+B*C将变为前缀的 “+A *BC”,后缀的“ABC * + ”,”,

中缀表达式转换为前缀和后缀形式

子表达式(B*C)的右括号, 如果把操作符 * 移到右括号的位置, 替代它, 再删去左括号, 得到 BC*, 这个正好把子表达式转换为后缀形式
在这里插入图片描述
同样的, 如果我们把操作符移动到左括号的位置替代之, 然后删掉所有的右括号,也就得到了前缀表达式

在这里插入图片描述
所以说, 无论表达式多复杂, 需要转换成前缀或者后缀, 只需要两个步骤

  1. 将中缀表达式转换为全括号形式
  2. 将所有的操作符移动到子表达式所在的左括号(前缀)或者右括号(后缀)处,替代之,再删除所有的括号
通用的中缀转后缀算法

首先我们来看中缀表达式A+BC, 其对应的后缀表达式是ABC+,操作数ABC的顺序没有改变。操作符的出现顺序,在后缀表达式中反转了,在从左到右扫描逐个字符扫描中缀表达式的过程中, 采用一个来暂存未处理的操作符,这样, 栈顶的操作符就是最近暂存进去的,当遇到一个新的操作符,就需要跟栈顶的操作符比较下优先级, 再行处理。
算法流程如下:

  • 后面的算法描述中, 约定中缀表达式是由空格隔开的一系列单词(token) 构成,
    操作符单词包括*/±(),而操作数单词则是单字母标识符A、 B、 C等。
  • 首先,创建空栈opstack用于暂存操作符,空表postfixList用于保存后缀表达式
  • 将中缀表达式转换为单词(token) 列表
    A+BC =split=> [‘A’, ‘+’, ‘B’, '’, ‘C’]
  • 从左到右扫描中缀表达式单词列表
    • 如果单词是操作数,则直接添加到后缀表达式列表的末尾
    • 如果单词是左括号“(”,则压入opstack栈顶
    • 如果单词是右括号“)”,则反复弹出opstack栈顶操作符,加入到输出列表末尾,直到碰到左括号
    • 如果单词是操作符“*/±”,则压入opstack栈顶
    • 但在压入之前,要比较其与栈顶操作符的优先级,如果栈顶的高于或等于它,就要反复弹出栈顶操作符,加入到输出列表末尾。直到栈顶的操作符优先级低于它
  • 中缀表达式单词列表扫描结束后, 把 opstack栈中的所有剩余操作符依次弹出,添加到输出列表末尾
  • 把输出列表再用join方法合并成后缀表达式字符串, 算法结束

代码如下:

class Stack:
    def __init__(self):
        self.items =[]
    def isEmpty(self):
        return self.items == []
    def push(self,item):
        self.items.append(item)
    def pop(self):
        return self.items.pop()
    def peek(self):  #窥视栈顶数据项
        return self.items[len(self.items)-1]
    def size(self):
        return len(self.items)
    
def infixToPostfix(infixexpr):
     ########记录符号优先级
    prec = {}
    prec["*"] = 3
    prec["/"] = 3
    prec["+"] = 2
    prec["-"] = 2
    prec["("] = 1
    opStack = Stack()  ##存放操作符
    postfixList = []  ##存放后缀表达式
    tokenList = infixexpr.split()   ##存放单词
    print( tokenList)
    for token in tokenList:
        if token in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" or token in "0123456789":
            postfixList.append(token)
        elif token =='(':
            opStack.push(token)
        elif token ==')':
            topToken = opStack.pop()
            while topToken != '(':
                postfixList.append(topToken)
                topToken = opStack.pop()
        else:  #如果是操作符
            while(not opStack.isEmpty()) and (prec[opStack.peek()] >= prec[token]):
                postfixList.append(opStack.pop())
            opStack.push(token)
    while not opStack.isEmpty():
        postfixList.append(opStack.pop())
    return " ".join(postfixList)
print(infixToPostfix("( A + B ) * C"))       
后缀表达式求值

如“4 5 6 * +”, 我们先扫描到4、 5两个操作数,但还不知道对这两个操作数能做什么计算, 需要继续扫描后面的符号才能知道;继续扫描, 又碰到操作数6,还是不能知道如何计算, 继续暂存入栈;直到“*”, 现在知道是栈顶两个操作数5、 6做乘法;我们弹出两个操作数, 计算得到结果30(需要注意:先弹出的是右操作数,后弹出的是左操作数,这个对于-/很重要);为了继续后续的计算, 需要把这个中间结果30压入栈顶,继续扫描后面的符号;当所有操作符都处理完毕, 栈中只留下1个操作数, 就是表达式的值
流程:

  • 创建空栈operandStack用于暂存操作数
  • 将后缀表达式用 s pl i t方法解析为单词(token) 的列表
  • 从左到右扫描单词列表
    – 如果单词是一个操作数,将单词转换为整数int,压入operandStack栈顶
    – 如果单词是一个操作符(*/±),就开始求值,从栈顶弹出2个操作数,先弹出的是右操作数,后弹出的是左操作数,计算后将值重新压入栈顶
  • 单词列表扫描结束后,表达式的值就在栈顶
  • 弹出栈顶的值,返回
    代码如下:
def doMath(op,op1,op2):
     if op == "*":
         return op1 * op2
     elif op == "/":
         return op1 / op2
     elif op == "+":
         return op1 + op2
     else:
         return op1 - op2

def postfixEval(postfixExpr):
    operandStack = Stack()
    tokenList = postfixExpr.split()
    for token in tokenList:
        if token in "0123456789":
            operandStack.push(int(token))
        else:
            operand2 = operandStack.pop()
            operand1 = operandStack.pop()
            result = doMath(token, operand1, operand2)
            operandStack.push(result)
    return operandStack.pop()

print(postfixEval("1 3 + 2 * "))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值