【数据结构与算法】栈及Python实现

1 栈抽象数据类型及Python实现

  • 定义: 一种有次序的数据项集合,在栈中,数据项的加入和移除都仅发生在同一端,这一端叫栈 “ 顶top ” ,另一端叫栈 “ 底base

  • 特性: 反转次序,进栈和出栈的次序正好相反

在这里插入图片描述


1.1 抽象数据类型Stack

  • Stack() :创建一个空栈,不包含任何数据项
  • push(item) :将item 加入栈顶,无返回值
  • pop() :将栈顶数据项移除,并返回,栈被修改
  • peek() : “ 窥视 ” 栈顶数据项,返回栈顶的数据项但不移除,栈不被修改
  • isEmpty() :返回栈是否为空栈
  • size():返回栈中有多少个数据项

【操作样例】

Stack OperationStack ContentsReturn Value
s= Stack()[]Stack object
s.isEmpty()[]True
s.push(4)[4]
s.push(‘dog’)[4,‘dog’]
s.peek()[4,‘dog’]‘dog’
s.push(True)[4,‘dog’,True]
s.size()[4,‘dog’,True]3
s.isEmpty()[4,‘dog’,True]False
s.push(8.4)[4,‘dog’,True,8.4]
s.pop()[4,‘dog’,True]8.4
s.pop()[4,‘dog’]True
s.size()[4,‘dog’]2

1.2 Python实现ADT Stack

class Stack:
    def __init__(self):
        self.items = []

    def isEmpty(self):
        # 判断是否为空
        return self.items == []

    def push(self, item):
        # 输入栈
        return 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)

# 结果
s = Stack()
print(s.isEmpty())	# True

s.push(4)
s.push('dog')
print(s.peek())		# dog

s.push(True)
print(s.size())		# 3
print(s.isEmpty())	# False

s.push(8.4)
print(s.pop())		# 8.4
print(s.pop())		# True
print(s.size())		# 2

2 栈的应用

2.1 简单括号匹配算法

  • 括号的使用必须遵循 “平衡”规则
    • 每个开括号要恰好对应一个闭括号
    • 每对开闭括号要正确的嵌套
