1. 栈(堆栈,stack)
Q:学习完链表之后,可能会有一个问题——链表是用来干什么的?
A:链表和顺序表统称为 线性表。线性表就是存储一组线性的数据。
- 顺序表:将元素顺序地存放在一块连续的存储区里,元素问的顺序关系由它们的存储顺序自然表示
- 链表:将元素存放在通过链接构造起来的一系列存储块中。
不管我们将数据进行连续存放还是离散存放,二者终归还是一个线性表。
对于这样的线性数据,我们应该如何利用它们呢?
这就引出了主题 —— 栈。
1.1 栈的概念
栈(stack),有些地方称为堆栈,是一种容器,它可以:
- 存入数据元素
- 访问元素
- 删除元素
它的特点在于只能允许在容器的一端(称为栈顶端指标top)进行加入数据(push)和输出数据(pop)的运算。
没有了位置概念,保证任何时候可以访问、删除的元素都是此前最后存入的那个元素,确定了一种默认的访问顺序。
由于栈数据结构只允许在一端进行操作,因而按照后进先出(LIFO,Last In First Out)的原理运作。
看图说话,堆栈类似于杯子,只有一个出口,底是封死的。
扫描二维码关注公众号,回复: 14255497 查看本文章
- 进栈 = 压栈 = 入栈
- 出栈 = 弹栈
1.2 堆栈该如何实现?
- 顺序表:因为bottom是完全封死的,元素只允许从top进出,所以使用顺序表是完全没有问题
- 链表:当然也可以的,我们将链表的
add(item)
方法屏蔽,只留下append(item)
方法即可
那么二者孰优孰劣?
操作 | 顺序表 | 链表 |
---|---|---|
search() 方法) |
O ( 1 ) O(1) O(1) | O ( n ) O(n) O(n) |
O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | |
在尾部插入/删除元素 | O ( 1 ) O(1) O(1) | O ( n ) O(n) O(n) |
O ( n ) O(n) O(n) | O ( n ) O(n) O(n) |
很明显,append(item)
方法来说,顺序表的时间复杂度更低,更优。
1.3 栈结构实现
1.3.1 栈实现的操作
Stack()
:创建一个新的空栈push(item)
:添加一个新的元素item
到栈顶(top)pop(item)
:弹出(删除)栈顶元素peek(item)
:返回栈顶元素is_empty()
:判断栈是否为空size()
:返回栈中元素的个数
1.3.2 代码实现
class Stack():
"""栈
因为我们要使用Python中的list来实现顺序表,
因此类定义时应该声明一个list
"""
def __init__(self):
"""
因为我们使用Python的List来充当顺序表,
而List从前添加元素和从后添加元素都是
可以的,因此我们在用List实现stack时,
方法是多样的,但我们应当选择时间复杂度
最低的方法
"""
self.__list = []
def push(self, item):
"""添加一个新的元素item到栈顶(top)
对于这个实现我们有两种方法:
1. self.__list.append(item) -> 将List的头当作bottom
2. self.__list.insert(0, item) -> 将List的尾当作bottom
然而这两种方式我们之前说过,append的时间复杂度比insert要低,
因此我们最后选择List.append的方法
"""
self.__list.append(item)
def pop(self):
"""弹出(删除)栈顶元素"""
return self.__list.pop() # 默认从尾部弹出元素(删除)
def peek(self):
"""返回栈顶元素
特殊条件:
1. 如果List为空
"""
if self.__list: # 如果List不为空
return self.__list[-1]
else: # 如果List为空
return None
def is_empty(self):
"""判断栈是否为空
下面的均return为False:
1. ""
2. 0
3. {}
4. ()
5. []
"""
# return self.__list == []
return not self.__list
def size(self):
"""返回栈中元素的个数"""
return len(self.__list)
if __name__ == "__main__":
stack = Stack()
"""顺序压入1, 2, 3, 4"""
stack.push(1)
stack.push(2)
stack.push(3)
stack.push(4)
"""顺序弹出4次"""
print(stack.pop())
print(stack.pop())
print(stack.pop())
print(stack.pop())
结果:
4
3
2
1
可以看到,堆栈的特点就是:先进后出(后来者居上)
接下来要将的队列就和堆栈不同了。
2. 队列(queue)
2.1 队列的概念
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
于栈不同,堆栈的一边是封死的,只能一端进/出;
而队列是一端入,另外一端出
队列是一种先进先出的(First In First Out,FIFO)的线性表,简称FIFO。允许插入的一端为队尾,允许删除的一端为队头。
队列不允许在中间部位进行操作!
假设队列是 q = ( a 1 , a 2 , . . . , a n ) q=(a_1, a_2, ..., a_n) q=(a1,a2,...,an),那么 a 1 a_1 a1就是队头元素,而 a n a_n an是队尾元素。这样我们就可以删除时,总是从 a 1 a_1 a1开始,而插入时,总是在队列最后。
这也比较符合我们通常生活中的习惯,排在第一个的优先出列,最后来的当然排在队伍最后。
2.2 队列实现的功能
Queue()
:创建一个空的队列enqueue(item)
:往队列中添加一个item元素dequeue()
:从队列头部删除一个元素is_empty()
:判断一个队列是否为空size()
:返回队列的大小
2.3 队列的代码实现
class Queue():
"""队列
这里我们使用的构造函数使用的是:Python封装好的List
"""
def __init__(self):
self.__list = []
def enqueue(self, item):
"""往队列中添加一个item元素"""
self.__list.append(item) # 方案1:从尾部添加 -> O(1)
# self.__list.insert(0, item) # 方案2: 从头部添加 -> O(n)
def dequeue(self):
"""从队列头部删除一个元素
我们发现,两套方案总有一个操作的时间复杂度是O(1),另外一个是O(n)
-----
那么我们应该怎么选?
我们需要搞明白,在今后使用这个Queue,主要是入队操作还是出队操作
+ 入队频繁:选用方案1
+ 出队频繁:选用方案2
"""
return self.__list.pop(0) # 方案1:必须从头部弹出 -> O(n)
# self.__list.pop() # 方案2:从尾部弹出 -> O(1)
def is_empty(self):
"""判断一个队列是否为空"""
return not self.__list
def size(self):
"""返回队列的大小"""
return len(self.__list)
if __name__ == "__main__":
queue = Queue()
"""入队是按照1, 2, 3, 4的顺序入队的"""
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
queue.enqueue(4)
"""出队时也是按照1, 2, 3 ,4的顺序进行出队 -> 先进先出(而堆栈是先进后出)"""
print(queue.dequeue())
print(queue.dequeue())
print(queue.dequeue())
print(queue.dequeue())
结果:
1
2
3
4
- 队列:先进先出
- 堆栈:先进后出
2.4 关于队列的注意事项
我们刚才写代码时发现:
入队和出队操作,总有一个的时间复杂度是 O ( 1 ) O(1) O(1),另外一个是 O ( n ) O(n) O(n)。
def __init(self): # 构造函数
self.__list = []
def enqueue(self, item):
"""往队列中添加一个item元素"""
self.__list.append(item) # 方案1:从尾部添加 -> O(1)
self.__list.insert(0, item) # 方案2:从头部添加 -> O(n)
def dequeue(self):
"""从队列头部删除一个元素
"""
self.__list.pop(0) # 方案1:必须从头部弹出 -> O(n)
self.__list.pop() # 方案2:从尾部弹出 -> O(1)
遇到这样的情况,我们需要我们需要搞明白,在今后使用这个Queue,主要是入队操作还是出队操作:
- 入队频繁:选用方案1
- 出队频繁:选用方案2
3. 队列Queue的扩展 —— 双端队列
3.1 双端队列的概念
双端队列(deque,全名double-ended queue),是一种具有队列和栈的性质的数据结构。
双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。
双端队列可以在队列任意一端入队和出队。
如果我们单看一侧,其实就是栈
3.2 双端队列要实现的功能
Deque()
:创建一个空的双端队列add_front(item)
:从队头加入一个item元素add_rear(item)
:从队尾加入一个item元素remove_front
:从队头删除一个item元素remove_rear
:从队尾删除一个item元素is_empty()
:判断双端队列是否为空size()
:返回双端队列的大小
3.3 双端队列代码实现
class Deque():
"""双端队列
怎么定义构造函数List的头在哪里都可以,哪个顺手用哪个
"""
def __init__(self):
self.__list = []
def add_front(self, item):
"""从队头加入一个item元素"""
self.__list.insert(0, item)
def add_rear(self, item):
"""从队尾加入一个item元素"""
self.__list.append(item)
def remove_front(self):
"""从队头删除一个item元素"""
return self.__list.pop(0)
def remove_rear(self):
"""从队尾删除一个item元素"""
return self.__list.pop()
def is_empty(self):
return not self.__list
def size(self):
return len(self.__list)
if __name__ == "__main__":
deque = Deque()
"""[test 1]一头一尾添加元素
一头一尾删除元素
和堆栈是一样的,先进后出
"""
deque.add_front(1)
deque.add_rear(1)
deque.add_front(2)
deque.add_rear(2)
deque.add_front(3)
deque.add_rear(3)
deque.add_front(4)
deque.add_rear(4)
print(deque.remove_front())
print(deque.remove_rear())
print(deque.remove_front())
print(deque.remove_rear())
print(deque.remove_front())
print(deque.remove_rear())
print(deque.remove_front())
print(deque.remove_rear())
print("[test 1]" * 3)
"""[test 2]在头部添加元素
在头部删除元素
和Stack一样,先进后出
"""
deque.add_front(1)
deque.add_front(2)
deque.add_front(3)
deque.add_front(4)
print(deque.remove_front())
print(deque.remove_front())
print(deque.remove_front())
print(deque.remove_front())
print("[test 2]" * 3)
"""[test 3]在尾部添加元素
在尾部删除元素
和Stack一样,先进后出
"""
deque.add_rear(1)
deque.add_rear(2)
deque.add_rear(3)
deque.add_rear(4)
print(deque.remove_rear())
print(deque.remove_rear())
print(deque.remove_rear())
print(deque.remove_rear())
print("[test 3]" * 3)
"""[test 4]在头部添加元素
在尾部删除元素
和Queue一样,先进先出
"""
deque.add_front(1)
deque.add_front(2)
deque.add_front(3)
deque.add_front(4)
print(deque.remove_rear())
print(deque.remove_rear())
print(deque.remove_rear())
print(deque.remove_rear())
print("[test 4]" * 3)
"""[test 5]在尾部添加元素
在头部删除元素
和Queue一样,先进先出
"""
deque.add_rear(1)
deque.add_rear(2)
deque.add_rear(3)
deque.add_rear(4)
print(deque.remove_front())
print(deque.remove_front())
print(deque.remove_front())
print(deque.remove_front())
print("[test 5]" * 3)
4
4
3
3
2
2
1
1
[test 1][test 1][test 1]
4
3
2
1
[test 2][test 2][test 2]
4
3
2
1
[test 3][test 3][test 3]
1
2
3
4
[test 4][test 4][test 4]
1
2
3
4
[test 5][test 5][test 5]
4. 数据结构总结
现在,有关数据结构的基本概念已经讲完了。接下来,我们将扩展到二维平面上去。
主要讲的是,线性表的排序算法。