【面试官】谈谈你对顺序栈和链式栈的认识

news2024/10/8 17:07:01

思维导图

  栈(Stack)是一种数据结构,遵循后进先出(LIFO)原则。在java中Stack在java.util.Stack中。

一.常用方法的使用

1. push(E item):把元素压入栈顶。
 

代码示例:

import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
    }
}

2. pop():弹出并返回栈顶元素。 

 代码示例:

import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        int topElement = stack.pop();
        System.out.println("弹出的元素:" + topElement);
    }
}

  注意:如果栈为空,会抛出EmptyStackException异常。

3. peek():查看栈顶元素,但不弹出。

代码示例:

​
import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        stack.push(1);
        int top = stack.peek();
        System.out.println("栈顶元素:" + top);
    }
}

​

  注意:如果栈为空,会抛出EmptyStackException异常。

4. empty():判断栈是否为空,返回true或false。 

代码示例:

import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        boolean isEmpty = stack.empty();
        System.out.println("栈是否为空:" + isEmpty);
    }
}

二.细节问题

1.空栈操作

  在调用pop和peek方法前,一定要先检查栈是否为空。如果对空栈执行pop或peek操作,会抛出EmptyStackException异常。

正确写法:

​
Stack<Integer> stack = new Stack<>();
if (!stack.isEmpty()) {
    Integer topElement = stack.peek();

    // 进一步处理栈顶元素
}

​

2.同步问题

  java.util.Stack是同步的,这在多线程环境下可能会带来性能开销。如果不需要线程安全,可以考虑使用非同步的数据结构或者采取其他同步策略以提高性能。但如果错误地假设它是非同步的,在多线程环境下可能会导致数据不一致等问题。

代码示例:

// 错误示例:在多线程环境下未正确同步对栈的操作

Stack<Integer> stack = new Stack<>();
Thread t1 = new Thread(() -> {
    stack.push(1);
});
Thread t2 = new Thread(() -> {
    stack.pop();
});
t1.start();
t2.start();

3.为什么Stack是同步的?

三.栈的模拟实现

 1.顺序栈(源码实现也是顺序栈)

代码示例:

//实现类
public class ArrayStack<T> {
    private T[] stack;
    private int top;
    private int capacity;

    public ArrayStack(int capacity) {
        this.capacity = capacity;
        stack = (T[]) new Object[capacity];
        top = -1;
    }

    public boolean isEmpty() {
        return top == -1;
    }

    public boolean isFull() {
        return top == capacity - 1;
    }

    public void push(T item) {
        if (isFull()) {
            throw new RuntimeException("Stack is full");
        }
        stack[++top] = item;
    }

    public T pop() {
        if (isEmpty()) {
            return null;
        }
        return stack[top--];
    }

    public T peek() {
        if (isEmpty()) {
            return null;
        }
        return stack[top];
    }
}

//测试类
public class Test {
    public static void main(String[] args) {
        ArrayStack<Integer> stack = new ArrayStack<>(10);
        stack.push(1);
        stack.push(2);
        System.out.println(stack.isFull());
        System.out.println(stack.isEmpty());
        System.out.println(stack.pop());
        System.out.println(stack.peek());
    }
}

运行结果:

2.链式栈

代码示例:

//实现类
class StackNode<T> {
    T data;
    StackNode<T> next;

    public StackNode(T data) {
        this.data = data;
        this.next = null;
    }
}

class LinkedStack<T> {
    private StackNode<T> top;

    public LinkedStack() {
        top = null;
    }

    public boolean isEmpty() {
        return top == null;
    }

    public void push(T item) {
        StackNode<T> newNode = new StackNode<>(item);
        newNode.next = top;
        top = newNode;
    }

    public T pop() {
        if (isEmpty()) {
            return null;
        }
        T item = top.data;
        top = top.next;
        return item;
    }

    public T peek() {
        if (isEmpty()) {
            return null;
        }
        return top.data;
    }
}

//测试类
public class Main {
    public static void main(String[] args) {
        LinkedStack<Integer> stack = new LinkedStack<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);

