一本正经的聊数据结构(3):栈和队列
前文传送门:
引言
前一篇内容我们介绍了数组和向量,虽然说向量是数组的一个升级版,但是在另一个维度上,他们都属于线性结构。
那么什么是线性结构呢?
线性结构是一个有序数据元素的集合。常用的线性结构有:线性表,栈,队列,双队列,数组,串。
线性结构是最常用的数据结构,它最大的特点是数据元素之间存在一对一的线性关系。
线性结构拥有两种不同的存储结构,即顺序存储结构和链式存储结构。
顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的。
链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息。
线性结构中存在两种操作受限的使用场景,就是我们本文要介绍的栈和队列。
至于为什么说栈和队列是受限的线型结构,我们下面细聊。
栈
栈是一种比较奇葩的数据结构,栈的结构是支持对象的插入和删除操作,但是,栈操作的范围仅限于栈的某一特定端,就是下面这样的。
栈遵循先进后出( last-in-first-out, LIFO )的规律,这是重点。
栈一般使用两种方式来实现:
- 顺序表:采用顺序存储结构可以模拟栈存储数据的特点,从而实现栈存储结构。
- 链表:采用链式存储结构实现栈结构。
注意,这两种实现方式的区别,仅限于数据元素在实际物理空间上存放的相对位置,顺序栈底层采用的是数组,链栈底层采用的是链表。
栈结构我们还是会经常用到,一个非常经典的场景就是在浏览器的后退功能中。
例如我们每次打开一个页面,浏览器都会把这个页面放入栈中,当我们点击后退按钮的时候,在从栈中将这个页面取出来。
顺序栈
顺序栈,是用顺序表实现栈存储结构。
栈存储结构操作数据元素必须遵守 「先进后出 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
本篇内容就这样了,涉及到的代码肯定会上传代码仓库,有兴趣的同学可以去翻翻看。
示例代码
作者:极客挖掘机
定期发表作者的思考:技术、产品、运营、自我提升等。
本文版权归作者极客挖掘机和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果您觉得作者的文章对您有帮助,就来作者个人小站逛逛吧:极客挖掘机