【数据结构篇】第三章:解锁受限线性结构:栈与队列的深度探索与应用

大家好,我是小黄。

【数据结构篇】第三章:解锁受限线性结构:栈与队列的深度探索与应用_第1张图片

栈(Stack)

栈的定义与概念

栈是一种特殊的线性表,它遵循后进先出(Last In First Out,LIFO)的原则。想象一下餐厅里一摞叠放整齐的餐盘,我们总是从最上面取用餐盘,而新洗净的餐盘也是放在这摞餐盘的最顶端。在这个过程中,最后放入的餐盘会最先被取走,这就是栈的工作模式。

从数据结构的角度来看,栈有一个限定的入口和出口。元素只能从栈顶一端进行插入(通常称为入栈操作,push)和删除(通常称为出栈操作,pop)。栈底是固定的,不允许直接对栈底元素进行操作。这种特殊的结构使得栈在处理某些特定问题时具有极高的效率。

栈的操作

  1. 初始化(Initialization):在使用栈之前,我们需要对其进行初始化,为栈分配内存空间,并设置栈顶指针(通常用于指示栈顶元素的位置)。栈顶指针初始时一般指向一个特定的位置,比如栈的底部下方,以表示栈为空。

class Stack:

def __init__(self):

self.items = []

在这段 Python 代码中,我们通过创建一个空列表items来初始化一个栈。

  1. 入栈(Push):将一个新元素添加到栈顶。在执行入栈操作时,首先检查栈是否已满(如果栈有容量限制)。如果栈未满,将新元素放置在栈顶指针所指向的位置,然后将栈顶指针向上移动一个位置。

def push(self, item):

self.items.append(item)

这里使用 Python 列表的append方法,将元素添加到列表末尾,相当于将元素入栈。

  1. 出栈(Pop):移除并返回栈顶元素。执行出栈操作前,需检查栈是否为空。若栈不为空,将栈顶指针向下移动一个位置,然后返回原来栈顶指针所指向的元素。

def pop(self):

if not self.is_empty():

return self.items.pop()

使用列表的pop方法,默认弹出并返回列表的最后一个元素,即栈顶元素。

  1. 获取栈顶元素(Top or Peek):返回栈顶元素,但不将其从栈中移除。同样需要先检查栈是否为空。

def peek(self):

if not self.is_empty():

return self.items[-1]

通过索引获取列表的最后一个元素,即栈顶元素。

  1. 判断栈是否为空(Is Empty):通过检查栈顶指针的位置或者栈中元素的数量来判断栈是否为空。

def is_empty(self):

return len(self.items) == 0

如果列表长度为 0,则表示栈为空。

栈的应用场景

  1. 表达式求值:在编译器和计算器中,栈常用于表达式求值。例如,对于中缀表达式(3 + 4) * 2 - 1,可以通过将操作数和运算符分别入栈,按照特定规则进行出栈和计算,从而得到表达式的结果。在将中缀表达式转换为后缀表达式(也称为逆波兰表达式)的过程中,栈也发挥着关键作用。后缀表达式更易于计算机进行求值,通过栈可以方便地将中缀表达式转换为后缀表达式。
  1. 函数调用栈:在程序执行过程中,当一个函数被调用时,系统会将该函数的相关信息(如参数、返回地址等)压入栈中。当函数执行完毕返回时,这些信息会从栈中弹出。这使得函数调用能够正确地嵌套和返回,保证了程序执行的顺序和逻辑正确性。例如,在一个递归函数中,每次递归调用都会将当前函数的状态压入栈中,当递归结束时,再依次从栈中弹出状态,恢复到上一层调用的环境。
  1. 深度优先搜索(DFS):在图的遍历算法中,深度优先搜索利用栈来实现。从起始节点开始,将其入栈,然后不断取出栈顶节点并访问其未访问过的邻接节点,将这些邻接节点入栈,直到栈为空。这种方式使得搜索沿着一条路径尽可能深地进行,直到无法继续,然后回溯到之前的节点,继续探索其他路径。

队列(Queue)

队列的定义与概念

队列同样是一种线性表,但它遵循先进先出(First In First Out,FIFO)的原则。可以把队列想象成日常生活中的排队场景,比如在银行办理业务,先到的客户先接受服务,后到的客户在队列末尾排队等待。在队列中,新元素总是添加到队尾,而从队头移除元素。

