栈与队列
基础知识
栈
灵魂四问:
- C++中 stack,queue 是容器么?
- 我们使用的 stack,queue 是属于那个版本的 STL?
- 我们使用的 STL 中 stack,queue 是如何实现的?
- stack,queue 提供迭代器来遍历空间么?
栈和队列是 STL(C++标准库)里面的两个数据结构,C++标准库是有多个版本的,要知道我们使用的 STL 是哪个版本,才能知道对应的栈和队列的实现原理。
三个最为普遍的 STL 版本:
- HP STL 其他版本的 C++ STL,一般是以 HP STL 为蓝本实现出来的,HP STL 是 C++ STL 的第一个实现版本,而且开放源代码。
- P.J.Plauger STL 由 P.J.Plauger 参照 HP STL 实现出来的,被 Visual C++编译器所采用,不是开源的。
- SGI STL 由 Silicon Graphics Computer Systems 公司参照 HP STL 实现,被 Linux 的 C++编译器 GCC 所采用,SGI STL 是开源软件,源码可读性甚高。
栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。所以 STL 中栈往往不被归类为容器,而被归类为 container adapter(容器适配器)。
STL 中栈是用什么容器实现的? 栈的底层实现可以是 vector,deque,list 都是可以的, 主要就是数组和链表的底层实现。
我们常用的 SGI STL,如果没有指定底层实现的话,默认是以 deque 为缺省情况下栈的底层结构。
deque 是一个双向队列,只要封住一段,只开通另一端就可以实现栈的逻辑了。
SGI STL 中 队列底层实现缺省情况下一样使用 deque 实现的。
队列
队列中先进先出的数据结构,同样不允许有遍历行为,不提供迭代器, SGI STL 中队列一样是以 deque 为缺省情况下的底部结构。
STL 队列也不被归类为容器,而被归类为 container adapter( 容器适配器)。
双端队列 Deque 解释
Deque 是一个双端队列接口,继承自 Queue 接口,Deque 的实现类是 LinkedList、ArrayDeque、LinkedBlockingDeque,其中 LinkedList 是最常用的。
Deque 有三种用途:
- 普通队列(一端进另一端出):
Queue queue = new LinkedList()或Deque deque = new LinkedList() - 双端队列(两端都可进出)
Deque deque = new LinkedList() - 堆栈
Deque deque = new LinkedList()
注意:Java 堆栈 Stack 类已经过时,Java 官方推荐使用 Deque 替代 Stack 使用。Deque 堆栈操作方法:push()、pop()、peek()。
Deque 是一个线性 collection,支持在两端插入和移除元素。名称 deque 是“double ended queue(双端队列)”的缩写,通常读为“deck”。大多数 Deque 实现对于它们能够包含的元素数没有固定限制,但此接口既支持有容量限制的双端队列,也支持没有固定大小限制的双端队列。
此接口定义在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。插入操作的后一种形式是专为使用有容量限制的 Deque 实现设计的;在大多数实现中,插入操作不能失败。
下表总结了上述 12 种方法:
| 第一个元素 (头部) | 最后一个元素 (尾部) | |||
|---|---|---|---|---|
| 抛出异常 | 特殊值 | 抛出异常 | 特殊值 | |
| 插入 | addFirst(e) | offerFirst(e) | addLast(e) | offerLast(e) |
| 删除 | removeFirst() | pollFirst() | removeLast() | pollLast() |
| 检查 | getFirst() | peekFirst() | getLast() | peekLast() |
Deque 接口扩展(继承)了 Queue 接口。在将双端队列用作队列时,将得到 FIFO(先进先出)行为。将元素添加到双端队列的末尾,从双端队列的开头移除元素。从 Queue 接口继承的方法完全等效于 Deque 方法,如下表所示:
| Queue 方法 | 等效 Deque 方法 |
|---|---|
| add(e) | addLast(e) |
| offer(e) | offerLast(e) |
| remove() | removeFirst() |
| poll() | pollFirst() |
| element() | getFirst() |
| peek() | peekFirst() |
双端队列也可 用作 LIFO(后进先出)堆栈。应优先使用此接口而不是遗留 Stack 类。在将双端队列用作堆栈时,元素被推入双端队列的开头并从双端队列开头弹出。堆栈方法完全等效于 Deque 方法,如下表所示:
| 堆栈方法 | 等效 Deque 方法 |
|---|---|
| push(e) | addFirst(e) |
| pop() | removeFirst() |
| peek() | peekFirst() |
优先级队列
其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。
而且优先级队列内部元素是自动依照元素的权值排列。那么它是如何有序排列的呢?
缺省情况下 priority_queue 利用 max-heap(大顶堆)完成对元素的排序,这个大顶堆是以 vector 为表现形式的 complete binary tree(完全二叉树)。
什么是堆呢?
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
所以大家经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接用 priority_queue(优先级队列)就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。