0

0

java代码如何用链表实现链式栈 java代码链式栈结构的基础编写技巧​

星夢妙者

星夢妙者

发布时间:2025-08-11 23:45:01

|

756人浏览过

|

来源于php中文网

原创

链式栈的核心实现是通过单向链表在头部进行所有操作以满足lifo特性,1. 节点类包含数据和指向下一节点的引用;2. 栈类维护top指针和size计数器;3. push操作将新节点置为新的栈顶;4. pop操作移除并返回栈顶元素,需检查空栈;5. peek操作返回栈顶元素但不移除;6. 所有基本操作均为o(1)时间复杂度;7. 链式栈优势在于动态扩容,避免栈满问题,适用于元素数量不确定的场景;8. 缺点是每个节点额外占用指针内存,可能导致较高内存开销和碎片化;9. 常见错误包括未处理空栈导致nullpointerexception、top指针更新错误、size变量维护不一致及泛型使用不当;10. 实际应用包括撤销重做功能、表达式求值、浏览器历史记录、深度优先搜索和函数调用栈等,其动态性和高效操作使其在多种算法和系统机制中具有重要价值。

java代码如何用链表实现链式栈 java代码链式栈结构的基础编写技巧​

在Java中,用链表实现链式栈,核心在于利用链表的动态特性来模拟栈的“后进先出”(LIFO)行为。这意味着我们不再受限于固定大小的数组,可以根据需要自由地增减元素。本质上,它是一个单向链表,所有的操作——入栈(push)、出栈(pop)、查看栈顶(peek)——都集中在链表的头部进行,因为这样效率最高,也最符合栈的逻辑。

解决方案

要构建一个链式栈,我们需要两个基本组件:一个表示栈中元素的“节点”类,以及一个管理这些节点的“栈”类。

节点(Node)类

立即学习Java免费学习笔记(深入)”;

这是链式栈的基石。每个节点都包含两部分信息:实际存储的数据,以及一个指向下一个节点的引用。

class Node {
    T data;         // 存储的数据
    Node next;   // 指向下一个节点的引用

    public Node(T data) {
        this.data = data;
        this.next = null; // 新节点默认不指向任何地方
    }
}

链式栈(LinkedStack)类

这个类将负责封装栈的所有操作。我们需要一个

top
引用来始终指向栈的顶部元素,以及一个
size
变量来跟踪栈中元素的数量,这在某些场景下会很有用。

public class LinkedStack {
    private Node top; // 栈顶元素,所有操作都围绕它进行
    private int size;    // 栈中元素的数量

    public LinkedStack() {
        this.top = null; // 初始时栈是空的
        this.size = 0;
    }

    /**
     * 入栈操作:将元素添加到栈顶。
     * 这是一个O(1)操作,非常高效。
     */
    public void push(T item) {
        Node newNode = new Node<>(item);
        newNode.next = top; // 新节点指向当前的栈顶
        top = newNode;      // 新节点成为新的栈顶
        size++;
        // 思考一下,这种操作方式,新元素总是在最前面,这正是栈的LIFO特性所需要的。
    }

    /**
     * 出栈操作:移除并返回栈顶元素。
     * 同样是O(1)操作。
     */
    public T pop() {
        if (isEmpty()) {
            // 必须处理栈空的情况,否则会遇到NullPointerException。
            // 抛出异常是比较标准的做法,告知调用者栈无法执行此操作。
            throw new IllegalStateException("Stack is empty, cannot pop.");
        }
        T data = top.data; // 获取栈顶数据
        top = top.next;    // 栈顶下移,指向下一个节点
        size--;
        return data;
    }

    /**
     * 查看栈顶元素:返回栈顶元素但不移除它。
     * O(1)操作。
     */
    public T peek() {
        if (isEmpty()) {
            // 同样需要检查栈是否为空。
            throw new IllegalStateException("Stack is empty, cannot peek.");
        }
        return top.data; // 直接返回栈顶数据
    }

