赞
踩
目录
栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
- 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
- 出栈:栈的删除操作叫做出栈。出数据在栈顶。
从上图中可以看到,Stack继承了Vector。Vector和ArrayList类似,都是动态的顺序表,不同的是Vector是线程安全的。值得注意的是,现如今Vector几乎已经被淘汰,所以我们只介绍Stack。
我们先简单看一下java中封装好的栈Stack。它的操作很简单,我们可以根据下表所示的功能,进行栈的模拟实现。
我们只需用一个数组存储栈中的所有元素,为了使栈可以存放任意类型的数据元素,我们在此使用泛型。栈的模拟实现比较简单,在此仅将代码展示如下:
MyStack.java
- package MyStack;
-
-
- import java.util.Arrays;
-
- public class MyStack<E> {
-
- public E[] elem;
- public int usedSize;
-
- public MyStack() {
- this.elem = (E[])new Object[2];
- }
-
- /**
- * 入栈
- * @param val
- */
- public void push(E val) {
- if(isFull()){
- this.elem = Arrays.copyOf(this.elem,2*elem.length);
- }
- elem[usedSize] = val;
- usedSize++;
-
- }
-
- /**
- * 判断当前栈是否为满
- * @return
- */
- public boolean isFull() {
- return usedSize == elem.length;
- }
-
- /**
- * 出栈
- * @return
- */
- public E pop() {
- if(empty()){
- System.out.println("当前栈已空!");
- return null;
- } else {
- E tmp = elem[usedSize-1];
- elem[usedSize-1] = null;
- usedSize--;
- return tmp;
- }
- }
-
- public boolean empty() {
- return usedSize==0;
- }
-
- /**
- * 获取栈顶元素 不删除!
- * @return
- */
- public E peek() {
- if(empty()){
- System.out.println("当前栈已空!");
- return null;
- } else {
- E tmp = elem[usedSize-1];
- return tmp;
- }
- }
-
- /**
- * 获取大小
- * @return
- */
- public int getUsedSize() {
- return usedSize;
- }
-
- }
Test.java
- package MyStack;
-
- public class Test {
- public static void main(String[] args) {
- MyStack myStack = new MyStack();
- myStack.push(1);
- myStack.push(2);
- myStack.push(3);
- myStack.push(4);
- myStack.push(5);
- int tmp = (int) myStack.pop();
- System.out.println(tmp);
- int tmp2 = (int)myStack.pop();
- System.out.println(tmp2);
- int tmp3 = (int)myStack.peek();
- System.out.println(tmp3);
- int tmp4 = (int)myStack.pop();
- System.out.println(tmp4);
- }
- }
掌握了栈的模拟实现,栈的使用自然不在话下了。示例代码如下:
- public static void main(String[] args) {
- Stack myStack = new Stack();
- myStack.push(1);
- myStack.push(2);
- myStack.push(3);
- myStack.push(4);
- myStack.push(5);
- int tmp = (int) myStack.pop();
- System.out.println(tmp);
- int tmp2 = (int)myStack.pop();
- System.out.println(tmp2);
- int tmp3 = (int)myStack.peek();
- System.out.println(tmp3);
- int tmp4 = (int)myStack.pop();
- System.out.println(tmp4);
-
- int size = myStack.size();
- System.out.println("栈中元素个数为"+size);
-
- boolean isempty = myStack.empty();
- if(isempty == false){
- System.out.println("当前栈不为空!");
- }else{
- System.out.println("当前栈为空!");
- }
- }
运行结果如下:
队列是只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,具有先进先出FIFO(FirstIn First Out)的特点。
- 入队列:进行插入操作的一端称为队尾(Tail/Rear)
- 出队列:进行删除操作的一端称为队头(Head/Front)
在Java中,Queue是个接口,底层是通过链表实现的。
我们先简单看一下Java中封装好的队列Queue。它的操作很简单,我们可以根据下表所示的功能,进行队列的模拟实现。需要注意的是,队列底层是通过链表实现的。
MyLInkedList.java
- package MyQueue;
-
- import java.util.Queue;
-
- public class MyLinkedList {
-
- class Node {
- public int val;
- public Node next;
- public Node(int val) {
- this.val = val;
- }
- }
-
- public int usedSize;
- public Node head;
- public Node last;
-
- /**
- * 入队
- * @param val
- */
- public void offer(int val) {
- Node node = new Node(val);
- if(head == null){
- head = node;
- last = node;
- } else {
- last.next = node;
- last = node;
- }
- usedSize++;
-
- }
-
- /**
- * 出队
- * @return
- */
- public int poll() {
- if(isEmpty()){
- throw new RuntimeException("当前队列为空!");
- }else{
- int ret = head.val;
- head = head.next;
- usedSize--;
- return ret;
- }
- }
- /**
- * 出队 但是不删除
- * @return
- */
- public int peek() {
- if(isEmpty()){
- throw new RuntimeException("当前队列为空!");
- }else{
- int ret = head.val;
- return ret;
- }
-
- }
- public int size() {
- return usedSize;
- }
- public boolean isEmpty() {
- return usedSize == 0;
- }
- }
Test.java
- package MyQueue;
-
- public class Test {
- public static void main(String[] args) {
- MyLinkedList myLinkedList = new MyLinkedList();
- myLinkedList.offer(12);
- myLinkedList.offer(23);
- myLinkedList.offer(34);
- myLinkedList.offer(45);
- myLinkedList.offer(56);
- System.out.println(myLinkedList.poll());
- System.out.println(myLinkedList.poll());
- System.out.println(myLinkedList.peek());
- System.out.println("当前队列的长度为:"+myLinkedList.size());
- }
-
- }
运行结果如下:
现在我们来思考一个问题。既然栈的实现使用了数组,那为什么队列的实现没有使用数组,而是使用了链表呢?如下图所示为一个数组,假设我们用它来实现队列,那么,你是否能知道队头和队尾分别在哪呢?
显然,随着队列中元素的入队和出队,队头和队尾也在不断发生变化。
显然,如果使用数组实现队列,会造成极大的空间浪费。那么如何才能解决这一问题呢?答案是:把这个数组“卷”起来;当队尾指针来到数组最末端时,不让其指向null,而是指向数组中的第一个元素。这就是我们接下来要谈的“循环队列”。
最终,队列就变成了这个样子:
一开始,head == last,说明该循环队列为空。
各就位,现在开始入队:
Full or not full, this is a question。当循环队列中元素已满时,head == null也成立,那么当这一条件成立时,如何判断当前队列是满还是空呢?以下给出三种方法:
1.通过添加usedSize属性记录;
2.使用标记;
3.保留一个位置。
前两种方法,我们容易理解。添加usedSize属性,判断其等于0还是与数组大小相等即可;使用标记,通过判断last和head的相遇时机,调整标记状态即可。最后一种方法的意思是,当数组中仅有一个位置为空时,我们就认为该数组已满。图解如下:
这里还有一点小问题,那就是在判断队列为满时,last+1=7+1=8,而此时head=0,这二者并不相等。为使数组下标也能循环起来,需要做出这样的调整:
index = (index + 1) % array.length。
保留一个位置表示满时:
- class MyCircularQueue {
- private int[] elem;
- private int front;//表示队头下标
- private int rear;//表示队尾下标
-
- public MyCircularQueue(int k) {
- elem = new int[k];
- }
-
- //入队
- public boolean enQueue(int value) {
- if (!isFull()) {
- elem[rear] = value;
- rear = (rear + 1) % (elem.length);
- return true;
- }
- return false;
- }
-
- //出队
- public boolean deQueue() {
- if (!isEmpty()) {
- front = (front + 1) % elem.length;
- return true;
- }
- return false;
-
- }
-
- //获取队头元素
- public int Front() {
- if(isEmpty()){
- return -1;
- }
- return elem[front];
-
- }
-
- //获取队尾元素
- public int Rear() {
- if(isEmpty()){
- return -1;
- }
- int index =(rear ==0 ) ? elem.length-1 : rear-1;
- return elem[index];
- }
-
- public boolean isEmpty() {
- return rear == front;
- }
-
- /*
- 浪费一个空间表示满
- */
- public boolean isFull() {
- return (rear + 1) % elem.length == front;
- }
- }
用usedSize做标记时:
- class MyCircularQueue {
- private int[] elem;
- private int front;//表示队头下标
- private int rear;//表示队尾下标
- private int usedSize;
-
- public MyCircularQueue(int k) {
- elem = new int[k];
- usedSize = 0;
- }
-
- //入队
- public boolean enQueue(int value) {
- if (!isFull()) {
- elem[rear] = value;
- rear = (rear + 1) % (elem.length);
- usedSize++;
- return true;
- }
- return false;
- }
-
- //出队
- public boolean deQueue() {
- if (!isEmpty()) {
- front = (front + 1) % elem.length;
- usedSize--;
- return true;
- }
- return false;
-
- }
-
- //获取队头元素
- public int Front() {
- if(isEmpty()){
- return -1;
- }
- return elem[front];
-
- }
-
- //获取队尾元素
- public int Rear() {
- if(isEmpty()){
- return -1;
- }
- int index =(rear ==0 ) ? elem.length-1 : rear-1;
- return elem[index];
- }
-
- public boolean isEmpty() {
- return usedSize == 0;
- }
-
- public boolean isFull() {
- return usedSize == elem.length;
- }
- }
链接:
力扣https://leetcode.cn/problems/implement-stack-using-queues/
1.题目:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(
push
、top
、pop
和empty
)。
2.解题
一个队列,只能实现先进先出,没法实现栈,所以至少需要两个队列。在入队时,永远入到不为空的队列;在出队时,从有元素的队列弹出size-1个元素,放到空的那个队列中,然后再将最后一个元素出队。图解如下:
具体代码如下:
- class MyStack {
- private Queue<Integer> qu1;
- private Queue<Integer> qu2;
-
- public MyStack() {
- qu1 = new LinkedList<>();
- qu2 = new LinkedList<>();
- }
-
- //入到不为空队列中
- //都为空,入到qu1
- public void push(int x) {
- if(!qu1.isEmpty()){
- qu1.offer(x);
- }else if(!qu2.isEmpty()){
- qu2.offer(x);
- }else{
- qu1.offer(x);
- }
- }
-
- public int pop() {
- if(empty()){
- return -1;
- }
- if(!qu1.isEmpty()){
- int size = qu1.size();
- for(int i = 0;i<size-1;i++){
- qu2.offer(qu1.poll());
- }
- return qu1.poll();
- }else{
- int size = qu2.size();
- for(int i = 0;i<size-1;i++){
- qu1.offer(qu2.poll());
- }
- return qu2.poll();
- }
- }
-
- public int top() {
- if(empty()){
- return -1;
- }
- int tmp = -1;
- if(!qu1.isEmpty()){
- int size = qu1.size();
- for(int i = 0;i<size;i++){
- tmp = qu1.poll();
- qu2.offer(tmp);
- }
- return tmp;
- }else{
- int size = qu2.size();
- for(int i = 0;i<size;i++){
- tmp = qu2.poll();
- qu1.offer(tmp);
- }
- return tmp;
- }
- }
-
- public boolean empty() {
- if(qu1.isEmpty() &&qu2.isEmpty()){
- return true;
- }
- return false;
- }
- }
-
- /**
- * Your MyStack object will be instantiated and called as such:
- * MyStack obj = new MyStack();
- * obj.push(x);
- * int param_2 = obj.pop();
- * int param_3 = obj.top();
- * boolean param_4 = obj.empty();
- */
要解决这个问题,一定要熟悉队列的操作方法。
链接:
力扣https://leetcode.cn/problems/implement-queue-using-stacks/
1.题目:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(
push
、pop
、peek
、empty
)。
2.解题
用两个栈实现队列,基本思路是:入的时候,都入到一个栈中;出的时候,都出第二个栈中的元素。如果第二个栈中没有元素了,就把第一个栈中的元素全部倒过来。图解如下:
具体代码如下:
- class MyQueue {
- Stack<Integer> s1;
- Stack<Integer> s2;
- public MyQueue() {
- s1= new Stack<>();
- s2= new Stack<>();
- }
-
- public void push(int x) {
- s1.push(x);
- }
-
- public int pop() {
- if(empty()){
- return -1;
- }
-
- if(s2.isEmpty()){
- while(!s1.empty()){
- s2.push(s1.pop());
- }
- }
- return s2.pop();
- }
-
- public int peek() {
- if(empty()){
- return -1;
- }
-
- if(s2.isEmpty()){
- while(!s1.empty()){
- s2.push(s1.pop());
- }
- }
- return s2.peek();
- }
-
- public boolean empty() {
- return s1.empty()&&s2.empty();
- }
- }
-
- /**
- * Your MyQueue object will be instantiated and called as such:
- * MyQueue obj = new MyQueue();
- * obj.push(x);
- * int param_2 = obj.pop();
- * int param_3 = obj.peek();
- * boolean param_4 = obj.empty();
- */
链接:
力扣https://leetcode.cn/problems/min-stack/
1.题目:设计一个支持
push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
2.解题:
首先明确什么是最小栈。要求设计一个栈,除提供最基础的push,pop
,top
操作外,还能在O(1)的时间复杂度内检索到栈中的最小元素。
思路是:使用两个栈,其中一个s为当前栈,另一个minStack栈,用于保存当前栈中的最小元素。有出栈和入栈两种情况:
当入栈时,比较该元素与minStack栈顶元素的大小,如果该元素小于或等于minStack的栈顶元素,该元素入minStack栈中。这一操作可以保证minStack栈顶元素始终为s栈中最小的元素。
当有元素出栈时,也要比较该元素与minStack栈顶元素的大小,如果二者相等,则minStack的栈顶元素也要出栈,否则不对minStack进行操作。
具体代码如下:
- class MinStack {
-
-
- private Stack<Integer> s1;
- private Stack<Integer> minStack;
-
- public MinStack() {
- s1 = new Stack<>();
- minStack = new Stack<>();
- }
-
- /**
- s1这个栈 一定要放元素的
- */
- public void push(int val) {
- s1.push(val);
- if(minStack.empty()) {
- minStack.push(val);
- }else{
- int x = minStack.peek();
- //这里能不能取等号???
- if(val <= x) {
- minStack.push(val);
- }
- }
- }
-
- public void pop() {
- int x = s1.pop();
- int x2 = minStack.peek();
- if(x == x2) {
- minStack.pop();
- }
- }
-
- //获取当前的栈顶元素不删除 ,不是最小栈的栈顶元素
- public int top() {
- return s1.peek();
- }
-
- public int getMin() {
- return minStack.peek();
- }
- }
-
-
- /**
- * Your MinStack object will be instantiated and called as such:
- * MinStack obj = new MinStack();
- * obj.push(val);
- * obj.pop();
- * int param_3 = obj.top();
- * int param_4 = obj.getMin();
- */
本课主要讲解了栈和队列,了解了栈和队列的模拟实现后,关键是灵活使用java集合类中封装好的栈和队列,尤其注意出栈和入栈使用的是push()和pop()操作,出队和入队使用的是offer()和pull()操作;在栈中,判空使用的是empty()方法;在队列中,判空使用的是isEmpty()方法。而获取栈或队列的长度以及获取栈顶元素(队头元素),其方法名是一致的,为size()和peek()。
最后,通过用两个栈实现队列,用两个队列实现栈,进一步加深了我们对栈和队列的理解。
本课内容结束!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。