栈:先进后出。
典型场景:浏览网页,当前正在浏览的网页位于栈的顶端,最早浏览的网页则位于底端。如果点击返回按钮,便开始反向浏览这些网页
栈的基本操作
- isEmpty() 检查栈是否为空。它不需要参数,且会返回一个布尔值。
- size() 返回栈中元素的数目。它不需要参数,且会返回一个整数
- push(item) 将一个元素添加到栈的顶端。它需要一个参数item,且无返回值。
- pop()将栈顶端的元素移除。它不需要参数,但会返回顶端的元素,并且修改栈的内容。
- peek()返回栈顶端的元素,但是并不移除该元素。它不需要参数,也不会修改栈的内容。
栈的python实现
(1)选择将列表的尾部作为栈的顶端
class Stack:
def __init__(self): #初始化栈为空列表
self.items = [] #构造的属性
#判断栈是否为空,返回布尔值
def is_empty(self):
return self.items ==[]
#查询栈顶元素
def peek(self):
return self.items[len(self.items)-1]
#返回栈的大小
def size(self):
return len(self.items)
# 入栈
def push(self,item):
self.items.append(item)
#出栈
def pop(self):
return self.items.pop()
if __name__=="__main__":
# 初始化一个栈对象
my_stack = Stack()
# 把'h'丢进栈里
my_stack.push('h')
# 把'a'丢进栈里
my_stack.push('a')
# 看一下栈的大小(有几个元素)
print (my_stack.size())
# 打印栈顶元素
print (my_stack.peek())
# 把栈顶元素丢出去,并打印出来
print (my_stack.pop())
# 再看一下栈顶元素是谁
print (my_stack.peek())
# 这个时候栈的大小是多少?
print (my_stack.size())
# 再丢一个栈顶元素
print (my_stack.pop())
# 看一下栈的大小
print (my_stack.size)
# 栈空?
print (my_stack.is_empty())
(2)选择将列表的头部作为栈的顶端:
class Stack:
def __init__(self): #初始化栈为空列表
self.items = [] #构造的属性
#判断栈是否为空,返回布尔值
def is_empty(self):
return self.items ==[]
#查询栈顶元素
def peek(self):
return self.items[0]
#返回栈的大小
def size(self):
return len(self.items)
# 入栈
def push(self,item):
self.items.insert(0,item)
#出栈
def pop(self):
return self.items.pop(0)
if __name__=="__main__":
# 初始化一个栈对象
my_stack = Stack()
# 把'h'丢进栈里
my_stack.push('h')
# 把'a'丢进栈里
my_stack.push('a')
# 看一下栈的大小(有几个元素)
print (my_stack.size())
# 打印栈顶元素
print (my_stack.peek())
# 把栈顶元素丢出去,并打印出来
print (my_stack.pop())
# 再看一下栈顶元素是谁
print (my_stack.peek())
# 这个时候栈的大小是多少?
print (my_stack.size())
# 再丢一个栈顶元素
print (my_stack.pop())
# 看一下栈的大小
print (my_stack.size)
# 栈空?
print (my_stack.is_empty())
性能比较
想说一下,上述两种方法都实现了栈。但是在性能方面肯定有所不同。
- append方法和pop()方法的时间复杂度都是O(1),这意味着不论栈中有多少个元素,第一种实现中的push操作和pop操作都会在恒定的时间内完成。
- insert(0)和pop(0)的时间复杂度都是O(n),这意味着第二种实现的性能则受制于栈中的元素个数
栈的应用:匹配括号
匹配括号是指每一个左括号都有与之对应的一个右括号,并且括号对有正确的嵌套关系。
from data_structure import Stack #这里要说一下,一个文件调用另一个文件中的类或者函数,
#详见本文最后: 一个A.py文件如何调用另一个B.py文件中的类和函数
def parChecker(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.is_empty():
balanced = False
else:
top = s.pop()
if not matches(top,symbol):
balanced = False
index = index + 1
if balanced and s.is_empty():
return True
else:
return False
def matches (open,close):
opens = "[{("
closes = "]})"
return opens.index(open) == closes.index(close)
写在最后,一些杂七杂八的基础知识,可见自己真是代码小白了。
- 在编写栈的实现程序的时候,发现还是搞不懂python面向对象的一些基础知识,于是又学习了一下。
- self 代表类的实例,self在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。
- 类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。当然,把self换成其他的词语也是可以正常执行的
- 一个A.py文件如何调用另一个B.py文件中的类和函数
- 如果文件均在同一个文件夹下
- B.py调用A.py中的函数:
import A
或者from A import 函数名
- B.py调用A.py中的类:
import A
或者from A import A
- B.py调用A.py中的函数:
- 如果文件在不同文件夹下
- A.py文件的文件路径:E:\PythonProject\sq ;B.py文件:
- 如果文件均在同一个文件夹下
import sys
sys.path.append(r'E:\PythonProject\sq')
#python import模块时, 是在sys.path里按顺序查找的。sys.path是一个列表,里面以字符串的形式存储了许多路径。使用A.py文件中的函数需要先将他的文件路径放到sys.path中
import A