队列有两个主要的端点,分别是队头(front)和队尾(rear)。元素从队尾进入队列,从队头离开队列。与栈不同,队列的操作在两端进行,一端用于入队(enqueue)操作,另一端用于出队(dequeue)操作。

队列的操作

  1. 初始化(Initialization):和栈类似,在使用队列之前需要进行初始化。为队列分配内存空间,并设置队头指针和队尾指针,初始时它们通常都指向队列的起始位置,表示队列为空。

class Queue:

def __init__(self):

self.items = []

这里同样使用 Python 列表来初始化一个队列。

  1. 入队(Enqueue):将一个新元素添加到队尾。在执行入队操作时,首先检查队列是否已满(如果队列有容量限制)。如果队列未满,将新元素添加到队尾指针所指向的位置,然后将队尾指针向后移动一个位置。

def enqueue(self, item):

self.items.append(item)

利用列表的append方法,将元素添加到列表末尾,实现入队操作。

  1. 出队(Dequeue):移除并返回队头元素。执行出队操作前,需检查队列是否为空。若队列不为空,返回队头指针所指向的元素,然后将队头指针向后移动一个位置。在 Python 中,实现出队操作时,由于列表删除第一个元素的时间复杂度较高(为 O (n)),为了保持较好的性能,可以使用collections.deque模块。

from collections import deque

class Queue:

def __init__(self):

self.items = deque()

def dequeue(self):

if not self.is_empty():

return self.items.popleft()

这里使用deque的popleft方法,以 O (1) 的时间复杂度移除并返回队列的第一个元素,即队头元素。

  1. 获取队头元素(Front or Peek):返回队头元素,但不将其从队列中移除。同样需要先检查队列是否为空。

def peek(self):

if not self.is_empty():

return self.items[0]

通过索引获取列表的第一个元素,即队头元素。

  1. 判断队列是否为空(Is Empty):通过检查队头指针和队尾指针的位置关系或者队列中元素的数量来判断队列是否为空。

def is_empty(self):

return len(self.items) == 0

如果列表长度为 0,则表示队列为空。

队列的应用场景

  1. 广度优先搜索(BFS):在图的遍历算法中,广度优先搜索借助队列来实现。从起始节点开始,将其入队,然后不断取出队头节点并访问其未访问过的邻接节点,将这些邻接节点入队,直到队列为空。这种方式使得搜索以层为单位,一层一层地向外扩展,能够找到从起始节点到目标节点的最短路径(在无权图中)。
  1. 任务调度:在操作系统中,队列常用于任务调度。当多个任务同时请求执行时,这些任务会被放入一个任务队列中。操作系统按照任务进入队列的先后顺序,依次从队列中取出任务并分配资源进行处理。例如,在一个多线程的应用程序中,线程池中的任务队列管理着等待执行的任务,线程从队列中获取任务并执行,保证了任务执行的公平性和有序性。
  1. 打印机作业管理:在打印服务器中,打印作业会被放入一个队列中。打印机按照作业进入队列的顺序依次进行打印,确保每个作业都能得到及时处理,避免了打印顺序混乱的问题。

栈和队列的比较

  1. 数据存储与访问顺序:栈遵循后进先出(LIFO)原则,就像往一个桶里放东西,最后放进去的东西会最先拿出来。而队列遵循先进先出(FIFO)原则,如同排队一样,先到的先被服务。
  1. 操作特点:栈的操作主要集中在栈顶一端,入栈和出栈操作都在栈顶进行。队列的操作则分别在队头和队尾进行,入队在队尾,出队在队头。这使得它们在不同的应用场景中表现出各自的优势。
  1. 应用场景差异:由于其特性,栈常用于需要处理 “回溯”“嵌套” 等逻辑的场景,如表达式求值、函数调用栈、深度优先搜索等。而队列则更适合处理需要按照顺序依次处理元素的场景,如广度优先搜索、任务调度、打印机作业管理等。

总结

栈和队列作为数据结构中基础而重要的组成部分,以其独特的存储和访问方式,为解决各种复杂的编程问题提供了强大的工具。无论是在编译器、操作系统等系统软件中,还是在各种应用程序的算法设计中,它们都扮演着关键角色。

 

你可能感兴趣的:(数据结构)