一、概念
栈和队列是经过限制(或者说特殊的)的线性结构,我们经常将两个放在一起来比较学习。两者分别有以下特点:
- 栈:先进后出
- 队列:先进先出
栈就好比你从一摞书的下面找一本书,先得把上面得取下来;队列像排队,前面得人先得到服务。
二、实现
(一)栈的模拟
栈是一种后进先出的数据结构,对于Stack 我们希望至少要对外提供以下几个方法:
Stack<T>() | 创建一个空的栈 |
---|---|
void push(T s) | 往栈中添加一个新的元素 |
T Pop() | 移除并返回最近添加的元素 |
boolean IsEmpty() | 栈是否为空 |
int Size() | 栈中元素的个数 |
/**
* 此类用于模拟栈
* 三种方法:
* 1,入栈 push()
* 2,出栈 pop()
* 3,取栈顶元素 peek()
*/
public class Mystack {
// 这里以 int 型元素为例,不考虑扩容
private int[] array = new int[100];
private int size = 0;// 有效元素个数
// 入栈
public void push(int val) {
array[size++] = val;
}
// 出栈
public int pop() {
int result = array[size - 1];
size--;
return result;
}
// 取栈顶元素
public int peek() {
return array[size - 1];
}
public static void main(String[] args) {
Mystack mystack = new Mystack();
mystack.push(1);
mystack.push(2);
mystack.push(3);
mystack.push(4);
System.out.println(mystack.pop());
System.out.println(mystack.pop());
System.out.println(mystack.peek());
}
}
(二)队列的模拟
和Stack一样,他也有链表和数组两种实现,理解了Stack的实现后,Queue的实现就比较简单了。
Queue<T>() | 创建一个空的队列 |
---|---|
void push(T t) | 往队列中添加一个新的元素 |
T poll() | 将队列中的第一个元素删除且返回 |
T peek() | 将队列中的第一个元素返回 |
boolean IsEmpty() | 队列是否为空 |
int Size() | 队列中元素个数 |
(1)用数组模拟队列
/**
* 使用数组来模拟队列
* 队列的三种方法:
* (1)入队列push()
* (2)出队列poll()
* (3)取队首元素peek()
*/
public class MyQueueByArray {
private int[] array = new int[100];
// [head,tail)之间的为有效元素
private int head = 0;
private int tail = 0;
// 由于head等于tail时有两种情况,
// 空或者满,因此可以使用size来判断
private int size = 0;
// 入队操作,我们让入队在队尾
public void offer(int val) {
// 先排除特殊情况,需要保证操作不能越界
if (size == array.length) {
// 满队列,无法插入
return;
}
array[tail] = val;
tail++;
// 如果tail++之后超出了数组有效范围,就从头开始
if (tail >= array.length) {
tail = 0;
}
size++;
}
// 出队操作,我们让对手元素出队
public Integer poll(){
// 先排除特殊情况,不能是空队列
if (size == 0){
return null;
}
Integer ret = array[head];
head++;
if (head >= array.length){
head = 0;
}
size--;
return ret;
}
public Integer peek(){
if (size == 0){
return null;
}
return array[head];
}
public static void main(String[] args) {
MyQueueByArray myQueueByArray = new MyQueueByArray();
myQueueByArray.offer(1);
myQueueByArray.offer(2);
myQueueByArray.offer(3);
myQueueByArray.offer(4);
System.out.println(myQueueByArray.poll());
System.out.println(myQueueByArray.peek());
System.out.println(myQueueByArray.poll());
}
}
(2)用链表模拟队列
public class MyQueueByLinkedList {
static class Node{
private int val;
private Node next;
public Node(int val) {
this.val = val;
}
}
// 创建一个链表就得有头节点。此处 head 节点不是傀儡节点
private Node head = null;
private Node tail = null;
// 入队列(尾部插入)
public void offer(int val){
Node newNode = new Node(val);
// 若队列为空
if (head == null){
head = newNode;
tail = newNode;
return;
}
// 队列非空
tail.next = newNode;
tail = tail.next;
}
// 出队列(队首出)
public Integer poll(){
// 若当前为空队列
if (head == null){
return null;
}
// 不是空队列
int ret = head.val;
head = head.next;
if (head == null){
// 删除当前元素后,队列为空
tail = null;
}
return ret;
}
public Integer peek(){
if (head == null){
return null;
}
return head.val;
}
}
三、实际应用场景
(一)、栈
- 用于符号匹配
在编译器的语法检查中,一个过程就是检查各种括号是否匹配,比如 ([]) ,这就是匹配的,而 {[}] 就不匹配了。可以用堆栈来实现括号匹配。
- 用于计算按代数式(也可以用二叉树来解决)
- 构造表达式(也可以用二叉树来解决)
- 用于函数调用
(二)、队列
当多个任务分配给打印机时,为了防止冲突,创建一个队列,把任务入队,按先入先出的原则处理任务。
当多个用户要访问远程服务端的文件时,也用到队列,满足先来先服务的原则。
……
有一个数学的分支,叫队列理论(queuing theory )。用来计算 预测用户在队中的等待时间,队的长度等等问题。
答案取决于用户到达队列的频率,用户的任务的处理时间。如果比较简单的情况,可以单单用分析解决。一个简单的例子就是,一部电话,一个接线员。当一个电话打进来,如果接线员很忙,就电话放到等待线路后。接线员从队头开始应答。
如果有k个接线员,问题就变的复杂了。通常难以分析解决的问题就用模拟来解决。 可以用一个队列来模拟这个问题。如果k的值很大,
我们也可能需要其他数据结构来模拟问题。通过模拟,可以找到最小的 k 值,使队列满足一个合理的等待时间。