堆栈相关知识

学习资料来源:

【算法通关手册】:https://algo.itcharge.cn/

【datawhale组队学习】https://github.com/datawhalechina/team-learning

大多是对这次组队学习的学习资料的学习笔记,非原创~

堆栈(stack)基础知识

堆栈(Stack):简称为栈。一种线性表数据结构,是一种只允许在表的一端进行插入和删除操作的线性表。

一些常用的变量及概念解释:

  • 空栈:就是没有数据元素的栈。
  • top:栈顶,指的是栈中允许插入和删除的一端。实际上就是线性表的尾部,最后一个位置,不要误会“顶”这个字为首。
  • bottom:栈底,指相对于栈顶那一端的另一端,也就是线性表的第一个元素。
  • push入栈或者进栈,实际上就是插入元素,但是由于堆栈的特殊结构,只能在栈顶插入,所以形象的叫入栈(如下图)。
  • pop出栈或者退栈删除元素,堆栈只能在栈顶删除元素。
  • LIFO:即"Last In First Out——先进后出原则",简称为LIFO结构,具体定义马上就会讲到。
  • size:栈的大小

可以从【线性表】和【LIFO】两个方面解释栈的定义:

  1. 线性表:栈首先是一个,栈中元素具有前驱后继的线性关系。栈中元素按照 a 1 , a 2 , . . . , a n a_1, a_2,...,a_n a1,a2,...,an的次序依次进栈。栈顶元素为 a n a_n an
  2. LIFO:根据堆栈的定义,每次删除的总是堆栈中当前的栈顶元素,即最后进入堆栈的元素。而在进栈时,最先进入堆栈的元素一定在栈底,最后进入堆栈的元素一定在栈顶。也就是说,元素进入堆栈或者退出退栈是按照「后进先出(Last In First Out)」的原则进行的。
    请添加图片描述

堆栈的顺序存储与链式存储

顺序栈链式栈

  • 【顺序栈】:即堆栈的顺序存储结构。利用一组地址连续的存储单元依次存放自栈底到栈顶的元素,同时使用指针top指示栈顶元素在顺序栈中的位置
  • 【链式栈】:即堆栈的链式存储结构。利用单链表的方式来实现堆栈。栈中元素按照插入顺序依次插入到链表的第一个节点之前,并使用栈顶指针 top指示栈顶元素,top永远指向链表的头节点位置。

堆栈的基本操作

和常规操作有显著区别的主要是入栈(插入)push和出栈(删除)pop

  • 初始化空栈:创建一个空栈,定义栈的大小size,以及栈顶元素指针top
  • push进栈、入栈、插入元素:相当于在线性表最后元素后面插入一个新的数据元素。并改变栈顶指针top的指向位置。需要判断栈是否已满。
  • pop出栈、退栈、删除元素:相当于在线性表最后元素后面删除最后一个数据元素。并改变栈顶指针top的指向位置。需要判断栈是否为空。
  • 获取栈顶元素:相当于获取线性表中最后一个数据元素。与插入元素、删除元素不同的是,该操作并不改变栈顶指针top的指向位置。既需要判断栈是否为空,也需要判断栈是否已满(为什么?)。

堆栈的顺序存储的实现

顺序栈——可以借助python中的list来实现
请添加图片描述

具体操作:

  • 初始化空栈:使用列表创建一个空栈,定义栈的大小 self.size,并令栈顶元素指针 self.top 指向 -1,即 self.top = -1
  • 判断栈是否为空:当 self.top == -1 时,说明堆栈为空,返回 True,否则返回 False
  • 判断栈是否已满:当 self.top == self.size - 1,说明堆栈已满,返回 True,否则返回返回 False
  • 获取栈顶元素:先判断队列是否为空,为空直接抛出异常。不为空则返回 self.top 指向的栈顶元素,即 self.stack[self.top]
  • 插入元素(进栈、入栈):先判断队列是否已满,已满直接抛出异常。如果队列未满,则在 self.stack 末尾插入新的数据元素,并令 self.top 向右移动 1位。
  • 删除元素(出栈、退栈):先判断队列是否为空,为空直接抛出异常。如果队列不为空,则令 self.top 向左移动 1 位,并返回 self.stack[self.top]