        System.out.println("栈顶元素:" + stack.peek());
        System.out.println("弹出元素:" + stack.pop());
        System.out.println("弹出元素:" + stack.pop());

        stack.push(4);
        while (!stack.isEmpty()) {
            System.out.println("弹出元素:" + stack.pop());
        }
    }
}

使用链式栈需要注意内存管理

  如果使用自定义的链式栈,要注意内存泄漏问题。当从栈中弹出节点时,如果没有正确地处理节点的引用,可能会导致被弹出的节点无法被垃圾回收器回收,从而占用不必要的内存。

class LinkedStack<T> {
    // 需要做的事
    public T pop() {
        if (isEmpty()) {
            return null;
        }
        T item = top.data;
        StackNode<T> oldTop = top;
        top = top.next;
        // 解除对被弹出节点的引用,帮助垃圾回收
        oldTop.next = null;
        return item;
    }
    //需要做的事
}


 

四.顺序栈链式栈的优缺点

1.顺序栈

优点:

1. 简单直观:实现相对容易,操作也比较直接,易于理解和使用。

2. 随机访问:如果是基于数组实现的顺序栈,可以通过索引快速访问特定位置的元素,例如获取栈顶元素的操作非常高效。

3. 内存连续:存储在连续的内存空间中,有利于 CPU 缓存的利用,可能会提高访问速度。

缺点:

1. 固定容量:通常需要预先指定栈的大小,如果栈的大小设置不当,可能会导致空间浪费或者栈溢出。当栈的容量不够时,需要进行扩容操作,这可能涉及到创建新的数组并复制元素,比较耗时。

2. 灵活性差:难以动态地调整大小,对于一些不确定大小的应用场景可能不太适用。

二、链式栈

优点:

1. 动态大小:可以根据需要动态地增加或减少栈的大小,无需预先指定固定的容量,具有很高的灵活性。

2. 内存分配灵活:节点可以在内存中的任意位置分配,不会受到连续内存空间的限制。

缺点:

1. 额外开销:每个节点需要额外的空间来存储指向下一个节点的引用,相比顺序栈会占用更多的内存。

2. 访问效率:由于节点在内存中不一定连续存储,无法像顺序栈那样通过索引快速访问元素,访问特定位置的元素需要遍历链表,效率较低。

五.顺序栈链式栈的区别

1. 数据存储方式

   顺序栈通常使用数组来存储元素,元素在内存中是连续存储的。 链式栈使用节点(通常是自定义的类)通过链接的方式存储元素,节点在内存中可以是分散存储的。

2. 内存管理:

  顺序栈在创建时需要指定容量,可能会存在空间浪费或栈溢出的情况。如果栈满了需要扩容,可能涉及到创建新数组和复制元素的操作,比较耗时。

 链式栈可以根据需要动态地分配和释放内存,更加灵活,但每个节点需要额外的空间来存储指向下一个节点的引用。

3. 操作效率:

  顺序栈可以通过索引快速访问特定位置的元素,例如获取栈顶元素很高效。但是在插入和删除元素时,如果涉及到扩容或缩容操作,可能会比较耗时。

  链式栈在插入和删除元素(即push和pop操作)时,只需要改变指针的指向,速度较快。但是访问特定位置的元素需要遍历链表,效率较低。

4. 实现复杂度:

   顺序栈的实现相对简单,代码量较少。

链式栈的实现需要定义节点类,代码相对复杂一些。

六.顺序栈和链式栈的应用场景

顺序栈适用场景

1. 空间效率要求高

   当内存资源有限且需要尽可能节省内存空间时,顺序栈是一个较好的选择。因为它不需要为每个元素存储额外的指针,相比链式栈在存储大量小元素时可能占用更少的内存。

  例如:在嵌入式系统或者资源受限的环境中,顺序栈可以更有效地利用有限的内存资源。

2. 频繁随机访问

  如果需要经常随机访问栈中的元素,顺序栈可以通过索引快速定位到特定位置的元素。

