Stack
Stack作为线性数据结构中主要抽象数据类型之一,其特点就是“LIFO”(last in first out)即后进先出,这种特性我们一般称为反转次序。Stack可以解决许多问题,最为基础的就是括号匹配问题,十进制转换问题,中缀表达式转后缀表达式问题以及后缀表达式求值问题。以下便是这次数据结构栈类学习过程中的总结和笔记。
先是对栈类的定义,其功能一般有五个,即size(返回栈的大小),is_empty(判断栈是否为空),pop(弹出栈顶数据,并返回该数据),peek(返回栈顶数据),push(将数据压入栈)。
class Stack :
def __init__(self) :
self.items = []
def size(self) :
return len(self.items)
def is_empty(self) :
return self.items == []
def pop(self) :
return self.items.pop()
def peek(self) :
return self.items[len(self.items)-1]
def push(self,i) :
return self.items.append(i)
平时如果要用到栈类的话我们可以直接调用pythonds模块中的Stack来使用。
from pythonds.basic import Stack
在完成了栈类的定义后便可利用栈来解决上面提到的基础问题。
(1)括号匹配
括号匹配主要功能就是检验一段表达式中的括号是否对应匹配,其中栈的功能就是临时存放扫描到的左括号,并在扫描到右括号时弹出左括号,这一算法的判断依据就是balanced和stack.is_empty(),为True则表明括号匹配,反之不匹配。我们先从较为简单的方面入手,即解决匹配一种括号的问题。
#括号匹配(一种括号)
def par_checker(String) :
s = Stack()
balanced = True
index = 0
while index < len(String) and balanced :
item = String[index]
if item == '(' : #遇到左括号将其压入临时栈
s.push(item)
else :
if s.is_empty() : #遇到右括号如果临时栈为空则表明右括号是多余的,则不匹配
balanced = False
else :
s.pop() #如果临时栈不为空则弹出栈内的左括号
index += 1
if balanced and s.is_empty() : #当扫描完后,若balanced为True并且临时栈已空则返回TRUE,括号匹配
return True
else :
return False
print(par_checker('((()))'))
print(par_checker(')()'))
代码返回True则表示表达式中括号匹配,False则不匹配。上述代码返回结果分别为True和False。接下来提高一点难度,解决多种括号间的匹配问题。
def par_checker2(String) :
s = Stack()
balanced = True
index = 0
while index < len(String) and balanced :
item = String[index]
if item in "([{" :
s.push(item)
else :
if s.is_empty() :
balanced = False
else :
top = s.pop() #与第一种括号匹配方法不同之处
if not matches(top,item) : #引用自定义的matches函数来对多种括号匹配
balanced = False
index += 1
if balanced and s.is_empty() :
return True
else :
return False
def matches(first,second) :
String1 = "({["
String2 = ")}]"
if String1.index(first) == String2.index(second) : #String1.index(first)返回first在String1中对应字符的位置,
return True #若与String2.index(second)相同,则firstt和second匹配
else :
return False
print(par_checker2('(({[]})]'))
print(par_checker2('[({})]'))
上述代码返回结果分别为False和True。多种括号匹配与一种括号匹配唯一不同的地方就是前者会通过自定义的括号匹配函数matches来实现多种括号间的对应匹配,两者总体解决问题的思想都差不多。
(2)十进制的转换
十进制转换其他进制一般用的是除以对应进制的数然后求余数的算法,最后得出的余数从上到下依次对应低位到高位的进制位,这正好满足了栈反转次序的特性,即先算出的余数处于低进制位,后算出的余数为高进制位。我们通过对目标数取余,所得余数依次压入临时栈中,最后当取余结束时将栈内余数依次弹出并以字符串的形式输出。先来看看十进制转二进制。
def base_converter(number) :
new_stack = Stack() #临时栈
while number > 0 :
item = number % 2 #取余
new_stack.push(item) #将余数压入临时栈
number = number // 2 #更新目标数,继续取余
String = ""
while not new_stack.is_empty() :
String = String + str(new_stack.pop()) #以字符串形式返回
return String
base_converter(121)
上述代码返回结果为’1111001’。我们再来看看十进制转任意进制(2,8,16)。
def base_converter2(number,base) :
new_stack = Stack()
base_string = "0123456789ABCDEF" #十进制转换任意进制与转换为二进制的步骤差不多,只是在此多了一个对应的字符串
while number > 0 :
item = number % base #将上述中的2替换成了base,base为我们的目标进制数
new_stack.push(item)
number = number // base
String = ""
while not new_stack.is_empty() :
String = String + base_string[new_stack.pop()]
return String
print(base_converter2(121,8))
print(base_converter2(32,16))
返回结果分别为171和20。
(3)中缀表达式转后缀
中缀表达式转后缀算法所依靠的核心就是将表达式中的右括号替换为相应的操作符,其中栈的作用是压入左括号和操作符,在这个算法中除了需要一个临时栈,我们还需要一个中缀列表以及后缀列表,中缀列表通过split函数对表达式切片可以得到一系列以空格为间隔的单词,用于之后的扫描,后缀列表用于存放最终转换结果。
扫描中缀列表时,遇到左括号和操作符则压入临时栈,特别要注意的是遇到操作符时要对比当前操作符和栈顶操作符的优先级,若当前优先级高于栈顶则将其压入临时栈中,否则反复弹出栈顶操作符,直到栈顶操作符优先级低于当前操作符,弹出的栈顶操作符则加入到后缀列表中作为最终结果。遇到右括号则弹出临时栈中非左括号的操作符,并将其添加到后缀列表中作为最终结果。
def infix_to_postfix(infix_expr) :
prepare = {} #通过设立字典来比较操作符的优先级
prepare['*'] = 3
prepare['/'] = 3
prepare['+'] = 2
prepare['-'] = 2
prepare['('] = 1
postfix_list = [] #定义后缀列表、中缀列表以及临时存放操作符的栈
infix_list = infix_expr.split()
op_stack = Stack()
for item in infix_list : #开始从左到右扫描
if item in "0123456789" or item in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" : #遇到操作数直接加到后缀列表中
postfix_list.append(item)
elif item == '(' : #遇到左括号压入临时栈中
op_stack.push(item)
elif item == ')' : #遇到右括号则将临时栈中非左括号的操作符弹出并加入后缀列表中
top = op_stack.pop()
while top != '(' :
postfix_list.append(top)
top = op_stack.pop()
else : #排除括号和操作数后便是操作符
while (not op_stack.is_empty()) and (prepare[op_stack.peek()] >= prepare[item]) : #比较优先级,高的弹出并加入后缀列表
postfix_list.append(op_stack.pop())
op_stack.push(item)
while not op_stack.is_empty() :
postfix_list.append(op_stack.pop()) #扫描完后将临时栈中所有的操作符弹出并加到后缀列表末尾
return " ".join(postfix_list) #合成并返回后缀表达式字符串
print(infix_to_postfix("A * B + C * D"))
print(infix_to_postfix("( A + B ) * C "))
上述代码返回结果分别为A B * C D * + 和 A B + C * 。
(4)后缀表达式求值
栈在后缀表达式求值算法中用于临时存放操作数,利用split函数对后缀表达式切片得到后缀表达式的单词列表。
扫描时遇到操作数压入临时栈中,遇到操作符则连续弹出栈中的两个数据,其在操作符前后的顺序与弹出顺序相反,即先弹出的位于操作符后,后弹出的在前面,这对“+”和“*”运算没有什么影响。弹出两个操作数后将其和操作符一并带入自定义的计算函数do_math中计算并得出结果压入栈中,当扫描完后缀列表后弹出栈中的数作为最终结果。
def postfix_val(postfix_expr) :
op_stack = Stack() #临时空栈,用于临时存放操作数
postfix_list = postfix_expr.split() #将后缀表达式解析为单词列表
for item in postfix_list :
if item in "012345678910" :
op_stack.push(int(item))
else :
op2 = op_stack.pop()
op1 = op_stack.pop()
result = do_math(item,op1,op2)
op_stack.push(int(result))
return op_stack.pop()
def do_math(op,num1,num2) :
if op == "+" :
return num1 + num2
elif op == "-" :
return num1 - num2
elif op == "*" :
return num1 * num2
else :
return num1 / num2
postfix_val("10 2 / 3 4 * +")
返回结果为17。
参考资料:Problem Solving with Algorithms and Data Structures using Python