栈,队列,双端队列和列表都是有序的数据集合,其元素的顺序取决于添加或移除顺序。一旦某个元素被添加进来,它与前后元素的相对位置将保持不变。这样的数据集合经常被称为线性数据结构。
线性数据结构可以看作有两端。这两端有时候被称作“左端”和“右端”,有时候也被称作“前端”和“后端”。当然,它们还可以被称作“顶端”和“底端”。名字本身并不重要,真正区分线性数据结构的是元素的添加方式和移除方式,尤其是添加操作和移除操作发生的位置。举例来说,某个数据结构可能只允许在一端添加新元素,有些则允许从任意一端移除元素。
要搞清楚这个概念,首先要明白”栈“原来的意思,如此才能把握本质。"栈“者,存储货物或供旅客住宿的地方,可引申为仓库、中转站,所以引入到计算机领域里,就是指数据暂时存储的地方,所以才有进栈、出栈的说法。
栈有时候也被称为“下推栈”,它是有序集合,添加操作和移除操作总发生在同一侧,即“顶端”,另外一端被称为“底端”。
栈中的元素离底端越近,代表在栈中间的时间越长,因此栈的底端具有非常重要的意义。最新加入栈的元素将被最先移除。**这种排序原则被称为LIFO(last-in first-out),即后进后出。**它提供了一种基于在集合中的时间来排序的方式。最近添加的元素靠近顶端,旧元素则靠近底端。
栈具有反转(元素)特性,栈的应用很多,比如浏览器的返回(后退back)按钮,利用了一个栈,存储浏览过的网页的URL,单击按钮式,最新加入栈的URL弹出。
栈支持以下操作
Stack() 创建一个空栈。不需要参数,且会返回一个空栈。
push(item)将一个元素添加到栈的顶端。他需要一个参数item,且无返回值。 (即进栈)
pop()将栈顶端的元素移除。它不需要参数,但会返回顶端的元素,并且修改栈的内容。 (即出栈)
peek()返回栈顶端的元素,但是并不移除该元素。他不需要返回参数,也不会修改栈的内容。
isEmpty() 检查栈是否为空。它不需要参数,且会返回一个布尔值。
size() 返回栈中元素的数目。它不需要参数,且会返回一个整数。
class Stack: # 定义一个栈
def __init__(self): # 初始化栈为空列表
self.items = []
def isEmpty(self):
return self.items == []
def push(self,item):
self.items.append(item) # append()尾段加入数据
def pop(self):
return self.items.pop()
def peek(self):
return self.items[len(self.items)-1]
def size(self):
return len(self.items)
class Stack: # 定义一个栈 方法二 首端作为栈顶栈底
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def push(self,item):
self.items.insert(0,item)
def pop(self):
return self.items.pop(0) # 此方法pop(0)时间复杂度提高了
def peek(self):
return self.items[0]
def size(self):
return len(self.items)
s = Stack()
print(s.isEmpty())
s.push(4)
s.push("dog")
s.push(True)
print(s.peek())
print(s.pop())
print(s.peek())
括号匹配必须遵循平衡原则。
首先,每个开括号要恰好对应一个闭括号。
其次,每对开闭括号要正确的嵌套。
正确的括号:(()()()), (((())))), (()((()()))
错误的括号:(((()),()))),(()()(()
对括号是否正确匹配的识别,是很多语言编译器的基础算法。
def matches(open, close): # 进行匹配操作的匹配的函数
opens = ("([{") # 定义opens 包含三个左括号
closers = (")]}") # 定义closers 包含三个右括号
return opens.index(open) == closers.index(close) # 返回值为bool值 判断栈顶元素top和取到的元素symbol在两个
def parChecker(symbolString):
s = Stack() # 生成一个空栈s
balanced = True # 初始化balanced为True
index = 0 # index次序
while index < len(symbolString) and balanced: # 当index次序小于字符串的长度并且目前为止是平衡时 依次取元素
symbol = symbolString[index] #取其中一个元素赋值给symbol
if symbol in "([{": # 如果是左括号
s.push(symbol) # 入栈
else: # 如果是右括号
if s.isEmpty(): # 先判断栈是否为空,如果为空的话
balanced = False # banlanced为False 说明栈进去了一个右括号,不平衡
else: # 栈不为空,说明栈里面有左括号
top = s.pop() # 顶部元素(左括号)弹出,赋值给top
if not matches(top,symbol): # 利用matches函数进行比较,看弹出的top和此时取到的元素symbol是否相同
balanced = False # 先整体考虑算法实现过程,具体怎么匹配之后再考虑(抽象的思想)
index = index + 1 # index加一,继续下一次操作
if balanced and s.isEmpty(): # 最后判断 如果balanced为True 并且 栈最后为空,说明全部匹配成功(没有剩余,一一对应),返回True
return True
else:
return False
print(parChecker("({})[]")) # 测试语句
# 结果为True
数字进制转换
所谓进制,就是用多少个字符来表示整数。
十进制是0~9这十个数字字符,二进制是0,1两个字符。
# 除以2算法的python实现
def divideBy2(decNumber):
remstack = Stack() # 定义一个remstack变量,初始化为空栈
while decNumber > 0:
rem = decNumber % 2 .
# 变量rem存储所得到的余数 求余数
remstack.push(rem) # 求模 将求的的余数push()进栈
decNumber = decNumber // 2 # 取余 整数除 继续除2 循环
binString = "" # 定义一个字符串存储要输出的数字
while not remstack.isEmpty(): # 当栈非空时
binString = binString + str(remstack.pop()) # str()函数 返回一个值的字符串形式 pop()弹出栈顶元素
return binString # 返回值 函数返回binString 存储要输出的字符串 利用栈反转了次序
print(divideBy2(233))
# 数字进制转换 (暂时最高支持16进制),
def divideByN(decNumber,base): # 需要进行转换的数字 多了一个参数base 转换为几进制
digits = "0123456789ABCDEF" # digits(数字) 字符串形式存储
remstack = Stack() # 定义一个栈remstack
while decNumber > 0:
rem = decNumber % base # 定义rem变量 存储
remstack.push(rem)
decNumber = decNumber // base # 取余数
newString = "" # 定义一个字符串存储要输出的数字
while not remstack.isEmpty(): # 当栈非空时
newString = newString + digits[remstack.pop()] # 输出字符串 digits是字符串取字符串
return newString # 返回值
print(divideByN(233,4))
print(divideByN(233,8))
print(divideByN(233,16))
前序,中序,后序表达式
计算机需要使用完全括号表达式
比如A + B * C + D可以重新写成((A + (B * C) ) + D);
通过改变运算符和操作数的相对位置,可以得到前序表达式和后序表达式。
相互转化的方法
首先写出中序表达式全括号表达式,
将操作符替换至原本左括号的位置,得到前序表达式;
将操作符替换至原本右括号的位置,得到后序表达式;
中序 前序 后序
A + B * C + A * B C A B C * +
( A + B ) * C * + A B C A B + C *
中序转后序的通用算法
(1)创建用于保存运算符的空栈opstack,以及一个用于保存结果的空列表。
(2)使用字符串方法split将输入的中序表达式转换成一个列表。
(3)从左往右扫描这个标记列表。
a.如果标记是操作数,将其添加到结果列表的末尾。
b.如果标记是左括号,将其压人 opstack栈中。
c.如果标记是右括号,反复从opstack栈中移除元素,直到移除对应的左括号。将从取出的每一个运算符都添加到结果列表的末尾。
d.如果标记是运算符,将其压入opstack栈中。但是,在这之前,需要先从栈中取出优先级更高或相同的运算符,并将它们添加到结果列表的末尾。
(4)当处理完输入表达式以后,检查opstack。将其中所有残留的运算符全部添加到结果列表的末尾。
def infixToPostfix(infixexpr):
prec = {
}
prec["*"] = 3 # 字典key 操作符 value 优先级的数字
prec["/"] = 3 # 记录操作符优先级
prec["+"] = 2
prec["-"] = 2
prec["("] = 1
opStack = Stack() # 定义opStack为空栈 暂存操作符
postfixList = [] # 定义空列表postfixList 存储后缀表达式 最后的输出结果
tokenList = infixexpr.split() # 将中缀表达式转换为单词(token)列表
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]):# 栈不为空,且栈顶操作符优先级>=此token(单词) 进栈
postfixList.append(opStack.pop())
opStack.push(token)
while not opStack.isEmpty(): # 栈内还有为输出的操作符,弹出到空表postfixList
postfixList.append(opStack.pop())
return " ".join(postfixList)
print(infixToPostfix("A * B + C * D")) # 引号 空格分隔 大写字母或数字(前面规定)
上述代码最后的输出结果为
A B * C D * +
最后一个关于栈的例子是计算后序表达式
当扫描后序表达式时,需要保存操作数,而不是运算符。
换一个角度说当遇到一个运算符时,需要用离他最近的两个操作数来计算。
完整算法描述
假设后序表达式是一个以空格分隔的标记串。其中,运算符标记有*、/、+和-,操作数标记是一位的整数值。结果是一个整数。
(1)创建空栈operandstack。
(2)使用字符串方法split将输入的后序表达式转换成一个列表。
(3)从左往右扫描这个标记列表。
a.如果标记是操作数,将其转换成整数并且压入 operandstack栈中。
b.如果标记是运算符,从operandStack栈中取出两个操作数。第一次取出右操作数,第二次取出左操作数。进行相应的算术运算,然后将运算结果压人operandstack栈中。
(4)当处理完输入表达式时,栈中的值就是结果。将其从栈中返回。
为了方便运算,我们定义了辅助函数doMath。
它接受一个运算符和两个操作数,并进行相应的运算。
# 后缀表达式 求值
def postfixEval(postfixExpr):
operandStack = Stack() # 定义一个空栈 暂存操作数
tokenList = postfixExpr.split() # 定义单词列表tokenList 存储分割后的单词
for token in tokenList: # 开始循环
if token in "0123456789": # 如果是操作数
operandStack.push(int(token)) # 压入转化为 int类型 的单词
else:
operand2 = operandStack.pop() # 先弹出栈顶的数字是右操作数
operand1 = operandStack.pop() # 后弹出栈顶的数字是左操作数
result = doMath(token,operand1,operand2) # 结果利用doMath函数计算结果
operandStack.push(result) # 再将计算结果压入栈
return operandStack.pop() # 最后返回 栈弹出的数据 为最终结果
def doMath(op,op1,op2): # 定义doMath函数 计算运算结果 需要两个运算数,一个运算符op
if op == "*":
return op1 * op2
elif op == "/":
return op1 / op2
elif op == "+":
return op1 + op2
else:
return op1 - op2
print(postfixEval('4 5 6 * +')) # 中间用空格隔开
print(postfixEval('7 8 + 3 2 + /'))
上述代码最后的输出结果为
4
7
介绍了栈的python实现及其简单应用,包括简单括号匹配,进制转换,中缀转后缀表达式,后缀表达式求值。