• 一本正经的聊数据结构(3):栈和队列


    前文传送门:

    「一本正经的聊数据结构(1):时间复杂度」

    「一本正经的聊数据结构(2):数组与向量」

    引言

    前一篇内容我们介绍了数组和向量,虽然说向量是数组的一个升级版,但是在另一个维度上,他们都属于线性结构。

    那么什么是线性结构呢?

    线性结构是一个有序数据元素的集合。常用的线性结构有:线性表,栈,队列,双队列,数组,串。

    线性结构是最常用的数据结构,它最大的特点是数据元素之间存在一对一的线性关系。

    线性结构拥有两种不同的存储结构,即顺序存储结构和链式存储结构。

    顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的。

    链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息。

    线性结构中存在两种操作受限的使用场景,就是我们本文要介绍的栈和队列。

    至于为什么说栈和队列是受限的线型结构,我们下面细聊。

    栈是一种比较奇葩的数据结构,栈的结构是支持对象的插入和删除操作,但是,栈操作的范围仅限于栈的某一特定端,就是下面这样的。

    栈遵循先进后出( last-in-first-out, LIFO )的规律,这是重点。

    栈一般使用两种方式来实现:

    1. 顺序表:采用顺序存储结构可以模拟栈存储数据的特点,从而实现栈存储结构。
    2. 链表:采用链式存储结构实现栈结构。

    注意,这两种实现方式的区别,仅限于数据元素在实际物理空间上存放的相对位置,顺序栈底层采用的是数组,链栈底层采用的是链表。

    栈结构我们还是会经常用到,一个非常经典的场景就是在浏览器的后退功能中。

    例如我们每次打开一个页面,浏览器都会把这个页面放入栈中,当我们点击后退按钮的时候,在从栈中将这个页面取出来。

    顺序栈

    顺序栈,是用顺序表实现栈存储结构。

    栈存储结构操作数据元素必须遵守 「先进后出 LIFO 」 的原则。

    顺序表的底层是使用数组来实现的,简单理解可以直接理解成数组。

    只是栈结构对数据的存取过程有特殊的限制,而数组是没有的。

    链栈

    链栈,是用链表实现栈存储结构。

    链表这个结构在前面没聊过,简单画个图大家理解下:

    链表的结构相比较数组而言就稍微有些复杂了,链表的每个节点由两部分组成,一个是存放数据的,叫数据域,另一个是存放指针的,叫指针域。

    数组在内存中是连续的,所以我们可以轻松的知道数组的每一个元素的位置,而链表在内存中是分散的,我们需要一个指针来指明下一个元素在哪里。

    这里介绍的其实是最简单的一种链表,叫单链表,顾名思义,除了单链表之外还有双链表,这个我们有机会后面再聊。

    那么链栈是将链表的头部作为栈顶,尾部作为栈底。

    将链表头部作为栈顶的一端,可以避免在实现数据 「入栈」 和 「出栈」 操作时做大量遍历链表的耗时操作。

    链表的头部作为栈顶,意味着:

    • 在实现数据"入栈"操作时,需要将数据从链表的头部插入。
    • 在实现数据"出栈"操作时,需要删除链表头部的首元节点。

    因此,链栈实际上就是一个只能采用头插法插入或删除数据的链表。

    Python 实现栈

    在 Python 中,栈并不是一个基础数据结构,不过我们可以通过代码来简单的实现它。

    因为栈是可以通过两种方式来实现,一种是顺序表,另一种是链表:

    首先是最简单的通过顺序表来实现栈,这里使用的是 Python 中的 list 列表:

    class Stack(object):
    
        def __init__(self):
            '''
            创建空列表实现栈
            '''
            self.__list = []
    
        def is_empty(self):
            '''
            判断是否为空
            :return:
            '''
            return self.__list == []
    
        def push(self,item):
            '''
            压栈,添加元素
            :param item:
            :return:
            '''
            self.__list.append(item)
    
        def pop(self):
            '''
            弹出栈,将元素取出
            :return:
            '''
            if self.is_empty():
                return
            else:
                return self.__list.pop()
    

    如果不想使用顺序表来实现,还可以使用链表,这里使用的是单链表,链表的结构需要先提前定义:

    class Node(object):
        '''
        节点实现
        '''
        def __init__(self,elem):
            self.elem = elem
            self.next = None
    
    
    class Stack(object):
        def __init__(self):
            '''
            初始化链表头
            '''
            self.__head = None
    
        def is_empty(self):
            return self.__head is None
    
        def push(self, item):
            '''
            压栈
            :param item:
            :return:
            '''
            node = Node(item)
            node.next = self.__head
            self.__head = node
    
        def pop(self):
            '''
            弹出栈
            :return:
            '''
            if self.is_empty():
                return
            else:
                p = self.__head
                self.__head = p.next
                return p.elem
    

    在链表的实现中,我这里先定义了链表的数据结构,然后才定义了栈。

    上面两段代码都非常简单,只实现了最简单的两个功能,入栈和出栈,感兴趣的同学可以自己动手实现下。

    队列

    与栈一样,队列( queue) 也是存放数据对象的一种容器,其中的数据对象也按线性的逻辑次序排列。

    队列和栈不一样的地方在于栈是先进后出,而队列是先进先出( first-in-first-out, FIFO )。

    同栈一样的是队列也有两种实现方式:

    • 顺序队列:在顺序表的基础上实现的队列结构。
    • 链队列:在链表的基础上实现的队列结构。

    Python 中的 Queue

    在 Python 的标准库中,Python 为我们提供了线程安全的队列 Queue (总算不用我再自己写个队列了),使用方法异常简单:

    先进先出队列 (FIFO) :

    import queue
    
    q1 = queue.Queue(maxsize=5)
    
    for i in range(5):
        q1.put(i)
    
    while not q1.empty():
        print('q1:',q1.get())
    
    # 结果输出
    q1: 0
    q1: 1
    q1: 2
    q1: 3
    q1: 4
    

    Queue 这个标准库中,还为我们提供了 LIFO 队列,即先进后出队列,和我们前面介绍的栈非常类似,翻了下源码,看到是使用 list 实现的,和我们上面的实现基本一致,使用方式如下:

    import queue
    
    q2 = queue.LifoQueue(maxsize=5)
    
    for i in range(5):
        q2.put(i)
    
    
    while not q2.empty():
        print('q2:',q2.get())
    
    # 结果输出
    q2: 4
    q2: 3
    q2: 2
    q2: 1
    q2: 0
    

    本篇内容就这样了,涉及到的代码肯定会上传代码仓库,有兴趣的同学可以去翻翻看。

    示例代码

    示例代码-Github

    示例代码-Gitee

  • 相关阅读:
    A1035
    A1005
    A1073
    A1061
    A1058
    A1027
    A1019
    Java 操作临时文件创建与删除
    面试必会之HashMap源码分析
    springboot整合cxf框架启动报错
  • 原文地址:https://www.cnblogs.com/babycomeon/p/12784649.html
Copyright © 2020-2023  润新知