这篇博客主要记录我在学习python算法时实现栈的过程,这里栈的实现只是最简单的实现,其中也包括符号匹配,前缀、中缀以及后缀表达式的实例。参考书目为:
problem-solving-with-algorithms-and-data-structure-using-python 中文版。
运行环境:Python3.6 + Jupyter notebook。
这篇博客相当于学习笔记,请勿转载。
Python 实现栈¶
栈的抽象数据结构¶
- 栈是有序的LIFO(后进先出)。
- 栈的操作有:
- Stack() 创建新的空栈。
- push(item) 添加新项到栈顶部。
- pop() 删除栈顶项并返回栈顶项的值。栈被修改。
- peek() 返回栈顶部项。不修改栈。
- isEmpty() 测试栈是否为空,返回 Bool 值。
- size() 返回栈长度(栈中 item 数量)。
利用列表实现栈¶
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[-1]
def size(self):
return len(self.items)
if __name__ == ‘__main__‘:
s = Stack()
print(s.isEmpty())
s.push(4)
s.push(‘dog‘)
print(s.peek())
s.push(True)
print(s.size())
print(s.isEmpty())
s.push(8.4)
print(s.pop())
print(s.pop())
print(s.size())
简单括号匹配¶
区分括号是否匹配的能力是识别很多编程语言结构的重要部分。具有挑战的是如何编写一个算法,能够从左到右读取一串符号,并决定符号是否平衡。为了解决这个问题,我们需要做一个重要的观察。从左到右处理符号时,最近开始符号必须与下一个关闭符号相匹配(见 Figure 1)。此外,处理的第一个开始符号必须等待直到其匹配最后一个符号。结束符号以相反的顺序匹配开始符号。他们从内到外匹配。这是一个可以用栈解决问题的线索。
Figure 1
def parChecker(symbol_sring):
s = Stack()
balanced = True
index = 0
while index < len(symbol_sring) and balanced:
symbol = symbol_sring[index]
if symbol == ‘(‘:
s.push(symbol)
else:
if s.isEmpty():
balanced = False
else:
s.pop()
index += 1
if balanced and s.isEmpty():
return True
else:
return False
print(parChecker(‘((()))‘))
print(parChecker(‘((()‘))
注:栈是处理括号匹配极好的数据结构
def check(strings):
s = Stack()
for string in strings:
if string == ‘(‘:
s.push(string)
elif string == ‘)‘:
try:
s.pop()
except IndexError:
return False
else:
return False
return s.isEmpty()
print(check(‘((()))‘))
print(check(‘()))‘))
符号匹配¶
上面的两个程序仅用于‘()‘的匹配,实际上 Python 中常用的括号有 { } [ ] ( )。符号字符串如:
{ { ( [ ] [ ] ) } ( ) }
[ [ { { ( ( ) ) } } ] ]
[ ] [ ] [ ] ( ) { }
都是正确的匹配。
def check(strings):
s = Stack()
for string in strings:
if string in ‘{[(‘:
s.push(string)
elif string in ‘)]}‘:
try:
match(open=s.pop(), close=string)
except(IndexError, ValueError):
return False
else:
return False
return s.isEmpty()
def match(open, close):
opens = ‘{[(‘
closers = ‘}])‘
if opens.index(open) != closers.index(close):
raise ValueError
print(check(‘[][][](){}‘))
print(check(‘[{()]‘))
进制转换¶
首先看看 Python3 内置函数进制转换用法
- bin(x) oct(x) hex(x)
help(bin)
bin(256)
注:二进制字符串以0b开头
oct(256)
注:八进制字符串以0o开头
hex(256)
注:十六进制字符串以0x开头
- int([number | string[, base]])
# 其他进制 to 十进制
int(bin(256), 2)
int(oct(256), 8)
int(hex(256), 16)
其他进制转换¶
其他进制转换可以以十进制为媒介
bin(int(oct(256), 8)) # oct2bin
Python3 实现十进制 to 其他进制¶
基本思想是将十进制数不断迭代除以2,并跟踪余数。Figure2 以二进制为例,展示了栈是解决这个问题的数据结构。
Figure2
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[-1]
def size(self):
return len(self.items)
def __iter__(self):
for i in self.items[::-1]:
yield i
def box(dec_number, base=2):
remstack = Stack()
base_num = [str(i) for i in range(10)] + [chr(x) for x in range(ord(‘A‘), ord(‘A‘)+6)]
prefix = ‘box‘
while dec_number > 0:
index = dec_number % base
remstack.push(base_num[index])
dec_number = dec_number // base
try:
return ‘0‘+ prefix[[2, 8, 16].index(base)] + ‘‘.join(remstack)
except ValueError:
return ‘‘.join(remstack)
# test
print(box(233, 2))
print(box(233,3))
print(box(233,5))
print(box(233,8))
print(box(233,10))
print(box(233,16))
注:box()函数实现十进制至2到16之间任意进制转换,但是除bin, oct, hex 外均无前缀。
中缀、前缀和后缀表达式¶
我们平常的算术表达式一般是中缀表达式,例如:A+B*C
就是中缀表达式:运算符在操作数之间。
但是如何辨别运算顺序即优先级是个问题。我们可以用括号来明确:(A+(B*C))
。
其实,我们可以用前缀表达式:+ A * B C
,运算符在操作数前面紧邻;
利用后缀表达式:A B C * +
。
这样一来,运算符的优先级很容易体现:紧邻的运算符优先级高。
下面讨论中缀转后缀表达式,基本思想是:
- 创建一个名为 opstack 的空栈以保存运算符。给输出创建一个空列表。
- 通过使用字符串方法拆分将输入的中缀字符串转换为标记列表。
- 从左到右扫描标记列表。
- 如果标记是操作数,将其附加到输出列表的末尾。
- 如果标记是左括号,将其压到 opstack 上。
- 如果标记是右括号,则弹出 opstack,直到删除相应的左括号。将每个运算符附加到输出列表的末尾。
- 如果标记是运算符,
*,/,+
或-
,将其压入 opstack。但是,首先删除已经在 opstack 中具有更高或相等优先级的任何运算符,并将它们加到输出列表中。
- 当输入表达式被完全处理时,检查 opstack。仍然在栈上的任何运算符都可以删除并加到输出列表的末尾。
import re
def infix2postfix(infixexpr):
#确定优先级
prec = {}
prec[‘(‘] = 1
prec[‘+‘] = 2
prec[‘-‘] = 2
prec[‘*‘] = 3
prec[‘/‘] = 3
opstack = Stack() # 存储运算符
postfix_list = [] # 后缀表达式
token_list = [i for i in infixexpr if not re.match(r‘\s+‘,i)]
for token in token_list:
if re.match(r‘[0-9a-zA-Z]‘, token):
postfix_list.append(token) # 操作数加入后缀表达式
elif token == ‘(‘:
opstack.push(token) # ‘(‘ 压入栈内
elif token == ‘)‘:
top_token = opstack.pop()
while top_token != ‘(‘:
postfix_list.append(top_token)
top_token = opstack.pop()
else:
while (not opstack.isEmpty()) and (prec[opstack.peek()] >= prec[token]):
postfix_list.append(opstack.pop())
opstack.push(token)
while not opstack.isEmpty():
postfix_list.append(opstack.pop())
return ‘ ‘.join(postfix_list)
print(infix2postfix("A * B + C * D"))
print(infix2postfix("( A + B ) * C - ( D - E ) * ( F + G )"))
后缀表达式求值¶
基本思想如下图:
def postfix_eval(postfix_expr):
s = Stack()
token_list = postfix_expr.split()
for token in token_list:
if re.fullmatch(r‘([0-9])|([1-9][0-9]+)‘, token):
s.push(int(token))
else:
opra2 = s.pop()
opra1 = s.pop()
result = do_math(token, opra1, opra2)
s.push(result)
return s.pop()
def do_math(op, op1, op2):
if op == "*":
return op1 * op2
elif op == "/":
return op1 / op2
elif op == "+":
return op1 + op2
else:
return op1 - op2
print(postfix_eval(‘7 8 + 3 2 + /‘))
zxzhu 2018/2/12
Reference: