快速掌握栈和队列(含面试题)

1. 栈(Stack)

1.1 什么是栈

【图示】
栈的图示
【概念】
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶.

*生活中栈的应用:装填子弹时,先填进去的子弹最后发射
在这里插入图片描述

【总结】

  1. 栈是一种先进后出的数据结构,底层由数组和一个计数器组成(同顺序表)
  2. 栈,逻辑上先进后出,物理上连续
  3. usedSize计数器指向的下标就是栈顶,0下标是栈底

1.2 栈的使用

【方法说明】

  1. Stack(),构造一个空的栈
  2. E push(E e),将e入栈,并返回e
  3. E pop(),将栈顶元素出栈
  4. E peek(),获取栈顶元素,不出栈
  5. int size(),获取栈中有效元素个数
  6. boolean empty(),检测栈是否为空

【代码演示】

public class Test {  
    //演示Java中Stack的使用  
    public static void main(String[] args) {  
        Stack<Integer> stack = new Stack<>();  
        stack.push(12);  
        stack.push(23);  
        stack.push(34);  
        System.out.println(stack.peek());  
    }  
}

1.3 实现栈的CRUD

【前期准备】

  1. 准备数组和变量(计数器)
  2. 准备构造方法方便对数组进行初始化

【代码实现】

public class MyStack implements IStack {  
  
    public int elem[];  
    public int usedSize;  
  
    private static final int size = 8;
    
    //构造方法一
    public MyStack(int usedSize) {  
        //使代码调用者可以自定义数组内存大小  
        this.elem = new int[usedSize];  
    } 
    //构造方法二
    public MyStack() {  
        this.elem = new int[size];  
    }
}
a. 增(2道)

【需注意】

  1. 栈是否为满,满了不再能入栈

1. 构造一个空的栈(构造方法)
【代码逻辑】

  1. 找思路:自定义数组大小
  2. 写代码框架:无
  3. 填充代码:无
  4. 完善代码逻辑严谨性:无

【代码实现】

//构造方法一
public MyStack(int usedSize) {  
    //使代码调用者可以自定义数组内存大小  
    this.elem = new int[usedSize];  
} 
//构造方法二
public MyStack() {  
    this.elem = new int[size];  
}

2. 将e入栈,并返回e
【代码逻辑】

  1. 找思路:入栈的下标正好是usedSize指向的下标
  2. 写代码框架:无
  3. 填充代码:无
  4. 完善代码逻辑严谨性:判断栈是否满了,满了要扩容

【代码实现】

//该方法判断栈是否满了
private boolean isFull() {  
    return this.elem.length == this.usedSize;  
}

public void push(int e) {  
    if (isFull()) {  
        //满了就扩容  
        this.elem = Arrays.copyOf(this.elem,2*this.elem.length);  
    }  
    this.elem[this.usedSize] = e;  
    usedSize++;  
}
b. 删(1道)

【需注意】

  1. 栈是否为空,空了不再能出栈

1. 将栈顶元素出栈
【代码逻辑】
2. 找思路:必须用第三个变量存数据
3. 写代码框架:无
4. 填充代码:无
5. 完善代码逻辑严谨性:判断栈是否为空

【代码实现】

public int pop() {  
    if (empty()) {  
        return -1;  
    }  
    //用ret把usedSize存起来  
    int ret = this.usedSize-1;  
    //us--  
    this.usedSize--;  
    //返回ret下标的值  
    return this.elem[ret];  
  
}
c. 查(3道)

1. 获取栈顶元素,不出栈
【代码逻辑】

  1. 找思路:无
  2. 写代码框架:无
  3. 填充代码:无
  4. 完善代码逻辑严谨性:判断栈是否为空

【代码实现】

public int peek() {  
    if (empty()) {  
        return -1;  
    }  
    return this.elem[this.usedSize-1];  
}

2. 获取栈中有效元素个数
【代码逻辑】

  1. 找思路:无
  2. 写代码框架:无
  3. 填充代码:无
  4. 完善代码逻辑严谨性:无

【代码实现】

public int size() {  
    return this.usedSize;  
}

3. 检测栈是否为空
【代码逻辑】

  1. 找思路:无
  2. 写代码框架:无
  3. 填充代码:无
  4. 完善代码逻辑严谨性:无

【代码实现】

public boolean empty() {  
    //为空返回true  
    return this.usedSize == 0;  
}

2. 链式栈

【图示】
在这里插入图片描述

【代码演示】链式栈是一种底层由链表实现的栈,也就是说链表本身可以直接拿来当栈使用

LinkedList<Integer> linkedList = new LinkedList();  
//入栈  
linkedList.push(12);
linkedList.push(34);
//出栈
System.out.println(linkedList.pop());  //34

3. 栈,虚拟机栈和栈帧

  1. 栈:是一种先进后出的数据结构,底层由数组组成
  2. 虚拟机栈:是JVM划分的一块内存
  3. 栈帧:调用方法时,会在JVM中给这个方法开辟一块内存空间,这块内存就是栈帧

2. 队列(Queue)

2.1 什么是队列

【图示】
在这里插入图片描述
【概念】
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾(Tail/Rear)
出队列:进行删除操作的一端称为队头(Head/Front)

【总结】

  1. 与栈相反,队列是一种先进先出的数据结构
  2. 与栈不同,队列是一个接口,底层是双向链表;实例化队列时,必须实例化LinkedList的对象,因为LinkedList实现了Queue接口

2.2 队列的使用

【方法说明】

  1. boolean offer(E e),入队列
  2. E poll(),出队列
  3. peek(),获取队头元素
  4. int size(),获取队列中有效元素个数
  5. boolean isEmpty(),检测队列是否为空

【代码演示】