    /**
     * 判断栈是否为空。
     */
    public boolean isEmpty() {
        return top == null; // 或者 return size == 0; 效果一样,但top == null更直观地反映结构状态。
    }

    /**
     * 返回栈中元素的数量。
     */
    public int size() {
        return size;
    }

    // 可以在这里添加一个简单的main方法进行测试
    public static void main(String[] args) {
        LinkedStack myStack = new LinkedStack<>();
        System.out.println("Is stack empty? " + myStack.isEmpty()); // true

        myStack.push("Apple");
        myStack.push("Banana");
        myStack.push("Cherry");

        System.out.println("Stack size: " + myStack.size()); // 3
        System.out.println("Top element: " + myStack.peek()); // Cherry

        System.out.println("Popped: " + myStack.pop()); // Cherry
        System.out.println("Stack size after pop: " + myStack.size()); // 2
        System.out.println("New top element: " + myStack.peek()); // Banana

        myStack.push("Date");
        System.out.println("Top element after push: " + myStack.peek()); // Date

        while (!myStack.isEmpty()) {
            System.out.println("Popping: " + myStack.pop());
        }
        System.out.println("Is stack empty now? " + myStack.isEmpty()); // true

        try {
            myStack.pop(); // This should throw an exception
        } catch (IllegalStateException e) {
            System.out.println("Caught expected error: " + e.getMessage());
        }
    }
}

链式栈与数组栈:何时选择链式实现?

在选择栈的底层实现时,我们通常会在链式栈和基于数组的栈(比如Java的

ArrayDeque
Stack
类内部)之间做权衡。链式栈最显著的优势在于其动态扩容的能力。它不需要预先设定容量,理论上只要内存允许,就可以无限地添加元素。这意味着你永远不会遇到“栈满”导致的操作失败,这对于那些元素数量难以预测的应用场景来说,简直是福音。

另一方面,数组栈在特定情况下可能会更快,尤其是在访问元素时(因为数组是连续内存),但它最大的痛点在于固定容量。一旦达到容量上限,就需要进行扩容操作,这通常涉及到创建一个更大的新数组并将所有旧元素复制过去,这个过程的开销是O(N)级别的,虽然不常发生,但一旦发生,会带来性能上的瞬时抖动。

链式栈的每个操作(push、pop、peek)都是O(1)的,因为它只涉及对

top
引用和几个指针的修改,与栈中元素的总数无关。不过,链式栈的缺点在于内存开销。每个节点除了存储数据本身,还需要额外存储一个指向下一个节点的引用。这意味着每个元素会比数组栈多占用一些内存(通常是8字节或更多,取决于系统架构),这在存储大量小对象时可能会累积成可观的开销。而且,链式存储可能导致内存碎片化,尽管现代JVM的垃圾回收器在这方面做得很好,但这也是其与数组连续内存特性不同之处。因此,如果你对内存使用非常敏感,或者栈的规模相对固定且不大,数组栈可能更优;而如果追求极致的动态性,或者栈的深度变化莫测,链式栈的优势就凸显出来了。

实现链式栈时常见的错误与陷阱有哪些?

在编写链式栈时,一些常见的错误和陷阱可能会导致程序行为异常,甚至崩溃。

1. 空栈操作未处理(NullPointerException)

这是最常见也最危险的错误。当你尝试从一个空栈中

pop()
peek()
时,
top
引用将是
null
。如果此时不进行检查直接访问
top.data
top.next
,就会立即抛出
NullPointerException
。正确的做法是在这些方法开始时,先用
if (isEmpty())
进行判断,并根据业务需求选择返回
null
、抛出
IllegalStateException
或其他自定义异常。在我的示例代码中,我选择了抛出
IllegalStateException
,这是一种明确告知调用者操作不合法的强硬方式。

2.

top
引用更新错误

