【Java---数据结构】栈和队列【Stack & Queue】

一. 栈的概念

栈(Stack):是一种特殊的线性表,它只能在表的一端(称为栈顶)进行插入和删除操作。

可以将栈理解成一摞盘子,只能从上面进行取走,还放在最上面

栈顶:进行插入和删除的位置

栈底: 不能进行插入和删除

栈顶的元素会发生改变,但是栈底的元素相对来说是固定的

特点:

后进先出(LIFO):先进入栈的元素,会最后一个取出

操作的局部性:栈的操作只涉及栈顶元素,不会影响到栈中的其他元素。

二. 栈的基本操作

1. 栈内方法的实现

(1)增加

java 复制代码
    public void push(int val){
        if(isFull()){
            //扩容
            elem = Arrays.copyOf(elem,elem.length+1);
        }
            elem[usedSize++] = val;
    }

主要操作:

  1. 先判满,如果满则进行扩容,然后赋值,不满则直接进行赋值

其中usedSize表示栈中元素的个数

(2)删除

java 复制代码
    public Object  pop(){
        Object o = null;
        if(!isempty()){
//            usedSize表示下一个该放的位置
           o =  elem[usedSize-1];
           usedSize--;
        }else {
             System.out.println("栈为空");
             return null;
        }
        return o;
    }

主要操作:

  1. 先进行判空,如果栈为空,则返回null,不为空则进行删除操作,删除操作:usedSize--,

(3)获取

java 复制代码
    public Object peak(){
        Object o = null;
        if(!isempty()){
            o =  elem[usedSize-1];
        }else {
            System.out.println("栈为空");
            return null;
        }
        return o;
    }

主要操作:

判空,为空则返回null,否则返回栈顶元素

(4)判空

java 复制代码
    public Boolean isempty(){
        if(usedSize!=0){
            return false;
        }
        return true;
    }

元素为0,则表示为空

(5)判满

java 复制代码
    Boolean isFull(){
        if(usedSize>=elem.length){
            return true;
        }
        return false;
    }

如果大于栈的容量,则表示栈满

2. 栈内方法的使用

push():数据入栈

pop():数据出栈

peak():查看栈顶元素

empty():判断栈中是否还有元素

java 复制代码
public class Text_1 {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        //存放数据
        stack.push(1);
        stack.push(2);
        //删除数据
        stack.pop();//删除最后进来的一个元素
        //获取
        stack.peek();//获取最后一个元素,不删除
        //判断栈是否为空
        stack.empty();
    }
}

通过创建Stack对象,使用Stack内的方法操作

三. 实现方式

1. 基于数组实现

优点:

  1. 访问速度快:数组支持随机访问,时间复杂度为O(1)。
  2. 空间利用率高:不需要额外的指针存储空间。

缺点:

  1. 大小固定:数组的大小在创建时需要预先定义,无法动态扩展。
  2. 可能浪费空间:如果栈的使用量远小于数组大小,会浪费存储空间。
  3. 可能栈溢出:如果栈的使用量超过数组大小,会发生栈溢出。

2. 基于链表实现

优点:

  1. 动态扩展:链表的大小可以动态变化,不需要预先定义。
  2. 不会栈溢出:只要内存足够,栈可以无限扩展。

缺点:

  1. 访问速度慢:链表不支持随机访问,每次操作都需要遍历。
  2. 额外空间开销:每个节点需要额外存储一个指针。

单向列表

在单向链表的插入中,分为两种,头插法和尾插法

头插法的时间复杂度为O(1)---- 头插法只需要修改头节点的指针和新节点的指针

尾插法的时间复杂度为O(n)---- 每次插入操作都需要遍历整个链表以找到最后一个节点。

双向链表

双向链表的头插法和尾插法,时间复杂度都为O(1)

注意:我们一般使用的基于链表的栈都为双向链表

总结:

|------|------------------|---------------------|
| 不同点 | 基于数组的栈 | 基于链表的栈 |
| 空间大小 | 预先定义大小,可能浪费空间或溢出 | 动态扩展,不会溢出,但需要额外指针空间 |
| 访问速度 | O(1),访问速度快 | O(1),访问速度快,但链表操作稍复杂 |
| 灵活性 | 固定大小,不适合动态变化 | 动态大小,适合动态变化 |

四. 栈的应用

(1)逆波兰数的运算

图分析

主要步骤为:

  1. 后缀式中的元素依次入栈
  2. 如果遇到运算符,则取出栈中的两个元素,取出的第一个元素,放在第二位,取出的第二个元素放在第一位,进行运算,
  3. 运算后得到的结果放入栈内。

代码实现

java 复制代码
public class Text_3 {
    public int evalRPN(String[] tokens){
        Stack<Integer> stack = new Stack<>();

        int num = 0;
        for(String s:tokens){
            //检查是不是运算符
            if(!isOperation(s)){
                //字符串类型变为整型
                stack.push(Integer.parseInt(s));
            }else{
                //取出栈里面的数据
                int x2 = stack.pop();
                int x1 = stack.pop();

                switch (s){
                    case "+":
                        num = x1+x2;
                        break;
                    case "-":
                        num = x1-x2;
                        break;
                    case "*":
                        num = x1*x2;
                        break;
                    case  "/":
                        num = x1/x2;
                        break;
                }
                //将运算后的结果返回栈内
                stack.push(num);
            }
        }
        //结果为栈内第一个元素
        return stack.peek();
    }
    public boolean isOperation(String s){
        if(s.equals("+")||s.equals("-")||s.equals("*")||s.equals("/")){
            return true;
        }
        return false;
    }
}

(2)用栈得到最小数

最小数不是固定的,会随着添加的数据进行改变

图分析:

主要步骤:

入栈操作:

  1. 每次执行入栈操作,不管stack是否为空都要进行入栈操作,
  2. 在入栈的时候,进行判断:如果stack为空(stack栈为空,则minstack栈一定为空),则minstack和stack都要进行入栈操作
  3. minstack不为空,需要对入栈的元素进行判断,如果小于minstack栈顶元素,则入栈

出栈操作:

  1. 每次执行出栈操作的时候,stack栈都要执行出栈操作
  2. 在每次stack出栈的时候,要进行判断,如果出栈的元素和minstack的栈顶元素相同,则minstack也要进行出栈操作

代码实现:

java 复制代码
class MinStack {
    private Stack<Integer> stack;
    private Stack<Integer> minStack;

    public MinStack() {
        stack = new Stack<>();
        minStack  = new Stack<>();
    }
    public void push(int val) {
        if(minStack.empty()){
            //为空,都要入栈
            minStack.push(val);
        }else {
            //不为空进行判断
            if(val<=minStack.peek()){
                minStack.push(val);
            }
        }
        stack.push(val);
    }

    public void pop() {
        if(!stack.empty()){
             int x = stack.pop();
             if(x == minStack.peek()){
                 minStack.pop();
             }
        }
    }

    public int top() {
        if(stack.empty()) {
            return -1;
        }
        return stack.peek();

    }

    public int getMin() {
        if(minStack.empty()){
           return -1;
        }
        return minStack.peek();

    }
}

队列

一. 队列的概念

队列是一种线性表,只允许在表的一端(队尾,rear)进行插入操作,在另一端(队头,front)进行删除操作。

类似于现实生活中排队,新来的人总是排在队尾,而排在队头的人会先接受服务然后离开队伍。

队头:允许删除的一端

队尾:允许插入的一端。

特点:

**先进先出(FIFO):**最先进入队列的元素会最先被移除,一种先来先服务的原则。

二. 队列的基本操作

1.队列内方法的实现

基于链表的队列实现

(1)入队

java 复制代码
  //插入操作 采用头插法
    public void offer(int val){
        ListNode cur = new ListNode(val);
        if(front ==null){
            front = rear = cur;
        }else {
            cur.next= front;
            cur.prev = null;
            front.prev = cur;
            front = cur;
        }
        usedSize++;
    }

主要步骤:采用头插法

  1. 如果队列为空,将头指针(front)和尾指针(rear)都指向新节点。

  2. 否则,使用头插法将新节点插入到链表头部。

(2)出队

