栈与队列理论基础
1.栈:
- 栈,又称堆栈,是和列表类似的一种数据结构,但是却更高效
- 因为栈内的元素从列表的一端访问,称为栈顶,数据只能在栈顶添加或删除
- 遵循先入后出(LIFO,last-in-first-out)的原则。
class Stack:
def __init__(self):
self.stack = []
def push(self,element):
self.stack.append(element)
def pop(self):
return self.stack.pop()
def peek(self):
if len(self.stack) > 0:
return self.stack[-1]
else:
return None
def size(self):
return len(self.stack)
def is_empty(self):
return len(self.stack) == 0
if __name__ == "__main__":
stack = Stack()
stack.push("hello")
stack.push("world")
stack.push("itcast")
print(stack.size())
print(stack.peek())
print(stack.pop())
print(stack.pop())
print(stack.pop())
print(stack.size())
栈的操作:
- Stack():创建一个新的空栈
- push(item):添加一个新的元素item到栈顶
- pop():弹出栈顶元素
- peek():返回栈顶元素
- is_empty():判断栈是否为空
- size():返回栈的元素个数
2.队列:
- 队列是一种特殊的线性表,仅允许在列表的一端进行插入,另一端进行删除
- 进行插入的一端称为队尾(rear),插入动作称为进队或入队
- 进行删除的一端称为队头(front),删除动作称为出队
- 遵循先进先出(FIFO,First-in-First-Out)
顺序队列
class Queue(object):
"""队列"""
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def enqueue(self, item):
"""进队列"""
self.items.insert(0, item)
def dequeue(self):
"""出队列"""
return self.items.pop()
def size(self):
"""返回大小"""
return len(self.items)
if __name__ == "__main__":
q = Queue()
q.enqueue("hello")
q.enqueue("world")
q.enqueue("itcast")
print(q.size())
print(q.dequeue())
print(q.dequeue())
print(q.dequeue())
顺序队列的操作:
- Queue():创建一个空的队列
- enqueue(item):往队列中添加一个item元素
- dequeue():从队列头部删除一个元素
- is_empty():判断一个队列是否为空
- size():返回队列的大小
双端队列
- 是一种具有队列和栈性质的数据结构。
- 双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行,双端队列可以在队列任意一端入队和出队。
class Double_Queque(object):
"""双端队列"""
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def add_front(self, item):
self.items.insert(0, item)
def add_rear(self, item):
self.items.append(item)
def remove_front(self):
return self.items.pop(0)
def remove_rear(self):
return self.items.pop()
def size(self):
return len(self.items)
if __name__ == "__main__":
deque = Double_Queque()
deque.add_front(1)
deque.add_front(2)
deque.add_rear(3)
deque.add_rear(4)
print(deque.size())
print(deque.remove_front())
print(deque.remove_front())
print(deque.remove_rear())
print(deque.remove_rear())
双端队列的操作:
- Double_Queue():创建一个空的双端队列
- add_front(item):从队头加入一个item元素
- add_rear(item):从队尾加入一个item元素
- remove_front():从队头删除一个item元素
- remove_rear():从队尾删除一个item元素
- is_empty():判断双端队列是否为空
- size():返回队列的大小
思路:
这是一道模拟题,不涉及到具体算法,考察的就是对栈和队列的掌握程度。
使用栈来模式队列的行为,如果仅仅用一个栈,是一定不行的,所以需要两个栈一个输入栈,一个输出栈,这里要注意输入栈和输出栈的关系。
在push数据的时候,只要数据放进输入栈就好,但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入),再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。
最后如何判断队列为空呢?如果进栈和出栈都为空的话,说明模拟的队列为空了。
在代码实现的时候,会发现pop() 和 peek()两个函数功能类似,代码实现上也是类似的,可以思考一下如何把代码抽象一下。
代码:
class MyQueue:
def __init__(self): # 使用list(列表)来模拟一个栈
self.stack_in = [] # 创建入栈
self.stack_out = [] # 创建出栈
def push(self, x: int) -> None:
self.stack_in.append(x) # 往入栈中添加新元素
def pop(self) -> int:
if self.stack_out: # 如果出栈不为空,删除出栈栈顶元素并返回
return self.stack_out.pop()
else: # 如果出栈为空,依次删除入栈栈顶元素(直到为空),添加到出栈
for i in range(len(self.stack_in)):
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop() # 删除出栈栈顶元素并返回
def peek(self) -> int:
res = self.pop() # 直接使用已有的pop函数
self.stack_out.append(res) # 因为pop函数弹出了元素res,所以再添加回去
return res
def empty(self) -> bool:
return not (self.stack_in or self.stack_out) # 只要入栈或者出栈有元素,说明队列不为空
# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()
- 时间复杂度: push和empty为O(1), pop和peek为O(n)
- 空间复杂度: O(n)
思路:
有的同学可能疑惑这种题目有什么实际工程意义,其实很多算法题目主要是对知识点的考察和教学意义远大于其工程实践的意义,所以面试题也是这样!
1.那么我们先说两个队列来实现栈的思路:队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并没有变成先进后出的顺序。所以用栈实现队列, 和用队列实现栈的思路还是不一样的,这取决于这两个数据结构的性质。
但是依然还是要用两个队列来模拟栈,只不过没有输入和输出的关系,而是另一个队列完全用来备份的!用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。
2.其实这道题目就是用一个队列就够了 :一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。
代码:
两个队列来实现栈
from collections import deque # 导入collections模块中的双向队列(deque)类
class MyStack:
def __init__(self): # 使用deque(双向队列)来模拟一个队列
self.queue_in = deque()
self.queue_out = deque()
def push(self, x: int) -> None:
self.queue_in.append(x)
def pop(self) -> int:
for i in range(len(self.queue_in) - 1): # 将in导入out,但要留下最后一个元素
self.queue_out.append(self.queue_in.popleft())
self.queue_in, self.queue_out = self.queue_out, self.queue_in # 交换in和out,这也是为啥in只用来存
return self.queue_out.popleft()
def top(self) -> int:
for i in range(len(self.queue_in) - 1):
self.queue_out.append(self.queue_in.popleft())
self.queue_in, self.queue_out = self.queue_out, self.queue_in
temp = self.queue_out.popleft()
self.queue_in.append(temp)
return temp
def empty(self) -> bool:
return len(self.queue_in) == 0 # 因为只有in存了数据,只要判断in是不是有数即可
# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
- 时间复杂度: pop和top为O(n),其他为O(1)
- 空间复杂度: O(n)
补充:
collections是Python内建的一个集合模块,提供了许多有用的集合类和方法。
可以把它理解为一个容器,里面提供Python标准内建容器 dict , list , set , 和 tuple 的替代选择。
collections模块常用类型有:
双向队列(deque)
类似于list的容器,可以快速的在队列头部和尾部添加、删除元素
计数器(Counter)
dict的子类,计算可hash的对象
默认字典(defaultdict)
dict的子类,可以调用提供默认值的函数
有序字典(OrderedDict)
dict的子类,可以记住元素的添加顺序
可命名元组(namedtuple)
可以创建包含名称的tuple
一个队列来实现栈
from collections import deque
class MyStack:
def __init__(self):
self.que = deque()
def push(self, x: int) -> None:
self.que.append(x)
def pop(self) -> int:
for i in range(len(self.que)-1): # 将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部
self.que.append(self.que.popleft()) # popleft()移除并返回队列开头元素(第一个加入的元素)
return self.que.popleft() # 移除并返回队列开头元素
def top(self) -> int:
temp = self.pop() # 直接使用已有的pop函数
self.que.append(temp) # 因为pop函数弹出了元素temp,所以再添加回去
return temp
def empty(self) -> bool:
return not self.que
# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
- 时间复杂度: pop和top为O(n),其他为O(1)
- 空间复杂度: O(n)