push
pop
操作的核心就是正确地更新
top
引用。

  • push
    时,新节点应该指向当前的
    top
    ,然后新节点才成为新的
    top
    。顺序错了,链就断了。
  • pop
    时,
    top
    应该指向
    top.next
    。如果忘记了这一步,或者错误地指向了其他地方,就会导致栈的逻辑混乱,可能出现无限循环或者数据丢失

3.

size
变量维护不一致

文心快码
文心快码

文心快码(Comate)是百度推出的一款AI辅助编程工具

下载

虽然不是核心功能,但

size
变量对于获取栈的当前大小非常有用。如果每次
push
时没有
size++
,或者每次
pop
时没有
size--
,那么
size()
方法返回的值就会不准确。这看似小问题,但在依赖栈大小进行逻辑判断或资源分配的场景中,可能引发难以追踪的bug。养成每次修改栈元素数量时同步更新
size
的好习惯。

4. 泛型使用不当(Java特有)

在Java中,使用泛型

LinkedStack
可以确保栈能够存储任何类型的对象,并提供编译时类型安全。如果在定义
Node
LinkedStack
时没有正确使用泛型,或者在实例化时混淆了类型,可能会导致
ClassCastException
(运行时错误)或类型不匹配的编译错误

这些错误往往都围绕着对

top
引用和链表结构的理解是否透彻。画图是避免这些错误最有效的方法之一,模拟每一步操作后
top
和各个节点的
next
指针的变化,能帮助你清晰地看到逻辑上的漏洞。

链式栈在实际编程中有哪些应用场景?

链式栈作为一种基础数据结构,其“后进先出”的特性使其在许多实际编程问题中都扮演着关键角色。

1. 撤销/重做(Undo/Redo)功能

这是最直观的应用之一。在文本编辑器、图形设计软件等应用中,用户的每一次操作(比如输入一个字符、移动一个对象)都可以被看作是一个“事件”并被压入一个“操作栈”。当用户点击“撤销”时,就从栈顶弹出一个操作并将其反向执行。如果需要“重做”功能,可以再维护一个“重做栈”,将撤销的操作压入其中。

2. 表达式求值与语法分析

在编译器或解释器中,栈常用于处理算术表达式(如中缀表达式转后缀表达式,然后求值)和进行语法分析。例如,在将中缀表达式转换为后缀表达式时,运算符的优先级和括号的匹配都需要栈来辅助判断和存储。

3. 浏览器历史记录(后退/前进)

虽然不完全是链式栈的直接实现,但浏览器“后退”按钮的功能逻辑与栈高度相似。每访问一个新页面,就将其URL压入一个栈。点击“后退”时,弹出当前页面,并加载栈顶的URL。类似地,“前进”功能也可以用另一个栈来辅助实现。

4. 深度优先搜索(DFS)

在图和树的遍历中,深度优先搜索算法常常使用栈(或递归,递归本质上也是利用了系统调用栈)来管理待访问的节点。当访问一个节点时,将其邻居节点压入栈中,然后从栈中弹出一个节点继续访问,直到栈为空。

5. 函数调用栈(Call Stack)

这是操作系统和编程语言运行时环境的核心机制。当一个函数被调用时,它的局部变量、参数和返回地址等信息会被压入一个“调用栈”(Call Stack)。当函数执行完毕返回时,这些信息就会从栈中弹出。递归函数的实现也正是基于这种机制,每次递归调用都会在调用栈上创建一个新的栈帧。

链式栈的动态性和操作的常数时间复杂度,使其成为这些场景下非常合适的选择。它在处理那些需要灵活管理顺序、且操作集中在末尾(或开头)的数据流时,展现出强大的实用价值。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

825

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

724

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

728

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

395

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

398

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

445

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

428

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16861

2023.08.03

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

7

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Git 教程
Git 教程

共21课时 | 2.3万人学习

Kotlin 教程
Kotlin 教程

共23课时 | 2.1万人学习

PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号