比如在某些算法中,需要反复查看栈顶以下的特定位置的元素,顺序栈的这种特性就很有优势。

3. 已知最大容量

  当能够预先确定栈的最大容量时,顺序栈可以更高效地分配内存。因为可以在创建栈时一次性分配足够的连续内存空间,避免了后续动态分配的开销。

  如:在处理固定规模的数据集合或者具有明确边界条件的问题时,顺序栈可以提供更稳定的性能。

链式栈适用场景

1. 不确定容量

  当无法预先确定栈的大小,或者栈的大小可能动态变化很大时,链式栈更加灵活。它可以根据需要动态地分配和释放内存,不会出现栈满导致的溢出问题。

  如:在处理用户输入的动态数据或者需要处理不同规模的任务时,链式栈可以适应不确定的容量需求。

2. 频繁插入和删除

  对于频繁进行插入和删除操作的场景,链式栈的性能更好。因为在链式栈中进行插入和删除操作只需要改变指针的指向,时间复杂度为 O(1),而顺序栈在插入和删除元素时可能需要移动大量元素,尤其是在栈接近满或空的情况下。

  例如:在实现一些需要频繁调整的算法或者数据结构时,链式栈可以提供更高效的操作。

3. 多线程环境

  在多线程环境下,如果需要避免同步带来的性能开销,可以考虑使用链式栈。因为链式栈的各个节点是独立分配的,不同线程可以独立地对不同的节点进行操作,减少了线程之间的竞争。 当然在多线程环境下使用链式栈时需要注意正确的同步机制以确保数据的一致性。

在 Java 源码中某些地方使用顺序栈而不是链式栈可能有以下原因:

一、性能方面

  1. 局部性原理:顺序栈的元素存储在连续的内存空间中,更符合 CPU 的局部性原理,能够提高缓存命中率,从而在一定程度上提高访问速度。相比之下,链式栈的节点在内存中可能是分散存储的,不利于缓存的利用。

  2. 快速随机访问:如果需要随机访问栈中的元素,顺序栈可以通过索引快速定位到特定位置的元素,而链式栈需要遍历链表才能找到特定位置的元素,效率较低。

二、空间利用率

  1. 没有额外指针开销:顺序栈不需要像链式栈那样为每个节点存储指向下一个节点的指针,因此在存储相同数量的元素时,顺序栈可能占用更少的内存空间,尤其是在存储小对象时,这种优势可能更加明显。

  2. 可预测的内存占用:在某些情况下,需要对内存的使用进行更精确的控制和预测。顺序栈在创建时可以指定固定的容量,这使得内存的占用更加可预测,而链式栈的内存占用取决于动态的插入和删除操作,难以准确预估。

三、实现复杂度和稳定性

  1. 简单的实现:顺序栈的实现相对简单,代码逻辑更加清晰,容易理解和维护。对于一些对代码稳定性要求较高的场景,简单的实现可能更可靠,减少出现错误的可能性。

  2. 边界情况处理:在处理一些边界情况时,顺序栈可能更容易处理。例如,判断栈是否为空或满可以通过简单的条件判断来实现,而链式栈可能需要更多的逻辑来处理各种特殊情况。

需要注意的是,这并不意味着链式栈在 Java 中没有用武之地。在一些需要动态调整大小、对内存分配灵活性要求较高或者需要频繁进行插入和删除操作的场景中,链式栈可能更加合适。选择使用顺序栈还是链式栈取决于具体的应用需求和场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2196803.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

信息学奥赛复赛复习14-CSP-J2021-03网络连接-字符串处理、数据类型溢出、数据结构Map、find函数、substr函数

PDF文档回复:20241007 1 P7911 [CSP-J 2021] 网络连接 [题目描述] TCP/IP 协议是网络通信领域的一项重要协议。今天你的任务&#xff0c;就是尝试利用这个协议&#xff0c;还原一个简化后的网络连接场景。 在本问题中&#xff0c;计算机分为两大类&#xff1a;服务机&#x…

【AI知识点】反向传播(Backpropagation)

反向传播&#xff08;Backpropagation&#xff09; 是训练神经网络的核心算法&#xff0c;它通过反向逐层计算损失函数对每个权重的梯度&#xff0c;来反向逐层更新网络的权重&#xff0c;从而最小化损失函数。 一、反向传播的基本概念 1. 前向传播&#xff08;Forward Propag…

安装DNS

在 CentOS 7 上安装并配置 BIND 以实现 DNS 的正向和反向解析可以按照以下步骤进行&#xff1a; 安装 BIND 打开终端并运行以下命令来安装 BIND 及其工具&#xff1a; yum install bind bind-utils -y配置 BIND 编辑主配置文件&#xff1a; 使用文本编辑器打开 BIND 的主配…

双十一购物清单:这五款爆款科技好物绝不能错过!买到就是赚到!

随着一年一度的双十一购物狂欢节即将拉开帷幕&#xff0c;各大电商平台纷纷推出了一系列优惠活动&#xff0c;吸引着无数消费者的目光。对于科技爱好者而言&#xff0c;这无疑是一个绝佳的机会&#xff0c;能够以优惠的价格购得心仪的电子产品和智能设备。然而&#xff0c;在琳…

HTTPS介绍 --- 超详细保姆级知识讲解

目录 一. 认识HTTPS 二. 使用HTTPS加密的重要性 三. HTTPS的工作流程 四. 常见的加密方式 4.1 对称加密 4.2 非对称加密 五. 数据摘要 && 数据指纹 5.1 数据摘要 5.2 数据签名 六. HTTPS加密过程探究 6.1 方案一&#xff1a;只使用对…

晶体规格书及匹配测试

一、晶体参数介绍 晶体的电气规格相对比较简单,如下: 我们逐一看看每个参数, FL就是晶体的振动频率,这个晶体是24.576MHz的。 CL就是负载电容,决定了晶体频率是否准确,包括外接的实际电容、芯片的等效电容以及PCB走线的寄生电容等,核心参数。 Frequency Tolerance是…

骨传导耳机哪个牌子好?五大精选抢手骨传导耳机分享!

在数字化时代背景下&#xff0c;音乐和音频内容已经成为人们日常生活不可或缺的一部分。随着技术的发展&#xff0c;骨传导耳机凭借其独特的传输方式和健康优势&#xff0c;迅速赢得了市场和消费者的青睐。不同于传统耳机通过空气传导声音&#xff0c;骨传导耳机通过骨骼直接传…

《独自骑行与群骑之旅:探索不同的自由与氛围》

在快节奏的现代生活中&#xff0c;骑行作为一种既环保又健康的出行方式&#xff0c;越来越受到人们的青睐。然而&#xff0c;选择一个人骑车还是加入一群人的行列&#xff0c;这不仅仅是一种出行方式的选择&#xff0c;更是一种生活态度和价值观的体现。本文将探讨这两种骑行方…

【读书笔记·VLSI电路设计方法解密】问题1:什么是芯片

芯片&#xff08;集成电路或IC&#xff09;是在半导体材料的薄基底表面上制造的微型电子电路。在功能上&#xff0c;芯片是一种硬件组件&#xff0c;能够执行某些特定的功能。例如&#xff0c;一个简单的芯片可能被设计用来执行逻辑NOR&#xff08;或非&#xff09;的简单功能&…

如何在VSCode上运行C/C++代码

诸神缄默不语-个人CSDN博文目录 我是Win10&#xff0c;其他系统仅供参考。 文章目录 1. 下载所需插件2. 安装编译器3. 不借助编辑器的cpp代码执行3. 建立VSCode cpp项目3.1 c_cpp_properties.json3.2 settings.json3.3 tasks.json 4. 运行C代码参考资料 1. 下载所需插件 2. 安…

记一次N5105 NAS功耗测量

记一次N5105 NAS功耗测量 一、设备说明 HA500机器&#xff0c;N5105CPU&#xff0c;32GB内存。unraid最新6.12.13系统硬盘有一根500G M2硬盘和一个512G sata接口ssd硬盘&#xff0c;用于组成zfs raid 1&#xff0c;作为cache盘位。另外有三个4T机械硬盘&#xff0c;组成21的形…

鸿蒙应用示例:DevEco Testing 工具的常用功能及使用场景

DevEco Studio 是鸿蒙生态中的集成开发环境(IDE)&#xff0c;而 DevEco Testing 工具则是专门用于测试鸿蒙应用的强大工具。本文将详细介绍 DevEco Testing 中几个常用的测试功能及其使用场景&#xff0c;并给出相应的代码示例。 【1】安装应用 使用场景&#xff1a;在鸿蒙系统…

imx6q 的 header.s的理解

首先是 非设备树的。 就是这三个文件。 header 是配置文件。 .c 文件应该是对这个文件的操作。 那么 header 怎么生成呢? 它这里调整好的 应该参数 应该就是 这个 header.s 了。 但是 这个程序 是 设备树的 流程 ,不知道 对于 非设备树 是不是 适用。 然后是设备树的。 设…

基于Arduino的遥控自平衡小车

基于Arduino的遥控自平衡小车 一、项目简介二、所需材料三、理论支持四、外壳设计五、线路连接六、检查MPU6050连接七、烧录库八、PID控制设置九、设置传感器参数十、无线移动控制十一、超声波模块 一、项目简介 一个使用Arduino Nano、MPU-6050以及便宜的6伏直流齿轮电机的自…

Vue中使用富文本编辑框的实践与探索

在Web开发中&#xff0c;富文本编辑框是一个常见的功能。本文将介绍如何在Vue项目中集成和使用富文本编辑框&#xff0c;并分享一些实践经验。 一、为什么需要富文本编辑框 在开发网站、博客、论坛等应用时&#xff0c;用户往往需要编辑和发布带格式的文本内容。传统的文本输…

分布式事务seata AT和XA性能对比

1. AT模式和XA模式性能对比&#xff1a; AT的阻塞是阻塞在了业务服务层&#xff0c;全局锁。 而XA模式是阻塞在了数据库&#xff0c;对数据库的性能影响很大。 肯定是优选AT&#xff0c;可以提升数据库的性能。 &#xff08;由于AT模式数据库事务阻塞小&#xff0c;那么同一…

公司监控电脑都能监控哪些信息?深刻回答,一文详解!

在当今数字化办公环境中&#xff0c;公司监控电脑已成为许多企业保障信息安全、提升工作效率的重要手段。 然而&#xff0c;这种监控行为也引发了关于员工隐私权的广泛讨论。 本文将深入探讨公司监控电脑所能监控的信息范围&#xff0c;以及如何在保障企业安全与尊重员工隐私…

omron fins 内存区域写入(MEMORY AREA WRITE)

1. 完整的代码如下&#xff1a; import socket import binasciiclass Omron:def __init__(self, ip, port9600):self.ip ip # PLC的IP地址self.port port # PLC的端口&#xff0c;默认为9600def send_receive_fins(self):with socket.socket(socket.AF_INET, socket.SOCK_…

某个应用的CPU使用率居然达到100%,我该怎么办?

摘至https://learn.lianglianglee.com/ CPU使用率 Linux 作为一个多任务操作系统&#xff0c;将每个 CPU 的时间划分为很短的时间片&#xff0c;再通过调度器轮流分配给各个任务使用&#xff0c;因此造成多任务同时运行的错觉。 为了维护 CPU 时间&#xff0c;Linux 通过事先定…

使用标签实现MyBatis的基础操作

目录 前言 1.配置MyBatis⽇志打印 2.参数传递 2.1 #{} 和 ${}区别 2.2传递多个参数 3.增删改查 3.1增(Insert) 3.2删(Delete) 3.3改(Update) 3.4查(Select) 前言 接下来我们会使用的数据表如下&#xff1a; 对应的实体类为&#xff1a;UserInfoMapper 所有的准备工作都…