public static void main(String[] args) {  
    //队列的实例化是实现的LinkedList的接口,通过LinkedList类向上转型  
    Queue<Integer> queue = new LinkedList<>();  
    //入队  
    queue.offer(12);  
    queue.offer(23);  
    //出队  
    queue.poll();  
    //瞄一眼  
    queue.peek();  
    queue.peek();  
    //队列里有多少元素  
    System.out.println(queue.size());  
    //队列是否为空  
    System.out.println(queue.isEmpty());  
}

2.3 实现队列的CRUD

【前期准备】

  1. 因为其底层是双链表,同 前面实现双链表

【代码实现】

public class MyQueue implements IQueue{  
    public static class ListNode{  
        private int val;  
        private ListNode next;  
        private ListNode prev;  
  
        public ListNode(int val) {  
            this.val = val;  
        }  
    }  
    private ListNode first;  
    private ListNode last;
}
a. 增(1道)

1. 入队列
【代码逻辑】

  1. 找思路:尾插法插入
  2. 写代码框架:无
  3. 填充代码:无
  4. 完善代码逻辑严谨性:链表为空;计数器++

【代码实现】

public void offer(int e) {  //尾插法  
    //实例化要插入节点,假设从尾节点进,头节点出  
    ListNode node = new ListNode(e);  
  
    if (this.first == null) {  
        //链表为空,把头和尾节点都设为插入的节点  
        this.first = node;  
        this.last = node;  
    }else {  
        //链表已有节点,尾插法  
        this.last.next  = node;  
        node.prev = this.last;  
        this.last = node;  
    }  
    this.size++;  
}
b. 删(1道)

1. 出队列
【代码逻辑】

  1. 找思路:从头节点开始出
  2. 写代码框架:无
  3. 填充代码:无
  4. 完善代码逻辑严谨性:无

【代码实现】

public int poll() {   //从头节点开始  
    int val = -1;  
    //该if...else判断链表是否为空  
    if (this.first == null) {  
        //链表为空不弹出  
        return -1;  
    }else {  
        //链表不为空,返回头节点的值 头节点后移 size--        
        val = this.first.val;  
        this.first =this.first.next;  
        size--;  
    }  
    return val;  
}
c. 查(3道)

1. 获取队头元素
【代码逻辑】

  1. 找思路:无
  2. 写代码框架:无
  3. 填充代码:无
  4. 完善代码逻辑严谨性:无

【代码实现】

public int peek() {  
    if (this.first == null) {  
        return -1;  
    }  
    return this.first.val;  
}

2. 获取队列中有效元素个数
【代码逻辑】

  1. 找思路:无
  2. 写代码框架:无
  3. 填充代码:无
  4. 完善代码逻辑严谨性:无

【代码实现】

public int size() {  
    return this.Size;  
}

3. 检测队列是否为空
【代码逻辑】

  1. 找思路:无
  2. 写代码框架:无
  3. 填充代码:无
  4. 完善代码逻辑严谨性:无

【代码实现】

public boolean empty() {  
    //为空返回true  
    return this.Size == 0;  
}

3. 循环队列

3.1 什么是循环队列

循环队列底层是数组,但想要循环输入数组中,需要解决两个问题:

  1. 下标遍历到最后一个元素,如何返回0下标
  2. 怎么判断数组是否满了

3.2 设计循环队列

class MyCircularQueue {
    //准备循环队列的底层材料

    //数组
    private int[] elem;
    //队列头下标
    private int front;
    //队列尾下标(可存放元素的下标)
    private int rear;

    public MyCircularQueue(int k) {
        //因为浪费了一个空间来判断数组是否满,所以k+1
        this.elem = new int[k+1];
    }

    //入队
    public boolean enQueue(int value) {
        if (isFull()) {
            return false;
        }
        this.elem[rear] = value;
        //rear往后移
        this.rear = (rear+1) % elem.length;
        return true;
    }

    //出队
    public boolean deQueue() {
        if (isEmpty()) {
            return false;
        }
        //出队只需移动front,front右移
        this.front = (front+1) % elem.length;
        return true;
    }

    //获取队首元素
    public int Front() {
        if (isEmpty()) {
            return -1;
        }
        return this.elem[front];
    }

    //获取队尾元素
    public int Rear() {
        if (isEmpty()) {
            return -1;
        }
        //数组满时,rear回到了0下标,这时rear-1会异常
        int a = (this.rear == 0 ? elem.length-1 : this.rear-1);
        return this.elem[a];
    }
    
    //队列是否为空
    public boolean isEmpty() {
        //rear和front相遇,队列为空
        return this.rear == this.front;
    }

    //队列是否满了
    public boolean isFull() {
        //rear的下一个是front,说明数组满了
        return (rear+1)%elem.length == front;
    }
}

4. 双端队列(Deque)

4.1 什么是双端队列

双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,Deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队

  • 双端队列指两端都可以进行元素的出队入队操作的队列,Deque是一个接口,需要实例化一个LinkedList对象
  • 在实际的工程中,使用Deque接口是比较多的,栈和队列均可以使用Deque接口。

4.2 双端队列实现栈/队列

1. 链式实现(队列)
//使用Deque接口实现队列,双端队列  
Deque<Integer> queue1 = new LinkedList<>();  
//入队  
queue1.offer(1);  
queue1.offer(2);  
//出队  
System.out.println(queue1.peek());
2. 线性实现(栈)
//使用Deque接口实现栈,创建一个Deque对象  
Deque<Integer> stack = new ArrayDeque<>();  
//入栈  
stack.push(12);  
stack.push(34);  
//瞄一眼  
System.out.println(stack.peek());  //34

5. 面试题

5.1 用队列实现栈

5.2 用栈实现队列

6. 总结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值