• 【Java数据结构】详解Stack与Queue(一)


    🔒文章目录:

    1.❤️❤️前言~🥳🎉🎉🎉

    2.栈(Stack) 的概念 

    3.栈的模拟实现

    3.1顺序栈的模拟实现 

    3.2 链式栈的模拟实现

    3.3顺序栈和链式栈的区别

    4.Stack类的使用

    5.总结 


    1.❤️❤️前言~🥳🎉🎉🎉

    Hello, Hello~ 亲爱的朋友们👋👋,这里是E绵绵呀✍️✍️。

    如果你喜欢这篇文章,请别吝啬你的点赞❤️❤️和收藏📖📖。如果你对我的内容感兴趣,记得关注我👀👀以便不错过每一篇精彩。

    当然,如果在阅读中发现任何问题或疑问,我非常欢迎你在评论区留言指正🗨️🗨️。让我们共同努力,一起进步!

    加油,一起CHIN UP!💪💪

    🔗个人主页:E绵绵的博客
    📚所属专栏:

    1. JAVA知识点专栏

            深入探索JAVA的核心概念与技术细节

    2.JAVA题目练习

            实战演练,巩固JAVA编程技能

    3.c语言知识点专栏

            揭示c语言的底层逻辑与高级特性

    4.c语言题目练习

            挑战自我,提升c语言编程能力

    📘 持续更新中,敬请期待❤️❤️

    2.栈(Stack) 的概念 

    栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。


    在集合框架中,Stack(栈)是一个普通的类,具体框架图如下:


    3.栈的模拟实现

    我们能用顺序存储或链式存储来实现栈。

    采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素(也就是用数组去实现)

    采用链式存储的栈称为链式栈,通常采用双向链表实现(之所以用双向链表我们后面将会说)

     栈的方法如下,接下来我们要模拟  顺序栈和链式栈的实现

    3.1顺序栈的模拟实现 

    由于顺序栈是由顺序存储实现的,所以其底层是一个动态数组 。以下是其模拟实现的代码。


      
    1. public class ArrayStack {
    2. public int[] elem; //底层是动态数组
    3. public int usedSize;//有效数据
    4. public ArrayStack(){
    5. this.elem = new int[5];
    6. this.usedSize = 0;
    7. }
    8. //入栈
    9. public void push(int val){
    10. //如果栈满了就进行扩容,符合动态数组
    11. if(isFull()){
    12. this.elem = Arrays.copyOf(elem,2*this.elem.length);
    13. }
    14. this.elem[this.usedSize] = val;
    15. usedSize++;
    16. }
    17. //判断栈是否满
    18. public boolean isFull(){
    19. return usedSize==this.elem.length;
    20. }
    21. //出栈
    22. public int pop(){
    23. if(isEmpty()){
    24. throw new NullPointerException("该栈为空!");
    25. }
    26. int value = this.elem[usedSize-1];
    27. this.usedSize--;
    28. return value;
    29. }
    30. //判断栈是否为空
    31. public boolean isEmpty(){
    32. return this.usedSize==0;
    33. }
    34. //读取栈顶元素
    35. public int peek(){
    36. if(isEmpty()){
    37. throw new NullPointerException("该栈为空!");
    38. }
    39. return this.elem[usedSize-1];
    40. }
    41. //获取该栈元素数量
    42. public int size(){
    43. return usedSize;
    44. }
    45. }
     这样我们就实现了一个去存放整形数据的顺序栈。下图是对于该顺序栈的使用代码:
    1. public class Test {
    2. public static void main(String[] args) {
    3. ArrayStack myStack=new ArrayStack();
    4. myStack.push(4);//入栈
    5. myStack.push(3);
    6. myStack.push(2);
    7. myStack.push(1);
    8. System.out.println(myStack.size());//获取该栈元素数量
    9. System.out.println(myStack.peek());//获取栈顶元素
    10. System.out.println(myStack.pop());//出栈
    11. System.out.println(myStack.pop());
    12. System.out.println(myStack.pop());
    13. System.out.println(myStack.pop());
    14. System.out.println(myStack.isEmpty());//判断栈是否为空,如果为空返回true,否则返回false
    15. }
    16. }

    3.2 链式栈的模拟实现

    底层是链表需要满足的条件:

    先进后出
    入栈和出栈的时间复杂度为O(1)


    链表可以头插也可以尾插,那么入栈是使用头插法还是使用尾插法呢?

    假设我们是用单链表为底层:如果入栈使用尾插法,那么时间复杂度是O(n),因为尾插法每次都要找最后一个结点。
    假设:如果入栈使用头插法,那么时间复杂度是O(1);出栈的时候只需要删除头结点,时间复杂度也是O(1)
    经过推导得出,用单链表使用头插法实现栈能满足条件,但是如果非要使用尾插法呢?

    假设给一个last引用指向尾巴结点,此时入栈的时间复杂度为O(1)。
    但是出栈的时间复杂度还是O(n)。因为,虽然知道了最后一个结点,但是去掉尾巴结点后还要知道它的前一个结点,那么就要遍历单链表去找尾巴结点的前驱结点。此时时间复杂度又是O(n)。
    最终的解决办法就是用双向链表使用尾插法来实现一个栈。(双向链表可以知道一个结点的前驱结点与后继结点)。 自然双向链表也就能用头插法去实现一个栈。

    所以我们可以用单链表使用头插法去实现栈,也可以用双链表使用头插法或尾插法去实现栈 

     这里我们用双向链表使用尾插法去实现一个链式栈 :

    1. public class LinkedStack<E> {
    2. LinkedList linkedList=new LinkedList<>();
    3. public LinkedStack() {
    4. }
    5. public void push(E value){
    6. linkedList.add(value);
    7. }
    8. public E pop(){
    9. return linkedList.remove(linkedList.size()-1);
    10. }
    11. public E peek(){
    12. return linkedList.get(linkedList.size()-1);
    13. }
    14. public Boolean isEmpty(){
    15. if(linkedList.size()==0)
    16. return true;
    17. else
    18. return false;
    19. }
    20. public int size(){
    21. return linkedList.size();
    22. }
    23. }

     这样我们就用双链表使用尾插法实现了一个栈,下图是该栈的使用代码:

    1. public class Main {
    2. public static void main(String[] args) {
    3. LinkedStack<Integer> linkedStack = new LinkedStack<>();
    4. linkedStack.push(4);
    5. linkedStack.push(3);
    6. linkedStack.push(2);
    7. linkedStack.push(1);
    8. System.out.println(linkedStack.peek());//获取栈顶元素
    9. System.out.println(linkedStack.pop());//出栈
    10. System.out.println(linkedStack.pop());
    11. System.out.println(linkedStack.pop());
    12. System.out.println(linkedStack.pop());
    13. System.out.println(linkedStack.isEmpty());//判断栈是否为空,如果为空返回true,否则返回false
    14. System.out.println(linkedStack.size());//求栈中该元素的数量
    15. }}

     3.3顺序栈和链式栈的区别

    对比一下顺序栈与链式栈,它们在时间复杂度上是一样的,均为O(1)。对于空间性能,顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费的问题,但它的优势是存取时定位很方便,而链栈则要求每个元素都有指针域,这同时也增加了一些内存开销,但对于栈的长度无限制,不会存在内存空间浪费的情况。所以它们的区别和线性表中讨论的一样,如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些。

    4.Stack类的使用

    在集合框架中我们的Stack类其实就是一个顺序栈,其底层是动态数组。以下是Stack类的使用。

    1. public class Test {
    2. public static void main(String[] args) {
    3. Stack<Integer> myStack=new Stack<>();
    4. myStack.push(1);//入栈
    5. myStack.push(2);
    6. myStack.push(3);
    7. myStack.push(4);
    8. System.out.println(myStack.peek());//获取栈顶元素
    9. System.out.println(myStack.pop());//出栈
    10. System.out.println(myStack.pop());
    11. System.out.println(myStack.pop());
    12. System.out.println(myStack.pop());
    13. System.out.println(myStack.isEmpty());//判断栈是否为空,如果为空返回true,否则返回false
    14. System.out.println(myStack.size());
    15. }
    16. }

    5.总结 

    所以这篇文章我们就把栈的概念全部讲完啦!下篇文章将讲述栈的应用场景。在此,我们诚挚地邀请各位大佬们为我们点赞、关注,并在评论区留下您宝贵的意见与建议。让我们共同学习,共同进步,为知识的海洋增添更多宝贵的财富!🎉🎉🎉❤️❤️💕💕🥳👏👏👏 

  • 相关阅读:
    基于Python的信用评分卡模型建立和分析,万字详述,关注收藏
    华为OD:敏感字段加密
    Java SE 12 新增特性
    2023版 STM32实战4 滴答定时器精准延时
    《Linux运维总结:使用shell脚本一键生成Tomcat内网pfx格式证书》
    Kernel Memory 入门系列:异步管道
    SSM+基于ssm的汽车租赁平台的设计与实现 毕业设计-附源码211708
    [附源码]计算机毕业设计JAVAjsp基于web的停车收费管理系统
    C#.NET 国密SM3 HASH 哈希 与JAVA互通 ver:20230803
    瑞萨RZ/G2L处理器详细测评
  • 原文地址:https://blog.csdn.net/Easonmax/article/details/138923815