摘要
从原理上分析了栈与队列的理论基础知识,并用自定义双链表结构完成了实现。完成了leetcode上232.用栈实现队列、225. 用队列实现栈,一共两道相关题目,进行了具体的思路分析以及Java代码实现。
栈与队列理论基础
原理简析
栈和队列,是计算机基本的数据结构。
栈
栈最为人熟知的就是它后进先出LIFO(Last In First Out)的特性。栈就像一个木桶,不管什么时候,我们往其中放入木板(压栈),新放入的木板都在上面(栈顶)。而我们要取出木板(出栈),第一个取出的都是最后放进木桶的木板(后进先出),随后才能取出下面的木板。
队列
队列与栈相反,其特性是先进先出FIFO(First In First Out)。队列就是一根隧道,火车进入隧道(进队列),火车头先进入隧道一端(队首),火车尾随后进入;火车出隧道时(出队列),火车头先从隧道另一段出来(队尾),火车尾随后出来。
通过以上两个比喻的描述,我们可以很清晰地认识到这两个数据结构的原理。那么如何用代码来实现它们呢?
代码实现
本文使用leetcode《707.设计链表》中的自定义双链表MyLinkedList
实现栈。
博主的《Carl代码随想录算法训练营-Day 3- 链表理论基础、203.移除链表元素、707.设计链表、24.两两交换链表中的节点》中有该自定义双链表的详细Java实现代码。
但此处新添加两个方法:
size()
:返回一个int
,表示链表长度。
isEmpty
:返回一个boolean
,表示链表是否为空。
//MyLinkedList的类结构
package Leetcode.editor.util;
public class MyLinkedList{
final private ListNode _head;
final private ListNode _tail;
private int _size;
private class ListNode{
int val;
ListNode next,prev;
}
public ListNode(int val,ListNode next,ListNode prev){}
public ListNode(){}
}
public MyLinkedList() {}
private ListNode get(int index,boolean mark){}
public int get(int index) {}
public void addAtHead(int val) {}
public void addAtTail(int val) {}
public void addAtIndex(int index, int val) {}
public void deleteAtIndex(int index) {}
public int size(){}
public int isEmpty(){}
}
栈的实现
对于栈,我们可以使用一个链表(或数组)来实现。
压栈(push)操作:新建节点接在链表尾部。
出栈(pop)操作:将链表尾节点取出并删除。
栈顶(peek)操作:查询链表尾节点的值。
查看栈内元素个数(size):返回链表节点个数。
栈是否为空(isEmpty):返回链表长度是否为0。
package leetcode.editor.util;
import java.util.NoSuchElementException;
public class MyStack {
private final MyLinkedList list;
public MyStack(){this.list=new MyLinkedList<>();}
public void push(int val){list.addAtTail(val);}
public int pop(){
if(list.isEmpty()){throw new NoSuchElementException();}
int last = list.size() - 1;
int val = list.get(last);
list.deleteAtIndex(last);
return val;
}
public int peek(){
if(list.isEmpty()){throw new NoSuchElementException();}
int last = list.size() - 1;
return list.get(last);
}
public int size(){return list.size();}
public boolean isEmpty(){return list.isEmpty();}
}
队列的实现
对于队列,我们同样可以使用一个链表(或数组)来实现。
进队(offer)操作:新建节点接在尾部。
出队(poll)操作:将链表头节点取出并删除。
队顶(peek)操作:查询链表头节点的值。
查看队列内元素个数(size):返回链表节点个数。
队列是否为空(isEmpty):返回链表长度是否为0。
package leetcode.editor.util;
import java.util.NoSuchElementException;
public class MyQueue {
private final MyLinkedList<Integer> list;
public MyQueue(){this.list=new MyLinkedList<>();}
public void offer(int val){list.addAtTail(val);}
public int poll(){
if(list.isEmpty()){throw new NoSuchElementException();}
int val = list.get(0);
list.deleteAtIndex(0);
return val;
}
public int peek(){
if(list.isEmpty()){throw new NoSuchElementException();}
return list.get(0);
}
public int size(){return list.size();}
public boolean isEmpty(){return list.isEmpty();}
}
232、用栈实现队列
思路分析
我们知道栈只有一个口,同时用于进出,而队列有两个口,一个进一个出;而且栈的元素进出顺序相反,队列的元素,进出顺序是相同的。
因此要用栈实现队列,我们就需要两个栈,一个为进栈stackIn
,一个为出栈stackOut
,这样经过反-反为正的处理之后,元素进出顺序就一致了。
进队(offer
)操作,调用stackIn.push()
进行压栈即可。
出队(poll
),先看stackOut
是否为空,如果是,就将stackIn
中的全部元素依次弹出,并压入stackOut
中。最后再弹出stackOut
的栈顶元素即可。
返回栈顶(peek
),先出队,然后再将该元素压回stackOut
。
代码实现
class MyQueue {
private final Stack<Integer> stackIn;
private final Stack<Integer> stackOut;
public MyQueue() {
stackIn = new Stack<>();
stackOut = new Stack<>();
}
public void push(int x) {
stackIn.push(x);
}
public int pop() {
if (stackOut.isEmpty()) {
while (!stackIn.isEmpty()) {
stackOut.push(stackIn.pop());
}
}
return stackOut.pop();
}
public int peek() {
int x = this.pop();
stackOut.push(x);
return x;
}
public boolean empty() {
return stackIn.isEmpty() && stackOut.isEmpty();
}
}
225、用队列实现栈
思路分析
如果用双端队列实现栈,这将非常简单,与用双链表实现栈是一摸一样。进栈直接加在队列尾部,出栈直接删掉队列尾部。
如果用单端队列实现栈,将复杂一些。我们主要分析单端队列实现栈的进出操作。
进栈(push),同样使用队列的offer操作即可。
出栈(pop),我们发现必须要将队列尾部元素弹出才行。但对于单端队列来说,尾部元素一定是最后出队的。故而,我们可以让队列先弹出尾部元素前面所有元素,并将它们重新加入队列中。完成这个操作之后,队列头部就是我们要出栈的元素了,可以直接出队列。
代码实现
class MyStack {
private final LinkedList<Integer> list;
public MyStack() {
this.list = new LinkedList<>();
}
public void push(int x) {
list.offer(x);
}
public int pop() {
for (int i = 0; i < list.size() - 1; i++) {
list.offer(list.poll());
}
return list.poll();
}
public int top() {
int x = this.pop();
list.offer(x);
return x;
}
public boolean empty() {
return list.isEmpty();
}
}
总结和思考
今天我们通过分析栈和队列的基础原理,分别使用自定义双链表实现了栈和队列,又分别用队列实现了栈,用栈实现了队列,这对于我们学习栈和队列的数据结构相关知识起到了重要作用。
希望对大家的算法学习有所帮助,谢谢阅读!