java 复制代码
  public int poll(){

        //为空
        if(front ==null){
            return -1;
        }
        int ret = rear.val;
        //只有一个
        if(front ==rear){
            front = null;
            rear = null;
            usedSize--;
            return ret;
        }
        rear = rear.prev;
        rear.next = null;
        usedSize--;
        return ret;
    }

主要步骤:

  1. 如果队列中只有一个元素,将头指针和尾指针都置为null;

  2. 否则,将rear指针前移一位,并将新的尾指针的next 置为null。

(3)获取

java 复制代码
  public int peak(){
        if(front ==null){
            return -1;
        }
        return rear.val;
    }

(4)判空

java 复制代码
    public boolean isEmpty(){
        if(usedSize ==0){
            return true;
        }
        return false;
    }

如果队列内元素为空,则返回true

2. 队列内方法的使用

入队操作:add( )和offer( )

出队操作:remove( )和poll( )

获取队头元素:peek( )和element()

获取队列元素个数:size( )

java 复制代码
public class Text_1 {
    public static void main(String[] args) {
        //底层通过链表实现
        Queue<Integer> queue = new LinkedList<Integer>() ;
        //入队
        queue.add(2);
        queue.offer(1);
        System.out.println(queue);
        //出队
        queue.remove();
        System.out.println(queue);
//        queue.poll();
        //获取队列第一个元素
        System.out.println(queue.peek());
        System.out.println(queue.element());
        //获取队列元素
        System.out.println(queue.size());
    }
}

通过对Queue接口实例化,这意味着LinkedList类为Queue接口中定义的所有方法都提供了具体的实现。

三. 实现方式

1. 基于数组实现

数组实现的队列使用一个固定大小的数组来存储队列中的元素,通过两个指针(队头指针和队尾指针)来管理队列的插入和删除操作。

数组实现的队列需要解决"队列溢出"和"队列空间浪费"的问题,所以一般采用循环队列的方式

循环队列

在循环队列中如何区分空和满?

  1. 通过添加 size 属性记录
  2. 保留一个位置

其中rear表示插入的位置,front表示删除的位置

空队列:

当rear==front的时候处于空队列

满队列:

当(rear+1)%长度 = front

注意:

在申请空间的时候,一定要在申请大小的基础上加1,因为一个空间不存放数据,用于判断队列的空或满

(1)判满

java 复制代码
    public boolean isFull(){
        if((rear+1)% elem.length ==front){
            return true;
        }
        return false;
    }

(2)判空

java 复制代码
    public boolean isEmpty(){
        if(rear == front ){
            return true;
        }
        return false;
    }

(3)出队

java 复制代码
    public boolean deQueue(){
        if(isEmpty()){
            return false;
        }
        elem[front]= 0;
        front = (front+1)%elem.length;
        return true;
    }

主要操作:

  1. 判空,如果为空则表明无法进行出队操作
  2. 不为空,则将front下标的元素置为0,下标加1(不能直接加1操作,要取模)

(4)入队

java 复制代码
    public boolean enQueue(int val){
        if(isFull()){
            return false;
        }else {
            elem[rear] = val;
            rear = (rear+1)%elem.length;
        }
        return true;
    }

主要操作:

  1. 判满,如果满了则不能进行入队操作
  2. 不满,则将元素赋值在rear下标的位置,下标加1(不能直接加1操作,要取模)

(5)获取

返回队首元素

java 复制代码
    public int Front(){
        if(isEmpty()){
            return -1;
        }
        return elem[front];
    }

返回队尾元素

java 复制代码
    public int Rear(){
        if(isEmpty()){
            return -1;
        }
        int index = 0;
        if(rear ==0){
            index = elem.length-1;
        }else {
            index = rear-1;
        }
        return elem[index];
    }

注意:不为空,才能获取

优点

  1. 空间利用率高,通过循环队列设计避免了空间浪费。
  2. 访问速度快,所有操作的时间复杂度均为O(1)

缺点

  1. 队列大小固定,需要提前定义容量。
  2. 如果队列容量不足,可能会导致队列溢出。

2. 基于链表的队列实现

具体的实现过程上面已经进行了说明

优点

  1. 动态扩展,不会出现队列溢出的问题。
  2. 所有操作的时间复杂度均为O(1)

