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> {
    T data;         // 存储的数据
    Node<T> next;   // 指向下一个节点的引用

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

链式栈(LinkedStack)类

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

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

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

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

    /**
     * 入栈操作:将元素添加到栈顶。
     * 这是一个O(1)操作,非常高效。
     */
    public void push(T item) {
        Node<T> 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<String> 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
变量维护不一致

Nanonets
Nanonets

基于AI的自学习OCR文档处理,自动捕获文档数据

下载

虽然不是核心功能,但

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

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

在Java中,使用泛型

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

这些错误往往都围绕着对

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

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

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

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

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

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

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

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

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

4. 深度优先搜索(DFS)

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

5. 函数调用栈(Call Stack)

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

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

254

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1089

2024.03.01

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1567

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

241

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

150

2025.10.17

if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

847

2023.08.22

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

550

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

30

2025.12.22

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

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

精品课程

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

共23课时 | 4.4万人学习

PostgreSQL 教程
PostgreSQL 教程

共48课时 | 10.6万人学习

C 教程
C 教程

共75课时 | 5.4万人学习

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

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