正确的括号:(( )( )( )( )) ,(((( )))) ,(( )((( ))( )))
错误的括号:((((((( )) ,( ))) ,(( )( )(( )

构造括号匹配识别算法

从左到右扫描括号串,最新打开的左括号,应该匹配最先遇到的右括号,这样,第一个左括号(最早打开),就应该匹配最后一个右括号(最后遇到),这种次序反转的识别,正好符合栈的特性!

在这里插入图片描述

在这里插入图片描述

【代码】

class Stack: ...

def parChecker(symbolString):
    s = Stack()
    balanced = True
    index = 0

    while index < len(symbolString) and balanced:
        symbol = symbolString[index]
        # 若为左括号,则存到栈中
        if symbol == "(":
            s.push(symbol)
		# 若为右括号,且栈不为空,则弹出一个左括号
        else:
            if s.isEmpty():
                balanced = False
            else:
                s.pop()
        index = index + 1

    if balanced and s.isEmpty():
        return True
    else:
        return False
        

print(parChecker('(())'))		# True
print(parChecker('((())'))		# False

2.2 通用括号匹配算法

  • 在实际的应用里,我们会碰到更多种括号

    • 如python 中列表所用的方括号 “[] ”

    • 字典所用的花括号 “{} ”

    • 元组和表达式所用的圆括号 “() ”

  • 这些不同的括号有可能混合在一起使用,

  • 因此就要注意各自的开闭匹配情况

【代码】

def matches(open, close):
    opens = '([{'
    closers = ')]}'
    return opens.index(open) == closers.index(close)


def parChecker2(symbolString):
    s = Stack()
    balanced = True
    index = 0

    while index < len(symbolString) and balanced:
        symbol = symbolString[index]
        if symbol in '([{':
            s.push(symbol)
        else:
            if s.isEmpty():
                balanced = False
            else:
                top = s.pop()
                if not matches(top, symbol):
                    balanced = False
        index = index + 1

    if balanced and s.isEmpty():
        return True
    else:
        return False

print(parChecker2('({[]})'))		# True
print(parChecker2('({[}])'))		# False

2.3 十进制转化为二进制算法

如: ( 233 ) 10 (233)_{10} (233)10对应的二进制为 ( 11101001 ) 2 (11101001)_{2} (11101001)2

具体是这样的:
( 233 ) 10 = 2 × 1 0 2 + 3 × 1 0 1 + 3 × 1 0 0 (233)_{10} = 2 \times 10^2 + 3 \times 10^1 + 3 \times 10^0 (233)10=2×102+3×101+3×100

( 11101001 ) 2 = 1 × 2 7 + 1 × 2 6 + 1 × 2 5 + 0 × 2 4 + 1 × 2 3 + 0 × 2 2 + 0 × 2 1 + 1 × 2 0 (11101001)_{2} = 1\times 2^7 + 1\times 2^6 + 1\times 2^5 + 0\times 2^4 + 1\times 2^3 + 0\times 2^2 + 0\times 2^1 + 1\times 2^0 (11101001)2=1×27+1×26+1×25+0×24+1×23+0×22+0×21+1×20

  • 十进制转换为二进制,采用的是“除以2求余数”的算法

    将整数不断除以2 ,每次得到的余数就是由低到高的二进制位

在这里插入图片描述

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

【代码】

class Stack: ...

# 方法一: 栈求解
def divideby2(decNumber):
    remStack = Stack()

    while decNumber > 0:
        rem = decNumber % 2			# 求余数
        remStack.push(rem)
        decNumber = decNumber // 2	# 整除循环

    binString = ""
    while not remStack.isEmpty():
        binString = binString + str(remStack.pop())		# 反向输出

    return binString

# 方法二: 逆序求解
def divideby2_2(decNumber):
    s = []
    while decNumber > 0:
        rem = decNumber % 2
        s.append(rem)
        decNumber = decNumber // 2

    # 逆序输出
    s.reverse()

    # 将列表中数字转换为字符串
    lst1 = [str(i) for i in s]
    # 将列表转化为字符串
    str1 = ''.join(lst1)

    return str1


print(divideby2(35))		# 100011
print(divideby2_2(35))		# 100011

2.4 十进制转化为n进制(n<16)算法

如: ( 233 ) 10 (233)_{10} (233)10等于 ( 351 ) 8 (351)_8 (351)8 ( E 9 ) 16 (E9)_{16} (E9)16

( 351 ) 8 = 3 × 8 2 + 5 × 8 1 + 1 × 8 0 (351)_8 = 3 \times 8^2 + 5\times 8^1 + 1\times 8^0 (351)8=3×82+5×81+1×80

( E 9 ) 16 = 14 × 1 6 1 + 9 × 1 6 0 (E9)_{16} = 14\times 16^1 + 9\times 16^0 (E9)16=14×161+9×160

二进制有两个不同数字0 、1

十进制有十个不同数字0 、1 、2 、3 、4 、5 、6 、7 、8 、9

八进制可用八个不同数字0 、1 、2 、3 、4 、5 、6、7

十六进制的十六个不同数字则是0 、1 、2 、3 、4、5 、6 、7 、8 、9 、A 、B 、C 、D 、E 、F

【代码】

def baseConverter(decNumber, base):
# decNumber为十进制数, base为要转化进制
    digits = '0123456789ABCDEF'

    remstack = Stack()
    while decNumber > 0:
        rem = decNumber % base
        remstack.push(rem)
        decNumber = decNumber // base

    newString = ''
    while not remstack.isEmpty():
        newString = newString + digits[remstack.pop()]

    return newString


print(baseConverter(25, 8))		# 31

2.5 后缀表达式转换算法

虽然人们已经习惯了中缀运算符优先级表示法,但计算机处理最好是能明确规定所有的计算顺序,这样无需处理复杂的优先规则,因而将表达式中操作符的位置稍移动一下,即:

中缀表达式前缀表达式后缀表达式
A+B+ABAB+
A+B*C+A*BCABC*+
A + B * C + D+ + A * B C DA B C * + D +
(A + B) * (C + D)* + A B + C DA B + C D + *
A * B + C * D+ * A B * C DA B * C D * +
A + B + C + D+ + + A B C DA B + C + D +
  • 看子表达式 ( B ∗ C ) (B*C) (BC)右括号,如果把操作符 ∗ * 移到右括号的位置,替代它,再删去左括号,得到 B C ∗ BC* BC,这个正好把子表达式转换为后缀形式
    在这里插入图片描述

  • 同样的,如果我们把操作符移动到左括号的位置替代之,然后删掉所有的右括号,也就得到了前缀表达式
    在这里插入图片描述

所以说,无论表达式多复杂,需要转换成前缀或者后缀,只需要两个步骤

  • 将中缀表达式转换为全括号形式

  • 将所有的操作符移动到子表达式所在的左括号(前缀 )或者右括号( 后缀 )处,替代之,再删除所有的括号
    在这里插入图片描述

中缀转后缀算法:

  • 在中缀表达式转换为后缀形式的处理过程中,操作符比操作数要晚输出

    所以在扫描到对应的第二个操作数之前,需要把操作符先保存起来

  • 而这些暂存的操作符,由于优先级的规则,还有可能要反转次序输出

    A + B ∗ C A+B*C A+BC中,+ 虽然先出现,但优先级比后面这个 ∗ * 要低,所以它要等 ∗ * 处理完后,才能再处理。

  • 这种反转特性,使得我们考虑用来保存暂时未处理的操作符

  • 再看看 ( A + B ) ∗ C (A+B)*C (A+B)C,对应的后缀形式是 A B + C ∗ AB+C* AB+C

    这里+ 的输出比 ∗ * 要早,主要是因为括号使得+ 的优先级提升,高于括号之外的 ∗ *

  • 后缀表达式中操作符应该出现在左括号对应的右括号位置

    所以遇到 左括号 ,要标记下,其后出现的操作符优先级提升 了,一旦扫描到对应的右括号,就可以马上输出这个操作符

【总结】

在从左到右扫描逐个字符扫描中缀表达式的过程中,采用一个栈来暂存未处理的操作符

这样,栈顶的操作符就是最近暂存进去的,当遇到一个新的操作符,就需要跟栈顶的操作符比较下优先级,再行处理。

在这里插入图片描述

【代码】

class Stack:...

def infixToPostfix(infixexpr):
    # 记录操作符优先级
    prec = {'*': 3, '/': 3, '+': 2, '-': 2, '(': 1}

    opStack = Stack()
    postfixList = []
    # 解析表达式到单词列表
    tokenList = [str(i) for i in infixexpr]

    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)+D'))		# A B C + * D +

2.6 后缀表达式求值算法

后缀表达式求值跟中缀转换为后缀问题不同,在对后缀表达式从左到右扫描的过程中,由于操作符在操作数的后面,所以要暂存操作数,在碰到操作符的时候,再将暂存的两个操作数进行实际的计算,仍然是栈的特性:操作符只作用于离它 最近 的两个操作数

456 ∗ + 4 5 6 * + 456+,我们先扫描到4、5两个操作数,但还不知道对这两个操作数能做什么计算
,需要继续扫描后面的符号才能知道,继续扫描,又碰到操作数6,还是不能知道如何计算,继续暂存入栈,直到 ∗ * ,现在知道是栈顶两个操作数5、6做乘法,我们弹出两个操作数,计算得到结果30。

注意:
先弹出的是右操作数,后弹出的是左操作数,这个对于减法除法很重要!

为了继续后续的计算,需要把这个中间结果30压入栈顶,继续扫描后面的符号,当所有操作符都处理完毕,栈中只留下1个操作数,就是表达式的值。

【例】4 + 5 * 6
在这里插入图片描述

【例】( 7 + 8 ) / ( 3 + 2 )

在这里插入图片描述

【代码】

class Stack:...

def infixToPostfix(infixexpr):...

def doMath(op, op1, op2):
    if op == '*':
        return int(op1) * int(op2)
    elif op == '/':
        return int(op1) / int(op2)
    elif op == '+':
        return int(op1) + int(op2)
    else:
        return int(op1) - int(op2)


def postfixEval(postfixExpr):
    operandStack = Stack()
    tokenList = postfixExpr.split()
    for token in tokenList:
        # 操作数
        if token in '0123456789':
            operandStack.push(token)
        # 操作符
        else:
            operand2 = operandStack.pop()
            operand1 = operandStack.pop()
            result = doMath(token, operand1, operand2)
            operandStack.push(result)
    return operandStack.pop()


a = infixToPostfix("3*(2-1)+5")
print("后缀表达式为:\n", a)				#  3 2 1 - * 5 +

print("后缀表达式计算结果为:\n", postfixEval(a))		#  8
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值