作者:mobiledu2502931637 | 来源:互联网 | 2023-10-12 19:12
文章目录栈、队列和双端队列栈队列双端队列Java中的栈、队列和双端队列单调栈和单调队列二叉堆和优先队列二叉堆优先队列目录栈、队列和双端队列栈和队列是常见的数据结构。栈的特点是后进
文章目录
- 栈、队列和双端队列
- 单调栈和单调队列
- 二叉堆和优先队列
- 目录
栈、队列和双端队列
栈和队列是常见的数据结构。栈的特点是后进先出,添加元素、删除元素和查看元素都在栈顶操作。队列的特点是先进先出,添加元素在队尾操作,删除元素和查看元素在队首操作。
双端队列比栈和队列更加灵活,可以在双端队列的两端添加元素、删除元素和查看元素。
栈、队列和双端队列都满足每次添加元素、删除元素和查看元素的时间复杂度是 O(1)O(1)O(1)。
栈、队列和双端队列都可以基于数组或链表实现。
栈
栈是一种特殊的线性表,特点是后进先出,最先加入的元素最后取出,最后加入的元素最先取出。
栈的常见操作包括判断栈是否为空和获得栈内元素个数,以及对元素的操作。对元素的操作都位于栈顶,包括以下三种操作:
- 添加元素:将元素加入栈顶,该操作称为入栈;
- 删除元素:将栈顶元素删除,该操作称为出栈;
- 查看元素:获得栈顶元素,不删除元素。
下图为栈的示意,左边为栈底,右边为栈顶。将数字 111 到 555 依次入栈之后,栈的状态如下图所示,此时栈顶元素是 555,第一个出栈的元素是 555。
队列
队列是一种特殊的线性表,特点是先进先出,最先加入的元素最先取出,最后加入的元素最后取出。队列有头部和尾部,队列头部称为队首,队列尾部称为队尾,队列内的元素从队首到队尾的顺序符合加入队列的顺序。
队列的常见操作包括判断队列是否为空和获得队列内元素个数,以及对元素的操作。对元素的操作包括以下三种操作:
- 添加元素:将元素加入队尾,该操作称为入队;
- 删除元素:将队首元素删除,该操作称为出队;
- 查看元素:获得队首元素,不删除元素。
需要注意的是,队列的三种对元素的操作所在的位置不同,查看元素和删除元素位于队首,添加元素位于队尾。
下图为队列的示意,左边为队首,右边为队尾。将数字 111 到 555 依次入队之后,队列的状态如下图所示,此时队首元素是 111,队尾元素是 555,第一个出队的元素是 111。
双端队列
双端队列是一种特殊的线性表,同时具有栈和队列的性质,特点是在队首和队尾都可以加入和取出元素。栈和队列都可以基于双端队列实现。
Java 中的栈、队列和双端队列
Java 提供了多种类和接口支持栈、队列和双端队列的实现。
Stack\texttt{Stack}Stack 类是早期版本的栈的实现类,继承自 Vector\texttt{Vector}Vector 类。在后续版本中,JDK 的官方文档不建议使用 Stack\texttt{Stack}Stack 类实现栈的功能,而是建议使用 Deque\texttt{Deque}Deque 接口及其实现类实现栈的功能。
Queue\texttt{Queue}Queue 接口是队列的接口,需要通过实现类完成实例化,常见的实现类包括 ArrayDeque\texttt{ArrayDeque}ArrayDeque 类和 LinkedList\texttt{LinkedList}LinkedList 类。
Deque\texttt{Deque}Deque 接口是双端队列的接口,需要通过实现类完成实例化,常见的实现类包括 ArrayDeque\texttt{ArrayDeque}ArrayDeque 类和 LinkedList\texttt{LinkedList}LinkedList 类。
ArrayDeque\texttt{ArrayDeque}ArrayDeque 类和 LinkedList\texttt{LinkedList}LinkedList 类都可以作为栈和队列的实现类,区别在于,ArrayDeque\texttt{ArrayDeque}ArrayDeque 类的底层实现是循环数组,LinkedList\texttt{LinkedList}LinkedList 类的底层实现是双向链表。根据 JDK 的官方文档,ArrayDeque\texttt{ArrayDeque}ArrayDeque 类作为栈使用时效率高于 Stack\texttt{Stack}Stack 类,ArrayDeque\texttt{ArrayDeque}ArrayDeque 类作为队列使用时效率高于 LinkedList\texttt{LinkedList}LinkedList 类。无论是栈的实现还是队列的实现,都推荐使用 ArrayDeque\texttt{ArrayDeque}ArrayDeque 类。
单调栈和单调队列
单调栈和单调队列是栈和队列的高级应用,可以解决一些复杂的问题。
单调栈和单调队列满足元素的单调性。具体而言,单调栈内从栈底到栈顶的元素依次递增或递减,单调队列内从队首到队尾的元素依次递增或递减。在单调栈和单调队列中添加元素时,必须维护元素的单调性。
向单调栈添加元素时,首先需要检查栈顶元素和待添加元素是否满足单调性,如果不满足单调性则将栈顶元素出栈,直到栈为空或者栈顶元素和待添加元素满足单调性,然后将待添加元素入栈。
向单调队列添加元素时,首先需要检查队尾元素和待添加元素是否满足单调性,如果不满足单调性则将队尾元素出队,直到队列为空或者队尾元素和待添加元素满足单调性,然后将待添加元素入队。
由于普通的队列不支持在队尾将元素出队,因此需要使用双端队列实现单调队列的功能。
单调栈和单调队列的应用需要考虑具体情况。有时需要维护下标信息,因此在单调栈和单调队列中存储的不是元素本身,而是元素下标,下标对应的元素满足单调性。
使用单调栈和单调队列实现通常需要两层循环,但是由于每个元素最多只会在单调栈或单调队列中被添加一次和删除一次,因此时间复杂度是线性复杂度,而不是平方复杂度。
二叉堆和优先队列
优先队列是一种不同于栈、队列和双端队列的数据结构。栈、队列和双端队列的实现原理是线性表,而优先队列的实现原理是二叉堆。
二叉堆
在介绍二叉堆之前,首先需要介绍二叉树和完全二叉树。
二叉树是一个树的结构,每个结点最多有两个子结点,称为左子结点和右子结点。
完全二叉树的性质是&#xff0c;除了层数最大的层以外&#xff0c;其余各层的结点数都达到最大值&#xff0c;且层数最大的层的所有结点都连续集中在最左边。假设完全二叉树有 lll 层&#xff0c;其中根结点位于第 000 层&#xff0c;则对于 0≤i0≤i<l−1&#xff0c;第 iii 层有 2i2^i2i 个结点。
二叉堆是一棵完全二叉树&#xff0c;其中的元素按照特定规则排列。常见的例子有小根堆和大根堆。
- 小根堆的性质是&#xff0c;每个结点的值都小于其子结点的值&#xff0c;根结点的值是堆中最小的&#xff1b;
- 大根堆的性质是&#xff0c;每个结点的值都大于其子结点的值&#xff0c;根结点的值是堆中最大的。
在二叉堆中添加元素和删除元素时&#xff0c;必须维护二叉堆的性质。以小根堆为例&#xff0c;添加元素和删除元素的操作如下&#xff1a;
- 添加元素时&#xff0c;假设添加的元素是 xxx&#xff0c;首先将 xxx 添加到末尾&#xff0c;然后比较 xxx 和父结点元素的大小&#xff0c;如果 xxx 小于父结点元素&#xff0c;则将 xxx 和父结点交换&#xff0c;直到 xxx 到达根结点或者大于等于父结点元素&#xff1b;
- 删除元素时&#xff0c;假设堆中的最后一个元素是 xxx&#xff0c;首先将 xxx 放置到根结点处&#xff0c;然后比较 xxx 和子结点元素的大小&#xff0c;如果 xxx 大于至少一个子结点元素&#xff0c;则将 xxx 和较小的子结点交换&#xff0c;直到 xxx 到达叶结点&#xff08;没有子结点&#xff09;或者小于等于全部子结点元素。
考虑以下小根堆。
将 888 添加到小根堆中&#xff0c;首先将 888 添加到末尾。
由于 888 比父结点 151515 小&#xff0c;因此交换&#xff0c;交换后 888 比父结点 101010 小&#xff0c;因此再次交换&#xff0c;此时 888 不再比父结点 555 小&#xff0c;因此停止交换。添加元素操作结束。
在添加 888 之后&#xff0c;删除元素。删除堆顶元素 555&#xff0c;将最后一个元素 151515 放置到根结点处。
由于 151515 比两个子结点元素都大&#xff0c;因此和较小的子结点 888 交换&#xff0c;交换后 151515 比较小的子结点 101010 大&#xff0c;因此和较小的子结点 101010 交换&#xff0c;此时 151515 不再比子结点 303030 大&#xff0c;因此停止交换。删除元素操作结束。
假设二叉堆中的元素个数是 nnn&#xff0c;二叉堆的层数是 lll。根据上述例子可知&#xff0c;在二叉堆中添加元素和删除元素时&#xff0c;需要维护二叉堆的性质&#xff0c;时间复杂度是 O(l)O(l)O(l)&#xff0c;由于 O(l)&#61;O(logn)O(l) &#61; O(\log n)O(l)&#61;O(logn)&#xff0c;因此二叉堆的添加元素和删除元素的时间复杂度是 O(logn)O(\log n)O(logn)。
优先队列
不同于栈的后进先出和队列的先进先出&#xff0c;优先队列中的每个元素都有优先级&#xff0c;优先级最高的元素位于队首&#xff0c;也是最先取出的元素。
Java 的 PriorityQueue\texttt{PriorityQueue}PriorityQueue 类是优先队列类&#xff0c;继承自 AbstractQueue\texttt{AbstractQueue}AbstractQueue 抽象类。PriorityQueue\texttt{PriorityQueue}PriorityQueue 类的实现原理是二叉堆&#xff0c;底层实现是数组&#xff0c;二叉堆满足堆顶元素为优先级最高的元素。创建优先队列的时候可以自定义优先队列中的元素的比较方法&#xff0c;从而自定义优先级。
由于 Java 的优先队列的实现原理是二叉堆&#xff0c;因此优先队列的添加元素和删除元素的时间复杂度是 O(logn)O(\log n)O(logn)&#xff0c;查看元素的时间复杂度是 O(1)O(1)O(1)&#xff0c;其中 nnn 是优先队列中的元素个数。
目录
- 栈题目&#xff1a;文件夹操作日志搜集器
- 栈题目&#xff1a;括号的最大嵌套深度
- 栈题目&#xff1a;有效的括号
- 栈题目&#xff1a;棒球比赛
- 栈题目&#xff1a;删除最外层的括号
- 栈题目&#xff1a;删除字符串中的所有相邻重复项
- 栈题目&#xff1a;用栈操作构建数组
- 栈题目&#xff1a;两数相加 II
- 栈题目&#xff1a;逆波兰表达式求值
- 栈题目&#xff1a;使括号有效的最少添加
- 栈题目&#xff1a;平衡括号字符串的最少插入次数
- 栈题目&#xff1a;最小栈
- 栈题目&#xff1a;行星碰撞
- 栈题目&#xff1a;验证栈序列
- 栈题目&#xff1a;检查替换后的词是否有效
- 栈题目&#xff1a;反转每对括号间的子串
- 栈题目&#xff1a;移除无效的括号
- 栈题目&#xff1a;简化路径
- 栈题目&#xff1a;括号的分数
- 栈题目&#xff1a;函数的独占时间
- 栈题目&#xff1a;字符串解码
- 栈题目&#xff1a;解析布尔表达式
- 栈题目&#xff1a;有效括号的嵌套深度
- 栈题目&#xff1a;删除字符串中的所有相邻重复项 II
- 栈题目&#xff1a;迷你语法分析器
- 栈题目&#xff1a;设计浏览器历史记录
- 栈题目&#xff1a;基本计算器
- 栈题目&#xff1a;基本计算器 II
- 栈题目&#xff1a;文件的最长绝对路径
- 栈题目&#xff1a;标签验证器
- 队列题目&#xff1a;无法吃午餐的学生数量
- 队列题目&#xff1a;最近的请求次数
- 队列题目&#xff1a;用队列实现栈
- 队列题目&#xff1a;用栈实现队列
- 队列题目&#xff1a;按递增顺序显示卡牌
- 队列题目&#xff1a;设计循环队列
- 队列题目&#xff1a;设计循环双端队列
- 单调栈题目&#xff1a;去除重复字母
- 单调栈题目&#xff1a;移掉 K 位数字
- 单调栈题目&#xff1a;找出最具竞争力的子序列
- 单调栈题目&#xff1a;132 模式
- 单调栈题目&#xff1a;每日温度
- 单调栈题目&#xff1a;接雨水
- 单调栈题目&#xff1a;股票价格跨度
- 单调栈题目&#xff1a;最大宽度坡
- 单调栈题目&#xff1a;子数组的最小值之和
- 单调栈题目&#xff1a;柱状图中最大的矩形
- 单调栈题目&#xff1a;最大矩形
- 单调队列题目&#xff1a;绝对差不超过限制的最长连续子数组
- 单调队列题目&#xff1a;满足不等式的最大值
- 单调队列题目&#xff1a;和至少为 K 的最短子数组
- 优先队列题目&#xff1a;最后一块石头的重量
- 优先队列题目&#xff1a;数据流中的第 K 大元素
- 优先队列题目&#xff1a;拼车
- 优先队列题目&#xff1a;合并K个升序链表
- 优先队列题目&#xff1a;滑动窗口最大值
- 优先队列题目&#xff1a;数据流的中位数
- 优先队列题目&#xff1a;多次求和构造目标数组
- 优先队列题目&#xff1a;数组的最小偏移量