缺点

  1. 占用空间较大,需要一些空间存放指针

四. 队列的应用

(1)

主要操作:

  1. 将元素依次放入stack1,在将stack1中的元素依次放入stack2中,这时候会发现栈顶的元素和队列第一个元素相等
java 复制代码
class MyQueue {
    Stack<Integer> stack1;
    Stack<Integer> stack2;

    public MyQueue() {
        this.stack1 = new Stack<>();
        this.stack2 = new Stack<>();
    }

    //增加操作
    public void push(int x) {
        stack1.push(x);
    }

    public int pop() {
        if(stack2.isEmpty()){
            while (!stack1.isEmpty()){
                int tmp = stack1.pop();
                stack2.push(tmp); 
            }
        }
        return stack2.pop();
    }

    public int peek() {
        if(stack2.isEmpty()){
            while(!stack1.isEmpty()){
                int tmp = stack1.pop();
                stack2.push(tmp);
            }
        }
        return stack2.peek();
    }
    public boolean empty() {
        if(stack1.isEmpty()&& stack2.isEmpty()){
            return true;
        }
        return false;
    }
}

(2)用队列实现栈

主要操作:

入队操作:

如果两个队列都为空,那么入队到第一个

如果一个空,一个不为空,那么元素入队到不为空的队列中

出队操作:

将元素依次入队到queue1中,在将 queue1中size()-1的元素依次入队到queue2中,queue1中剩下的这个元素就是栈顶元素

代码实现:

java 复制代码
class MyStack {
    Queue<Integer> queue1;
    Queue<Integer> queue2;

    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }

    public void push(int x) {
        //如果为空,在que1加,否则那个有数据加到那个
        if(empty()){
            queue1.offer(x);
        }else {
            if(!queue1.isEmpty()){
                queue1.offer(x);
            }else {
                queue2.offer(x);
            }
        }
    }

    public int pop() {
        //删除操作,将最后一个元素前面的所有元素移到另一个队列中储存,
//        最后一个元素不移,删除最后一个元素
        if(empty()){
            return -1;
        }
        if(!queue1.isEmpty()){
            int size = queue1.size();
            while(size-1>0){
                int tmp = queue1.poll();
                queue2.offer(tmp);
                size--;
            }
            return queue1.poll();
        }else {
            int size = queue2.size();
            while(size-1>0){
                int tmp = queue2.poll();
                queue1.offer(tmp);
                size--;
            }
            return queue2.poll();
        }
    }

    public int top() {
        //获取操作,将所有的元素移过去,记住最后一个元素的值,并返回
        if(empty()){
            return -1;
        }
        int tmp= -1;
        if(!queue1.isEmpty()){
            int size = queue1.size();
            while(size>0){
                tmp = queue1.poll();
                queue2.offer(tmp);
                size--;
            }
            return tmp;
        }else {
            int size = queue2.size();
            while(size>0){
                tmp = queue2.poll();
                queue1.offer(tmp);
                size--;
            }
            return tmp;
        }
    }

    public boolean empty() {
        if(queue1.isEmpty()&&queue2.isEmpty()){
            return true;
        }
        return false;
    }
}
相关推荐
Ryan_Gosling几秒前
QT-异步编程
开发语言·qt
Pocker_Spades_A4 分钟前
Python刷题:Python基础
开发语言·python
总斯霖14 分钟前
题解:士兵排列
数据结构·c++·算法
稳兽龙19 分钟前
P4268 [USACO18FEB] Directory Traversal G
c++·算法·换根dp
嗨起飞了28 分钟前
Maven快速入门指南
java·maven
放氮气的蜗牛35 分钟前
C++从入门到精通系列教程之第十篇:异常处理与调试技巧
开发语言·jvm·c++
A boy CDEF girl44 分钟前
【JavaEE】线程池
java·java-ee
Joeysoda44 分钟前
JavaEE进阶(2) Spring Web MVC: Session 和 Cookie
java·前端·网络·spring·java-ee
q567315231 小时前
用Go的resty库批量下载公开网站视频
开发语言·golang·音视频
Y雨何时停T1 小时前
深入理解 Java 虚拟机之垃圾收集
java·开发语言