当前位置: 首页 > news >正文

代码随想录刷题day28|(栈与队列篇:栈)232.用栈实现队列

目录

一、栈的基本知识

二、栈在Java中的实现

太长不看简易总结版

1.使用组合模式

2.通过Deque接口

3.基于底层数据结构的实现

使用数组:

使用链表:

4.使用Java的Stack类

三、相关算法题目

四、其他细节

1.组合模式如何通过封装隐藏其他方法?

2.为什么 Stack 类不能隐藏这些破坏栈特性的方法?

3.组合模式中创建对象的具体实现:

4.接口作为类的成员变量


一、栈的基本知识

栈是一种特殊的数据结构,所有的数据操作只能在一端(栈顶)进行操作,特点是先进后出,栈中除了栈顶元素其他都是未知的,栈无法随机访问;比如桶装羽毛球;

入栈:往栈中添加元素的操作,一般叫做push;

出栈:从栈中移除元素的操作,并返回栈顶元素,一般叫做pop,出栈(弹出栈顶元素);

peek:获取栈顶元素,并不出栈;

注意:这里说的"栈"与内存中的"栈空间"是两个不同的概念,具体不同见⬇️

Java 【数据结构】 栈(Stack)和队列(Queue)【神装】_java stack queue-CSDN博客

二、栈在Java中的实现

四种实现方法:

太长不看简易总结版

实现方式优点缺点
使用Stack类简单易用,线程安全

性能较差、设计缺陷

使用Deque接口的实现类性能高、代码简洁需自觉遵守栈的规则
组合模式实现栈隐藏底层实现、灵活性高需要手动实现栈的逻辑
直接通过数组、链表实现栈性能高、实现方式更底层灵活性较低、需要手动实现

1.使用组合模式

组合模式(Composition)是一种设计模式,它通过在一个类中持有另一个类的实例来实现功能,而不是通过继承。组合模式的核心思想是 “优先使用组合,而不是继承”  即:持有实例而不是通过继承。

组合模式的体现:

  • 在栈的实现中,MyStack类持有一个List接口的实例(如 ArrayList),并通过实例来实现栈的功能,而不是继承 List;

  • 这样,MyStack 类可以通过封装隐藏 List 的其他方法(防止破坏栈的特性),只暴露栈的标准接口;

  • 需要手动编写代码实现栈的操作逻辑(如push、pop、peek等);可以 完全控制栈的行为,隐藏底层数据结构的方法,确保栈的特性不被破坏。

示例代码:

public class MyStack<E> {
    private List<E> list; // 组合 List 的实例

    public MyStack() {
        this.list = new ArrayList<>(); //使用ArrayList实现
        //将list成员变量初始化为一个 ArrayList对象
        //创建了一个 ArrayList 的实例,并将其赋值给 list
    }
    // 栈的标准方法
    public void push(E element) {
        list.add(element);
    }
    public E pop() {
        if (isEmpty()) {
            throw new RuntimeException("Stack is empty");
        }
        return list.remove(list.size() - 1);
    }
    public E peek() {
        if (isEmpty()) {
            throw new RuntimeException("Stack is empty");
        }
        return list.get(list.size() - 1);
    }
    public boolean isEmpty() {
        return list.isEmpty();
    }
}

2.通过Deque接口

Deque接口(双端队列),支持在队列的两端进行插入和删除操作,继承了对队列Queue接口;

Deque有两个主要的实现类:ArrayDeque(底层使用数组),LinkedList(底层使用双向链表);

具体实现:

  • Deque接口的实现类(如 ArrayDeque)提供了栈的标准方法(如 push、pop、peek),直接调用即可;
  • 同时也提供了其他方法(如 addFirst、addLast、removeFirst、removeLast 等),误用可能会破坏栈的特性;
  • deque的设计目标是灵活,如果像组合模式一样封装,会影响其灵活性,增加代码复杂性,一般不进行封装;

示例代码:

Deque<Integer> stack = new ArrayDeque<>();//创建一个ArrayDeque类的对象stack
stack.push(1);//入栈
stack.push(2);
stack.push(3);

System.out.println(stack.pop()); // 输出 3
System.out.println(stack.peek()); // 输出 2

3.基于底层数据结构的实现

直接通过数组或者链表实现栈,不涉及另一个类;

使用数组:

public class ArrayStack<E> {
    private Object[] elements; // 用数组存储栈元素
    private int size; // 栈的大小
使用数组来实现栈
    public ArrayStack() {
        elements = new Object[10]; // 初始容量
        size = 0;
    }

    // 入栈
    public void push(E element) {
        if (size == elements.length) {
            resize(); // 扩容
        }
        elements[size++] = element; // 将元素添加到数组末尾
    }

    // 出栈
    public E pop() {
        if (isEmpty()) {
            throw new RuntimeException("Stack is empty");
        }
        E element = (E) elements[--size]; // 返回数组的最后一个元素
        elements[size] = null; // 帮助垃圾回收
        return element;
    }

    // 查看栈顶元素
    public E peek() {
        if (isEmpty()) {
            throw new RuntimeException("Stack is empty");
        }
        return (E) elements[size - 1]; // 返回数组的最后一个元素
    }

    // 判断栈是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    // 扩容
    private void resize() {
        Object[] newElements = new Object[elements.length * 2];
        System.arraycopy(elements, 0, newElements, 0, size);
        elements = newElements;
    }
}

使用链表:

public class LinkedStack<E> {
    private static class Node<E> {
        E element;
        Node<E> next;

        Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }
    }

    private Node<E> top;
    private int size;

    public LinkedStack() {
        top = null;
        size = 0;
    }

    // 入栈
    public void push(E element) {
        top = new Node<>(element, top);
        size++;
    }

    // 出栈
    public E pop() {
        if (isEmpty()) {
            throw new RuntimeException("Stack is empty");
        }
        E element = top.element;
        top = top.next;
        size--;
        return element;
    }

    // 查看栈顶元素
    public E peek() {
        if (isEmpty()) {
            throw new RuntimeException("Stack is empty");
        }
        return top.element;
    }

    // 判断栈是否为空
    public boolean isEmpty() {
        return size == 0;
    }
}

4.使用Java的Stack类

Java 的Stack类是基于 Vector 实现的,而 Vector 是一个过时的类,存在以下问题:

  • Stack继承自 Vector ​​​​​​,而Vector 是一个动态数组,提供了许多栈不需要的方法(如随机访问、插入、删除等),破坏了栈先进后出的特性;

  • Vector 的所有方法都加了 synchronized关键字,虽保证了线程安全,但在单线程下导致性能下降;

三、相关算法题目

232.用栈实现队列(使用Deque接口的ArrayDeque实现类)

232. 用栈实现队列 - 力扣(LeetCode)

class MyQueue {
    //成员变量 接口也可以作为成员变量
    private Deque<Integer> stack1;//用于入栈
    private Deque<Integer> stack2;//用于出栈
    public MyQueue() {//构造函数
        this.stack1 = new ArrayDeque<>(); //初始化stack1
        this.stack2 = new ArrayDeque<>();//初始化stack2
    }
    //成员方法
    public void push(int x) {
        stack1.push(x);
    }
    public int pop() {
        //先判断stack2是否为空
        if(stack2.isEmpty()){
            //为空 将stack1中的元素全部压入stack2
            while(!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
            //全部压入以后 再弹出stack2的栈顶元素
        }
        //不为空 直接弹出stack2中的元素
        if(stack2.isEmpty()){
            return -1;
        }
        return stack2.pop();
        }       
    public int peek() {
        if(stack2.isEmpty()){
            while(!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.peek();        
    }    
    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }
}

四、其他细节

1.组合模式如何通过封装隐藏其他方法?

  • 封装将类的内部状态(成员变量)和方法实现隐藏起来,只通过公共方法(如 pushpoppeek)来操作类的内部状态,而不是直接访问成员变量。

  • list是 MyStack 类的私有成员变量,外部无法直接访问。

  • MyStack 类只暴露了 pushpoppeek 等栈的标准方法,而没有暴露 list 的其他方法(如 add(int index, E element)remove(int index) 等)。

  • 这样,外部调用者只能通过栈的标准接口来操作栈,而无法直接操作底层 List,从而确保栈的特性不被破坏。

2.为什么Stack类不能隐藏这些破坏栈特性的方法?

Stack 类继承自 Vector,而 Vector 提供了许多不适合栈操作的方法(如 add(int index, E element)remove(int index) 等)。由于继承关系,Stack 类会继承这些方法,导致栈的特性被破坏。

3.组合模式中创建对象的具体实现:

  • 创建 MyStack 对象:当调用 MyStack<Integer> stack = new MyStack<>(); 时,会执行 MyStack 类的构造函数,创建一个MyStack 类的对象stack;
  • 初始化list:在构造函数中,this.list = new ArrayList<>(); 将 list 初始化为一个 ArrayList 对象,new ArrayList<>() 创建了一个 ArrayList的实例,并将其赋值给list;
  • 结果:stack 对象的 list 成员变量指向一个 ArrayList对象,可以通过list调用 ArrayList 的方法(如 addremove 等);
  • stack.push(1) 实际执行的是list.add(1);

4.接口作为类的成员变量

List是接口,但接口可以作为类的成员变量类型。接口类型的变量可以指向任何实现了该接口的类的对象。

因为在 Java 中,接口是一种引用类型,它可以指向任何实现了该接口的类的对象。例如:list是List接口类型的变量,但它指向的是ArrayList类型的对象。

相关文章:

  • pycharm中配置PyQt6详细教程
  • Turborepo 使用配置
  • 深入探讨Web应用开发:从前端到后端的全栈实践
  • LLaMA-Factory|微调大语言模型初探索(4),64G显存微调13b模型
  • 苹果确认iOS 18.4四月初推出:Apple Intelligence将迎来中文支持
  • MFC开发:如何创建第一个MFC应用程序
  • 将 Vue 项目打包后部署到 Spring Boot 项目中的全面指南
  • Python在实际工作中的运用-基础操作
  • 数据库面试知识点总结
  • 口腔应用AI模型推荐
  • 论文略读:Uncovering Hidden Representations in Language Models
  • 使用IDEA提交SpringBoot项目到Gitee上
  • 算法日记25:01背包(DFS->记忆化搜索->倒叙DP->顺序DP->空间优化)
  • 组合优化问题的机器学习研究——以图匹配问题为例
  • 二叉树(中等题)
  • AI赋能传统系统:Spring AI Alibaba如何用大模型重构机票预订系统?
  • 3.3.2 交易体系构建——缠论操作思路
  • Ubuntu 下 nginx-1.24.0 源码分析 - ngx_array_push
  • MySQL后端返回给前端的时间变了(时区问题)
  • 【Qt学习】| 如何使用QVariant存储自定义类型
  • 马上评丨岂能为流量拿自己的生命开玩笑
  • 复旦大学与上海杨浦共建市东医院
  • 江西省司法厅厅长张强已任江西省委政法委分管日常工作副书记
  • 航行警告:渤海海峡黄海北部执行军事任务,禁止驶入
  • 重庆大学通报本科生发14篇SCI论文:涉事学生及其父亲被处理
  • 国家主席习近平会见斯洛伐克总理菲佐