堆栈的顺序存储实现代码:

class Stack:
    # 初始化空栈
    def __init__(self, size = 100):
        self.stack = []
        self.size = size
        self.top = -1 # 栈顶的索引?
        
    # 判断栈是否为空
    def is_empty(self):
        return self.top == -1
    
    # 判断栈是否已满
    def is_full(self):
        return self.top + 1 == self.size
    
    # 入栈操作
    def push(self, value):
        if self.is_full():
            raise Exception('Stack is full') # raise语句用来抛出异常
        else:
            cur = self.top
            self.top = self.top.next # 这里的.next是如何定义的?
            del cur
            
    # 获取栈顶元素
    def peek(self):
        if self.is_empty():
            raise Exception('Stack is empty')
        else:
            return self.top.value # 这里的value是如何定义的?

堆栈的应用

堆栈是算法和程序中最常用的辅助结构,其的应用十分广泛。堆栈基本应用于两个方面:

  1. 使用堆栈可以很方便的保存和取用信息,因此长被用作算法和程序中的辅助存储结构,临时保存信息,供后面操作中使用。
    • 例如:操作系统中的函数调用栈,浏览器中的前进、后退功能。
  2. 堆栈的后进先出规则,可以保证特定的存取顺序。
    • 例如:翻转一组元素的顺序、铁路列车车辆调度

实际题目

20.有效的括号-力扣(LeetCode)

227. 基本计算器 II - 力扣(LeetCode)

更多堆栈题目列表见《算法通关手册》

堆栈与深度优先搜索知识

深度优先搜索简介

深度优先搜索算法(Depth First Search):英文缩写为 DFS。是一种用于遍历搜索树的算法。该算法沿着树的深度遍历树的节点,会尽可能深的搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止

深度优先搜索使用的是回溯思想,这种思想很适合使用 「递归」 来实现。而递归对问题的处理顺序,遵循了 「后进先出」 的规律。所以递归问题的处理,需要借助「堆栈」来实现。

在深度优先遍历的过程中,我们需要将当前遍历节点 v相邻节点暂时存储起来,以便于在回退的时候可以继续访问它们。 遍历到的节点顺序符合 「后进先出」 的特点,所以深度优先搜索可以通过「递归」或者 「堆栈」 来实现。

深度优先搜索过程演示

无向图为例

用邻接字典的方式存储无向图结构,对应结构代码如下:

# 定义无向图结构
graph = {
    "A":["B", "C"],
    "B":["A", "C", "D"],
    "C":["A", "B", "D", "E"],
    "D":["B", "C", "E", "F"],
    "E":["C", "D"],
    "F":["D"]
}

该无向图的结构如图左所示,图右为深度优先搜索的遍历路径。
请添加图片描述

基于递归实现的深度优先搜索

实现步骤:

  • graph 为存储无向图的字典变量visited 为标记访问节点的 set 集合变量。start 为当前遍历边的开始节点。def dfs_recursive(graph, start, visited):为递归实现的深度优先搜索方法。
  • start 标记为已访问,即将 start 节点放入 visited 中(visited.add(start))。
  • 访问节点 start,并对节点进行相关操作(看具体题目要求)。
  • 遍历与节点 start 相连并构成边的节点 end
    • 如果 end 没有被访问过,则从 end 节点调用递归实现的深度优先搜索方法,即 dfs_recursive(graph, end, visited)

实现代码:

def dfs_recursive(graph, start, visited):
    # 标记节点
    visited.add(start)
    # 访问节点
    print(start)
    
    for end in gragh[start]:
        if end not in visited:
            # 深度优先遍历节点
            dfs_recursive(graph, end, visited)

基于堆栈实现的深度优先搜索

