目录
一、什么是栈,什么是队列?
栈:栈的特点是后进先出,也就是从哪边进从哪边出(就像装在罐子里的糖果,最后装进去的,最先被取出来)
队列:队的特点是先进先出,也就是从哪边进,从另一边出(就像我们排队买饭一样,先排队的人先买到饭)
二、自己实现栈
2.1 核心操作
- 入栈push(把元素放到栈里面)
- 出栈pop(把最后进来的元素删掉)
- 取栈顶元素peek(获取到最后一个进来的元素的结果)
2.2 使用顺序表实现
尾插尾删即可(不建议头插头删,由于顺序表是基于数组实现的,如果头插头删,可能会存在大量的挪动元素,效率较低)
public class MyStack1 {
private int[] data=new int[100];
private int size=0;
//入栈
public void push(int val){
if(size>=data.length){
return;
}
data[size]=val;
size++;
}
//出栈
public Integer pop(){
if(size==0){
return null;
}
int ret=data[size-1];
size--;
return ret;
}
//取栈顶元素
public Integer peek(){
if(size==0){
return null;
}
return data[size-1];
}
}
2.3 使用链表实现
头插头删即可(也可以尾插尾删,要记录链表尾节点,所以会额外记录一些数据,代码稍微复杂一点)
public class MyStack2 {
//静态内部类Node,使这个类不依附于MyStack这个类
static class Node{
int val;
Node next;
public Node(int val) {
this.val = val;
}
}
private Node head=null;
//入栈
public void push(int val){
Node newNode=new Node(val);
if(head==null){
head=newNode;
return;
}
newNode.next=head;
head=newNode;
}
//出栈
public Integer pop(){
if(head==null){
return null;
}
int ret=head.val;
head=head.next;
return ret;
}
//取栈顶元素
public Integer peek(){
if(head==null){
return null;
}
return head.val;
}
}
三、自己实现队
3.1 核心操作
- 入队offer
- 出队poll
- 取队首元素peek
3.2 使用顺序表实现(环形队列)
尾插头删即可。现在来思考一下,假如我们的队列中存满了,接着出队了几个元素,意味着又多了几个空间,此时再想入队,我们该如何利用这几个空间呢?如何判断队列真正的满了呢?
我们可以定义一个head引用记录队首的位置,定义tail记录队尾的位置,定义一个size记录队中当前元素的个数。当size>=数组的长度时,我们就认为队满了,和tail是否走到数组的最后一个位置无关,因为也可能有因为出队,还有空间没使用的情况。
如果tail已经走到了数组的末尾,但是队并没有满,那么将tail置0即可。
public class MyQueue1 {
private int[] data=new int[100];
private int head=0;
private int tail=0;
private int size=0;
//入队
public boolean offer(int val){
if(size==data.length){
return false;
}
data[tail]=val;
tail++;
size++;
if(tail==data.length){
tail=0;
}
return true;
}
//出队
public Integer poll(){
if(size==0){
return null;
}
int ret=data[head];
head++;
size--;
if(head==data.length){
head=0;
}
return ret;
}
//取队首元素
public Integer peek(){
if(size==0){
return null;
}
return data[head];
}
}
3.3 使用链表实现
尾插头删即可,记录链表的头节点和尾节点。
public class MyQueue2 {
static class Node{
int val;
Node next;
public Node(int val) {
this.val = val;
}
}
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;
return ret;
}
//取队首元素
public Integer peek(){
if(head==null){
return null;
}
return head.val;
}
}
四、标准库中的栈和队
4.1 分析上图
上面的图中,我们可以看到,Stack是一个类,也就意味着可以直接拿过来用,而Queue是一个接口,不能实例化,需要创建相应的子类,Java标准库中提供的Queue对应的实现只有LinkedList用链表实现这一种选择,没有提供环形队列这个版本。
注意:由于Stack继承自Vector,实现了List接口,所以List能用的方法,在Stack中也都能用,但是我们还是尽量使用栈的核心操作:push,pop,peek
4.2 什么是Vector
Vector也是顺序表,和ArrayList差不多,细节上也有一些区别:
- Vector线程安全,ArrayList线程不安全
- 扩容策略不同
- 出现的时机不同,Vector出现的早,ArrayList是后来才出现的。
4.3 标准库中的栈和队的方法