00
前言前面介绍过了链表,以及如何使用 Python 实现链表,随后也分享了几道链表相关的例题,相信现在对链表的理解已经比刚开始接触时要更加深刻了。
链表我们知道是一种链式存储结构,而本文接下来即将介绍两种特殊的线性结构——栈与队列。
01
线性结构在正式开始介绍栈与队列之前,我们先来初步了解下线性结构。线性结构百度词条解释如下。
02
栈栈的百度词条解释如下。
如果有其他语言基础,了解过数组,那应该知道我们使用数组的时候可以通过下标访问数组的任意一个元素。但是栈就不一样了,我们只能访问到栈的栈顶元素,所有的操作的都是对栈顶进行的,这其中就包括了对栈顶的增删和查看。
我们无法访问栈顶以外的元素,不过我们可以把当前栈顶弹出 (pop),这样下一个元素就变成了栈顶,我们就可以对其进行操作。如果栈的空间足够,我们还可以往里面填入内容,称为压栈 (push)。
压栈 (push)
弹栈 (pop)
从上面两个示意图可以看出,先入栈的的元素反而是后出栈的,这也是栈的一个特点:先进后出 (FILO, First In Last Out)。
至于栈的代码实现,我们使用 Python 中的列表来模拟栈,另外对栈设置一个最大容量,超过最大容量不允许压栈。
我们考虑下栈应该都有哪些基本的功能。
push(x):将元素 x 压入栈内。
pop():将栈顶元素弹出并返回。
peek():查看栈顶元素,但不弹出。
isEmpty():判断栈是否为空。
isFull():判断栈是否已满。
length():返回栈中元素个数。
maxSize():获取栈的最大容量。
代码实现如下,这里将列表的最末尾一个元素作为栈顶,代码实现上没有难度,不做过多解释。
class Stack(object):
def __init__(self, size):
self.items = []
self.size = size # 最大容量
def push(self, x):
"""将元素 x 压入栈内"""
if self.isFull():
raise Exception("栈已满")
else:
self.items.append(x)
def pop(self):
"""将栈顶元素弹出并返回"""
if self.isEmpty():
raise Exception("栈为空")
else:
return self.items.pop()
def peek(self):
"""返回栈顶元素,但不弹出"""
if self.isEmpty():
raise Exception("栈为空")
else:
return self.items[-1]
def isEmpty(self):
"""判断栈是否为空"""
return self.items == []
def isFull(self):
"""判断栈是否已满"""
return self.length() == self.maxSize()
def length(self):
"""返回栈中元素个数"""
return len(self.items)
def maxSize(self):
"""获取栈的最大容量"""
return self.size
03
队列队列的百度词条解释如下。
队列可以理解成一个个的元素在排队,而排队一般遵循的都是先到的排在最前面,而排在最前面的也是最先结束任务离开队伍的。对比栈的先进后出,队列则是先进先出 (FIFO,First In First Out)。
在队列中,新入队的元素只能加到队列末尾,而出队的元素只能是队首元素。这和我们上面提到的排队例子完全一致。
入队 (enqueue)
出队 (dequeue)
关于出队操作的示意图描述,图中每出队一个元素,剩余元素整体前移,如果使用 Python 列表来实现队列,可以理解成是删除第 0 号元素后,剩余元素的索引值将整体减一,队首始终指向当前的第 0 号元素。如果使用链表实现队列,每出队一个元素,就是删除第 0 号结点,head 指向原来的第 1 号结点,此时原来的第 1 号结点也就成了当前的第 0 号结点。
我们同样也规定下队列的最大容量,超出队列最大容量时不允许继续入队 (enqueue)。队列的实现仍旧使用 Python 中的列表来完成。
我们先考虑下队列应当有哪些基本的功能。
enqueue(x):将元素 x 入队。
dequeue():将队首元素出队并返回。
front():获取队首元素,但不删除。
isEmpty():判断队列是否为空。
isFull():判断队列是否已满。
length():返回队列中元素个数。
maxSize():获取队列最大容量。
代码实现如下,其中列表第 0 号元素作为队首,列表末尾作为队尾。
class Queue(object):
def __init__(self, size):
self.items = []
self.size = size # 最大容量
def enqueue(self, x):
"""将元素 x 入队"""
if self.isFull():
raise Exception("队列已满")
else:
self.items.append(x) # 以列表末尾作为队尾
def dequeue(self):
"""将队首元素出队并返回"""
if self.isEmpty():
raise Exception("队列为空")
else:
return self.items.pop(0) # 以 0 号位置作为队首
def front(self):
"""返回队首元素,但不删除"""
if self.isEmpty():
raise Exception("队列为空")
else:
return self.items[0]
def isEmpty(self):
"""判断队列是否为空"""
return self.items == []
def isFull(self):
"""判断队列是否已满"""
return self.length() == self.maxSize()
def length(self):
"""返回队列中元素个数"""
return len(self.items)
def maxSize(self):
"""获取队列的最大容量"""
return self.size
代码实现几乎与栈相同,只有极少数内容做了修改,了解队列的入队和出队是怎样进行的即可,感兴趣的读者还可以自行尝试使用链表分别实现栈和队列,实现也很简单,将其中的增删步骤替换为链表结点的增删步骤即可。
在最后Last but not least
本文介绍了两种特殊的线性结构——栈和队列。
栈的特点是先进后出 (FILO),且只能对栈顶元素进行操作;队列的特点是先进先出 (FIFO),只能在队尾增加元素,在队首查看或移除元素。
文中使用 Python 列表实现栈和队列,如果换用链表实现,方法也很类似,将列表的增删步骤替换为链表的增删即可,最重要的还是掌握这两种数据结构的特点。
往期 精彩回顾算法学习——贪心算法例题&动态规划
Python数据结构——单链表
Python数据结构——单链表例题