实现步骤:

  1. start为开始节点。定义visited为标记访问节点的set集合变量。定义stack用于存放临时节点的栈结构。
  2. 首先将起始节点放入栈中,并标记访问。即visited = set(start)stack = [start]
  3. stack中取出第一个节点node_u
  4. 访问节点node_u,并对节点进行相关操作(看具体题目要求)。
  5. 遍历与节点node_u相连并构成边的节点node_v
    • 如果node_v没有被访问过,则将node_v节点放入栈中,并标记访问,即stack.append(node_v)visited.add(node_v)
  6. 重复步骤3 ∼ \sim 5,直到stack为空。

实现代码:

def dfs_stack(graph, start):
	print(start) # 访问节点 start
    visited = set(start) # 使用visited标记访问过的节点
    stack = [start] # 创建一个栈,并将 start 加入栈中
    
    while stack:
        node_u = stack[-1] # 获取栈顶元素

		i = 0
		while i < len(graph[node_u]): # 遍历栈顶元素,遇到未访问节点,访问节点并跳出。
			node_v = graph[node_u][i]

			if node_v not in visited: # node_v 未访问过
				print(node_v) # 访问节点node_v
				stack.append(node_v) # 将 node_v 加入栈中
				visited.add(node_v) # 标记为访问过 node_v 
				break
			i += 1
		
		if i == len(graph[node_u]): # node_u 相邻的节点都访问结束了,弹出node_u
			stack.pop()

深度优先搜索应用

单调栈

单调栈(Monotone Stack): 一种特殊的栈。在栈的「先进后出」规则基础上,要求「从 栈顶栈底 的元素是单调递增(或者单调递减)」。其中满足从栈顶到栈底的元素是单调递增的栈,叫做 「单调递增栈」。满足从栈顶到栈底的元素是单调递减的栈,叫做「单调递减栈」。

单调递增栈

实际上就是把大的放在栈底

单调递增栈: 只有比栈顶元素小的元素才能直接进栈,否则需要先将栈中比当前元素小的元素出栈,再将当前元素入栈。

出入栈过程:

  • 假设当前进栈元素为x,如果栈顶元素大于x,则直接入栈。
  • 否则从栈顶开始遍历栈中元素,把小于 x 或者等于 x 的元素弹出栈,直到遇到一个大于 x 的元素为止,然后再把 x 压入栈中。

图示过程:
请添加图片描述

单调栈适用场景

单调栈可以在时间复杂度为 O ( n ) O(n) O(n)的情况下,求解出某个元素左边或者右边第一个比它大或者小的元素。

常用场景:

  • 寻找左侧第一个比当前元素大的元素
  • 寻找左侧第一个比当前元素小的元素
  • 寻找右侧第一个比当前元素大的元素
  • 寻找右侧第一个比当前元素小的元素

求解方法:

  1. 寻找左侧第一个比当前元素大的元素

    • 从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素左侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。(因为插入的时候要把比他小的都拿出来)如果插入时的栈为空,则说明左侧不存在比当前元素大的元素。
  2. 寻找右侧第一个比当前元素小的元素

    • 从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减):一个元素右侧第一个比它小的元素就是将其【弹出单调递减栈】时即将插入的元素。如果该元素没有被弹出栈,则说明右侧不存在比当前元素小的元素。
    • 从右到左遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素左侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。如果插入时的栈为空,则说明左侧不存在比当前元素大的元素。
  3. 更多见《算法通关手册》

规则简记:

  • 无论哪种题型,都建议从左到右遍历元素。

  • 查找 「比当前元素大的元素」 就用 单调递增栈,查找「比当前元素小的元素」就用 单调递减栈。

  • 从 「左侧」 查找就看 「插入栈」 时的栈顶元素,从 「右侧」 查找就看 「弹出栈」 时即将插入的元素。

单调栈模板:

以从左到右遍历元素为例,介绍一下构造单调递增栈和单调递减栈的模板。

单调递增栈的模板

def monotoneIncreasingStack(nums):
    stack = []
    for num in nums:
        while stack and num >= stack[-1]:
            stack.pop()
        stack.append(num)

单调递减栈的模板

def monotoneDecreasingStack(nums):
    stack = []
    for num in nums:
        while stack and num <= stack[-1]:
            stack.pop()
        stack.append(num)

单调栈的应用

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值