数据结构与算法_part_4_①栈②队列③双端队列

1. 栈(堆栈,stack)

Q:学习完链表之后,可能会有一个问题——链表是用来干什么的

A:链表和顺序表统称为 线性表。线性表就是存储一组线性的数据。

  • 顺序表:将元素顺序地存放在一块连续的存储区里,元素问的顺序关系由它们的存储顺序自然表示
  • 链表:将元素存放在通过链接构造起来的一系列存储块中。

不管我们将数据进行连续存放还是离散存放,二者终归还是一个线性表。


对于这样的线性数据,我们应该如何利用它们呢?

这就引出了主题 ——


1.1 栈的概念

栈(stack),有些地方称为堆栈,是一种容器,它可以:

  1. 存入数据元素
  2. 访问元素
  3. 删除元素

它的特点在于只能允许在容器的一端(称为栈顶端指标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. 数据结构总结

现在,有关数据结构的基本概念已经讲完了。接下来,我们将扩展到二维平面上去。

主要讲的是,线性表的排序算法。

猜你喜欢

转载自blog.csdn.net/weixin_44878336/article/details/125226716