301 什么是线性结构
什么是线性结构 Linear Structure
- 线性结构是一种有序数据项的集合, 其中每个数据项都有唯一的前驱和后继.
- 线性结构总有两端, 在不同情况下, 两端的称呼也不同
- 不同线性结构的关键在于数据项增减的方式
- 学习四个简单但功能强大的线性数据结构: 栈 Stack、队列 Queue、双端队列 Deque 和列表 List
- 应用广泛
- 出现在各种算法中, 可以解决大量重要问题
302 栈抽象数据类型及 Python 实现
栈 Stack: 啥是栈?
- 一种有次序的数据项集合, 在栈中, 数据项的加入和移除都仅发生在同一端
- 日常生活中的栈: 书堆等
- 距离栈底越近的数据项, 留在栈中的时间就越长
- 这种次序称称为"先进先出LIFO", Last in First out
栈的特性: 反转次序
- 进栈和出栈次序正好反转
抽象数据类型 Stack
- 抽象数据类型"栈"是一个有次序的数据集, 每个数据项仅从"栈顶"一端加入到数据集中, 从数据集中移除, 栈具有后进先出的 LIFO 的特性
- 抽象数据类型"栈"定义为如下操作: ++ Stack(): 创建一个空栈, 不包含任何数据项 ++ push(item): 将 item 加入栈顶, 无返回值 ++ pop(): 将栈顶数据项移除, 并返回, 栈被修改 ++ peek(): 窥视栈顶数据, 并返回栈顶的数据但不移除, 栈不被修改 ++ isEmpty(): 返回栈是否为空栈 ++ size(): 返回栈中有多少个数据项
用 Python 实现 ADT Stack
- 在定义抽象数据类型 Stack 之后, 看一下如何利用 Python 实现
- 基于 Python 的面向对象编程, 可以用来实现用户自定义数据类型
课程配套代码: pythonds 模块
调用方法:
from pythonds.basic.stack import Stack
# ADT Stack
# 将 list 末尾作为栈底
class Stack(object):
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)
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.8)
print(s.pop())
8.8
print(s.pop())
True
print(s.size())
2
ADT Stack 的另一端实现
- 若将 list 的另一端作为 Stack 的栈顶, 同样也可以实现 Stack
- 不同实现方案保持了 ADT 接口的稳定性, 性能有所不同
class Stack(object):
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
# 复杂度为 O(n)
def push(self, item):
self.items.insert(0, item)
# 复杂度为 O(n)
def pop(self):
return self.items.pop(0)
def peek(self):
return self.items.pop(0)
def peek(self):
return len(self.items)
303 栈的应用: 简单括号匹配
栈的应用: 简单括号匹配
- 如何构造匹配识别算法: 从左到右扫描括号串, 最新打开的左括号要匹配最后打开的右括号, 这种次序反转的识别, 符合栈的特性.
识别算法流程图为
from pythonds.basic.stack import Stack
def parChecker(symbolString):
s = Stack()
balanced = True
index = 0
while index and balanced:
symbol = symbolString[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("()()()()()((()))"))
True
print(parChecker('((())'))
False
更多的括号匹配
- 在实际应用中, 会碰到更多的括号, 如 "[]", "{}".
- 这些括号可能混在一起用
- 注意整个字的开闭匹配情况
通用括号匹配算法: 代码
from pythonds.basic.stack import Stack
def parChecker(symbolString):
s = Stack()
balanced = True
index = 0
while index 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 += 1
if balanced and s.isEmpty():
return True
else:
return False
def matches(open, close):
opens = "([{"
closers = ")]}"
return opens.index(open) == closers.index(close)
print(parChecker('{([])}{}{}[({})]'))
True
304 栈的应用: 十进制转换为二进制
十进制转换为二进制
- 二进制是计算机中最基本的概念, 其输入和输出仅为两种情况: 0 和 1
- 对我们人而言, 十进制似乎更为常用.
- x 进制的概念: 例如
- 十进制数转换为二进制数可以利用"除以2求余数"的算法求解
from pythonds.basic.stack import 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
print(divideBy2(52))
110100
扩展到更多进制转换
- 将"除以2求余数"算法改为"除以N求余数"算法
- 计算机的另外两种进制是八进制和十六进制
十进制转换为十六以下的任意进制: 代码
from pythonds.basic.stack import Stack
def baseConverter(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, 2))
print(baseConverter(25, 16))
print(baseConverter(666, 16))
11001
19
29A
305 表达式的转换(上)
中缀表达式
- B * C 表示 B 乘以 C
- 这种操作符(operator)介于操作数(operand)中间的表示方法叫做"中缀"表示法
- 中缀表示法有时会会引起混淆, 例如 A + B * C
- 为了消除这种混淆, 可以引入操作符的"优先级"
- 引入括号表示强制优先级
全括号中缀表达式
- 好处是便于计算机处理
- 引入全括号表达式, 即在所有表达式的两边都加上括号. 例如 A+BC+D 即((A+(BC)+D))
前缀和后缀表达式
- 中缀表达式 A+B 改成前缀即, +AB; 改成后缀表达式即 AB+
- 移动操作符的位置, 即得到前缀和后缀表达式两种表示方法
- A+B * C 的前缀表达式为 +ABC, 后缀表达为 ABC+
306 表达式转换(下)
通用的中缀转后缀算法
from pythonds.basic.stack import Stack
def infixToPostfix(infixexpr):
# 操作符优先级
prec = {}
prec["*"] = 3
prec["/"] = 3
prec["+"] = 2
prec["-"] = 2
prec["("] = 1
opStack = Stack()
postfixList = []
tokenList = infixexpr.split()
for token in tokenList:
if token in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" or token in "012345679":
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"))
print(infixToPostfix("( A + B ) * C - ( D - E ) * ( F + G )"))
A+B*C
A B + C * D E - F G + * -
307 后缀表达式求值
后缀表达式求值
- 作为栈结构的结束, 讨论"后缀表达式求值"问题
from pythonds.basic.stack import Stack
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()
def doMath(op, op1, op2):
if op == "*":
return op1 * op2
elif op == "/":
return op1 / op2
elif op == "+":
return op1 + op2
else:
return op1 - op2
print(postfixEval('7 8 + 3 2 + /'))
3.0