栈和队列是使用频率最高的数据结构。栈和队列是指插入和删除只能在表的一端进行的线性表。
从数据结构的角度来看,栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表操作的子集,它们是操作受限的线性表,因此可称为限定性的数据结构。
栈
先进后出(限定性操作)
栈具有后进先出的特性,如果问题解决具有先进后出的天然特性的话,则求解的算法就要使用栈。比如:进制转换,括号匹配,表达式求值,函数调用,递归调用,八皇后问题,迷宫求解等。
队列(限定性操作)
先进先出
队列具有先进先出的特性,如果问题解决具有先进先出的特性的话,则求解的算法就要使用队列。比如:脱机打印操作,进程按等待时间创建优先级队列,信号按接受的先后顺序依次处理。
栈:限制在表的一端进行插入和删除运算的线性表
栈顶和栈底:允许插入、删除的一端称为栈顶,另一端称为栈底。
空栈:表中没有元素称为空栈
栈的运算规则:后进先出
添加元素时我们称之为入栈或压栈(push);删除元素称之为出栈(pop)
栈与一般线性表的区别:
一般线性表 | 栈 | |
---|---|---|
逻辑结构 | 一对一 | 一对一 |
存储结构 | 顺序表、链表 | 顺序栈、链栈 |
运算规则 | 任意位置插入和删除 | 只能在表尾插入和删除,先进后出 |
栈的存储分为顺序存储和链式存储
栈的本质是线性表,线性表的存储结构对栈也适用,栈的顺序存储结构称为顺序栈。与顺序表类似,在python中使用列表来实现顺序栈,其它的语言使用数组来实现顺序栈。
栈底位置固定不变,栈顶位置随着入栈和出栈操作而变化。用一个整型变量top存储栈顶的位置,通常称top为栈顶指针(实际是小标)。
代码实现:
功能函数编写
初始化一个空列表
def __init__():
self.lst = []
判断空栈
def is_empty(self):
if self.lst = []:
return True
return False
入栈
时间复杂度:O(1)
因为list采用的是动态顺序表技术,所以不需要判断表会不会满
def pushstack(self,item):
self.append(item)
return item
出栈
时间复杂度:O(1)
def popstack(self):
self.pop()
return item
读取栈顶元素
时间复杂度:O(1)
def peek(self):
item = self.lst[-1]
return item
完整代码:
class Stack:
def __init__():
self.lst = []
def is_empty(self):
if self.lst = []:
return True
return False
def pushstack(self,item):
self.append(item)
return item
def popstack(self):
self.pop()
return item
def peek(self):
item = self.lst[-1]
return item
栈的链式存储结构称为链栈,它是运算受限的单链表,插入和删除操作仅限制在表头位置上进行。
在内存中开辟不连续的内存空间,元素之间的关系是通过指针的指引来实现它前继和后继的关系。
时间复杂度为O(1)
图解:
初始化栈
# 定义节点类
class Node:
def __init__(self,item):
self.item = item
self.next = None
# 定义栈类
class LinkTack:
def __init__(self,node=None):
self._top = node
判断栈空
def is_empty(self):
if self._top == None:
return True
return False
入栈
def push(self,item):
node = Node(item)
node.next = self._top
self._top = node
出栈
def pop(self):
if self.is_empty():
return
else:
p = self._top
self._top = p.next
return p.item
完整代码:
# 定义节点类
class Node:
def __init__(self,item):
self.item = item
self.next = None
# 定义栈类
class LinkTack:
def __init__(self,node=None):
self._top = node
def is_empty(self):
if self._top == None:
return True
return False
def push(self,item):
node = Node(item)
node.next = self._top
self._top = node
def pop(self):
if self.is_empty():
return
else:
p = self._top
self._top = p.next
return p.item
进制转换
# 定义栈类
class LinkStack:
def __init__(self,node=None):
self._top = node
def is_empty(self):
if self._top == None:
return True
return False
def push(self,item):
node = Node(item)
node.next = self._top
self._top = node
def pop(self):
if self.is_empty():
return
else:
p = self._top
self._top = p.next
return p.item
if __name__ == "__main__":
N = int(input("输入一个待转换的十进制数:"))
r = int(input("输入要转换的进制:"))
s = LinkStack()
while N != 0:
s.push(N % r)
N = N // r
while N == 0:
item = s.pop()
print(item,end="")
if s.is_empty():
break
print()
函数栈:
栈里面每一层都装函数的栈就是函数栈,调用一个函数的时候,这个函数就入栈,这个函数调用完成了(执行到了函数的最后一个语句或者说return),就出栈。
栈与子程序的调用:
在高级语言和汇编语言编制的程序中,子程序(函数等)的调用和返回处理,在编译系统中都是采用栈来实现的。
当在一个主程序调用一个子程序时,在运行该主程序前,需先将子程序之前的所有中间结果、返回地址等保存在栈中,子程序调用结束后将栈中的数据取出
多个子程序嵌套调用的规则是:后调用-先返回,内存管理实行栈式管理
图解:
代码:
def fuc(n):
if n == 1:
return 1
else:
return fuc(n-1) * n
if __name__ == "__main__":
print(fuc(4))
队列(Queue):运算受限的线性表。它只允许在表的一端进行插入,而在另一端进行删除。
队头(front)和队尾(rear):允许删除的一端称为队头,允许插入的一端称为队尾
队列的运算规则:先进先出(FIFO)
队列的存储结构:
队列的顺序存储结构与顺序表相似,用连续的存储空间存放当前队列中的元素,另设两个指针分别指示队头和队尾元素在最烈中的位置。
通常顺序存储的队列采用循环队列结构
队头指针(front):指向队头元素所在的位置。初始为空时,队头指针为0;出队时,取出队头元素,然后将队头指针加1(并返回被删元素)
队尾指针(rear):指向队尾元素。初始为空时,队头指针为0;入队时,先将新元素插入到队尾,再将队尾指针加1
入队过程:先将入队元素放在rear指针的位置,然后再将rear指针向上移动一位
出队过程:先将队头元素取出,然后将front指针向上移动一位
图解:
创建一个3个长度的对列,当对列的长度占满后,新进入的变量会存放到对列已出去元素的位置上,这就称为循环队列。
顺序队列结构存在的问题:
循环队列:
将队列首尾连接成环形进行使用,让queue[0]接在queue[M-1]之后,若rear+1 == M,则令rear = 0
算法思想
在循环队列中进行出队/入队操作时,头尾指针仍要加1,向上移动。只不过头/尾指针指向队列上界(M-1)时,其加1操作的结果是指向向量的下界0。
用代码描述为:
if i+1 == M:
i=0
else:
i += 1
利用模运算可将其简化为:
i = (i+1) % M
利用求模运算表示入队、出队时指针的变化:
判断队列为空或满
问题:
如上图所示,队列为空或满时的front和rear的指针都指向同一个空间,是无法判断是空还是满的。
解决:
少用一个元素空间,即
图解
代码实现循环队列:
class Queue:
# 初始化
def __init__(self,maxsize):
# 定义一个一定空间的列表
self.queue = [None] * maxsize
# 列表的最大值
self.maxsize = maxsize
# 头指针初始值为0
self.front = 0
# 尾指针初始值为0
self.rear = 0
# 入队
# 时间复杂度为O(1)
def in_queue(self,item):
# 判断队是否为满
if (self.rear+1) % self.maxsize == self.front:
print("队已满,无法插入")
else:
# 队尾指针处插入元素
self.queue[self.rear] = item
# 指针向上移动一位
self.rear = (self.rear +1) % self.maxsize
# 出队
def out_queue(self):
# 判断队是否为空
if self.rear == self.front:
print("队已空,无法取出元素")
else:
# 取队头元素
front_item = self.queue[self.front]
# 指针向后移动一位
self.front = (self.front + 1) % self.maxsize
# 返回队头元素
return front_item
# 求队中元素的个数
def queue_nums(self):
nums = (self.front + self.maxsize - self.rear) % self.maxsize
return nums
# 遍历队列
def serch(self):
# 新设一个队头指针
cur = self.front
# 队头和队尾是否相遇
while cur != self.rear:
print(self.queue[cur])
# 指针后移
cur = (cur + 1) % self.maxsize
链队列:限制仅在表头进行删除和表尾进行插入的单链表。一个链队列由一个头指针和尾指针唯一确定。
定义链队列中的数据节点类
# 定义节点类
class QueueNode:
def __init__(self.item):
self.item = item
self.next = None
定义链队列中的表头节点类
# 定义链表头节点类
class QueueHead:
def __init__(self):
self.front = None
self.rear = None
定义一个链队列类
# 定义一个链队列类
class QueueLink:
def __init__(self):
self._head = QueueHead()
判断队列是否为空
图解:
代码实现:
def is_empty(self):
if self._head.front == self._head_rear and self._head.front == None:
return True
else:
return False
元素入队
在队尾添加元素,修改队尾指针
特殊情况:当队列为空时,不仅要修改队尾指针也要修改队头指针
图解:
代码实现:
def in_queue(self):
# 创建节点
node = QueueNode()
# 判断是否为空
if self.is_empty():
# 如果为空则队头和队尾指针都指向这个节点
self._head.front = node
self._head.rear = node
else:
# 添加元素
self._head.rear.next = node
# 移动指针
self._head.rear = node
出队
将头指针指向头部元素的next即可完成出队
特殊情况:当队内只有一个元素时,需同时修改队头和队尾元素的指针为空
图解:
代码实现:
def out_queue(self):
# 队空
if self.is_empty():
print("队列已空")
# 队中只含一个元素
if self._head.front == self._head.rear and self._head.front != None:
# 取对头元素
p = self._head.front
# 将头指针和尾指针指向None
self._head.front = None
self._head.rear = None
return p.item
else: # 含有多个节点
# 取队头元素
p = self._head.front
# 移动指针
self._head.front = self._head.front.next
return p.item
完整代码:
class QueueNode:
def __init__(self,item):
self.item = item
self.next = None
class QueueHead:
def __init__(self):
self.front = None
self.rear = None
class QueueLink:
def __init__(self):
self._head = QueueHead()
def is_empty(self):
if self._head.front == self._head.rear and self._head.front == None:
return True
else:
return False
def in_queue(self,item):
node = QueueNode(item)
if self.is_empty():
self._head.front = node
self._head.rear = node
else:
self._head.rear.next = node
self._head.rear = node
def out_queue(self):
if self.is_empty():
print("队列已空")
if self._head.front == self._head.rear and self._head.front != None:
p = self._head.front
self._head.front = None
self._head.rear = None
return p.item
else: # 含有多个节点
p = self._head.front
self._head.front = self._head.front.next
return p.item
_head.front = node
self._head.rear = node
else:
self._head.rear.next = node
self._head.rear = node
def out_queue(self):
if self.is_empty():
print("队列已空")
if self._head.front == self._head.rear and self._head.front != None:
p = self._head.front
self._head.front = None
self._head.rear = None
return p.item
else: # 含有多个节点
p = self._head.front
self._head.front = self._head.front.next
return p.item