栈Stack |队列Queue| 双端队列Deque| 优先队列PriorityQueue
堆栈和队列特点: 1. Stack - First In Last Out(FILO) 先入后出,先进来的被压入栈底 .Array or Linked List 2. Queue - First In First Out(FIFO) 排队时先来先出 .Array or Doubly Linked List
1. Java中栈的实现API
在Java中,用Deque可以实现Stack的功能:
- 把元素压栈:
push(E)/addFirst(E); - 把栈顶的元素“弹出”:
pop(E)/removeFirst(); - 取栈顶元素但不弹出:
peek(E)/peekFirst()。
为什么Java的集合类没有单独的Stack接口呢?因为有个遗留类名字就叫Stack,出于兼容性考虑,所以没办法创建Stack接口,只能用Deque接口来“模拟”一个Stack了。不要使用遗留类Stack
当我们把Deque作为Stack使用时,注意只调用push()/pop()/peek()方法,不要调用addFirst()/removeFirst()/peekFirst()方法,这样代码更加清晰。
Stack,ArrayDeque,LinkedList都可以作为栈使用:
由继承树看出,三者都是Collection的间接实现类。 ArrayDeque实现Deque接口,Stack继承于Vector,LinkedList实现Deque与List接口。
区别
底层数据存储方式:
| 存储方式 | |
|---|---|
| Stack | 长度为10的数组 |
| ArrayDeque | 长度为16的数组 |
| LinkedList |
链表
|
线程安全
| 线程安全 | |
|---|---|
| Stack | 线程同步 |
| ArrayDeque | 线程不同步 |
| LinkedList | 线程不同步 |
使用Collections工具类中synchronizedXxx() 可以将线程不同步的ArrayDeque以及LinkedList转换成线程同步。
性能选项
通常情况下,不推荐使用Vector以及其子类Stack (它们实现了线程同步的功能,所以性能比较差);
频繁的插入、删除操作,未知的初始数据量:LinkedList ;
频繁的随机访问操作:ArrayDeque
另一个区别是:LinkedList支持null元素,而ArrayDeque不支持。
2. Java中Queue队列的API
队列Queue实现了一个先进先出(FIFO)的数据结构:
- 通过
add()/offer()方法将元素添加到队尾; - 通过
remove()/poll()从队首获取元素并删除; - 通过
element()/peek()从队首获取元素但不删除。
要避免把null添加到队列。
// 这是一个List: List<String> list = new LinkedList<>();
// 这是一个Queue: Queue<String> queue = new LinkedList<>();
LinkedList即实现了List接口,又实现了Queue接口,但是,在使用的时候,如果我们把它当作List,就获取List的引用,如果我们把它当作Queue,就获取Queue的引用
ArrayDeque也实现了Deque接口(Deque接口继承了Queue接口)
3. 双端队列Deque的API
Deque是一个接口,它的实现类有ArrayDeque和LinkedList (LinkedList真是一个全能选手,它即是List,又是Queue,还是Deque)
// 不推荐的写法: LinkedList<String> deque = new LinkedList<>();
// 推荐的写法: Deque<String> deque = new LinkedList<>(); (面向抽象编程的一个原则就是:尽量持有接口,而不是具体的实现类。)
- 将元素添加到队首:
addFirst()即push()/offerFirst(); - 将元素添加到队尾:
addLast()即add()/offerLast() 即offer()
- 从队首获取元素并删除:
removeFirst()即remove() pop()/pollFirst() 即poll() - 从队尾获取元素并删除:
removeLast()/pollLast();
- 从队首获取元素但不删除:
getFirst()/peekFirst() 即peek() - 从队尾获取元素但不删除:
getLast()/peekLast();
- 总是调用
xxxFirst()/xxxLast()以便与Queue的方法区分开; - 避免把
null添加到队列。
如果直接写deque.offer(),我们就需要思考,offer()实际上是offerLast(),我们明确地写上offerLast(),不需要思考就能一眼看出这是添加到队尾。
因此,使用Deque,推荐总是明确调用offerLast() /offerFirst()或者pollFirst() /pollLast()方法。
4. 优先队列PriorityQueue的API
Queue<String> q = new PriorityQueue<>();
PriorityQueue和Queue的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue调用remove()或poll()方法,返回的总是优先级最高的元素。
要使用PriorityQueue,我们就必须给每个元素定义“优先级”。
PriorityQueue实现了一个优先队列:从队首获取元素时,总是获取优先级最高的元素。
PriorityQueue默认按元素比较的顺序排序(必须实现Comparable接口),也可以通过Comparator自定义排序算法(元素就不必实现Comparable接口)。
放入PriorityQueue的元素,必须实现Comparable接口,PriorityQueue会根据元素的排序顺序决定出队的优先级。
如果我们要放入的元素并没有实现Comparable接口怎么办?PriorityQueue允许我们提供一个Comparator对象来判断两个元素的顺序。
1. 栈Stack
Stack中文名可叫堆栈,不能叫堆,堆是heap
手写栈堆比较少了,很多语言在标准库都实现了。
1.1 概念与特性
后进先出,先进后出,这就是典型的“栈”结构。
从栈的操作特性上来看,栈是一种“操作受限”的线性表,只允许在一端插入和删除数据。从功能上来说,数组或链表可以替代栈,但特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。
当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构。
最近相关性 《====》 栈 现实中的洋葱,一层层,反应在工程中都具有 从外而内 或者 由内而外这种逐渐扩散,且它的最外层和最外层是一对,最内层和最内层是一对 可叫最近相关性。
栈:查询 O(n),平均情况,要看它栈中栈底的元素,要清空了才能看到。
插入和删除它的栈顶元素只需要一次性操作,时间复杂度是O(1)。
1.2 如何实现一个“栈”?
从栈的定义里,栈主要包含两个操作,入栈和出栈,即在栈顶插入一个数据和从栈顶删除一个数据。
自定义一个栈既可以用数组来实现,也可以用链表来实现。 用数组实现的栈,叫作顺序栈,用链表实现的栈,叫作链式栈。
// 基于数组实现的顺序栈 public class ArrayStack { private String[] items; // 数组 private int count; // 栈中元素个数 private int n; // 栈的大小 // 初始化数组,申请一个大小为 n 的数组空间 public ArrayStack(int n) { this.items = new String[n]; this.n = n; this.count = 0; } // 入栈操作 public boolean push(String item) { // 数组空间不够了,直接返回 false,入栈失败。 if (count == n) return false; // 将 item 放到下标为 count 的位置,并且 count 加一 items[count] = item; ++count; return true; } // 出栈操作 public String pop() { // 栈为空,则直接返回 null if (count == 0) return null; // 返回下标为 count-1 的数组元素,并且栈中元素个数 count 减一 String tmp = items[count - 1]; --count; return tmp; } }