• 栈和队列


    声明:本文主要作为作者的复习笔记,由于作者水平有限,难免有错误和不准确之处,欢迎读者批评指正。

    1. 线性表接口两个常用子类

    ArrayList => 底层是基于动态数组的实现
    LinkedList => 底层是基于双向链表的实现

    • LinkedList是队列Queue的子类,还是双端队列Deque的子类;
    • LinkedList有一个反向遍历的迭代器,从链表的尾部开始遍历;也有一个正向的迭代器,从链表的头部开始遍历。
    • 使用List接口的方法都一样,都是List接口中定义好的方法;

    2. 什么时候选择ArrayList,什么时候选择LinkedList?

    不同点ArrayListLinkedList
    存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
    随机访问支持;O(1)不支持;O(N)
    头插需要搬移元素,效率低O(N)只需修改引用的指向,时间复杂度为O(1)
    插入空间不够时需要扩容没有容量的概念
    应用场景元素高效存储+频繁访问任意位置插入和删除(频繁)

    若ArrayList和LinkedList都默认调用add方法进行插入,这两个结构都在进行尾插;其实数组的尾插比链表还快elementData[size++] = val;

    3. 栈和队列的关系

    栈和队列都是添加和删除元素操作受限的线性表,本质上栈和队列没什么不同,可以相互转换,都是线性表的一种;

    栈:树型结构的dfs(深度优先遍历 - 前中后序)

    队列:树型结构的bfs(广度优先遍历 - 层序遍历)

    应用场景
    可以使用栈来模拟队列,同样的,也可以使用队列来模拟栈的实现;

    4. 栈

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

    • 入栈(push)
      栈的插入操作叫做进栈/压栈/入栈;入数据在栈顶。
    • 出栈(pop)
      栈的删除操作叫做出栈;出数据在栈顶。
    • 无论入栈还是出栈,都只能固定在栈顶进行。
    • 查看栈顶元素peek():只取值,不删除。
      入栈出栈

    5. 栈的实现根据使用的内部结构不同分为顺序栈和链式栈

    1. 顺序栈:内部还是动态数组,规定只能从数组的末尾进行插入和删除;
      插入 => 入栈
      删除 => 出栈
    2. 链式栈:内部使用链表,默认规定在链表的尾部进行插入/删除;

    6. 栈、虚拟机栈、栈帧有什么区别

    1. 栈数据结构的栈:线性表的一种,满足后进先出的特点(LIFO),保存在栈中的元素都是相同数据类型;
    2. 虚拟机栈,操作系统栈:概念性的东西,底层到底是不是用的栈(数据结构),到底用的链式栈还是顺序栈都不是关键;
    3. 栈帧:函数从调用过程到结束的一个体现,一个函数从调用到销毁其中占用的空间,内部的局部变量都统一放在该函数的栈帧中(一种类型,专门描述函数在操作系统内部的调用过程);

    7. 队列(FIFO:First In First Out)

    操作受限的线性表,只能从队列的一端添加元素(队尾),从队列的另一端删除元素(队首);

    队列

    • 入队:向队列中添加元素(offer | push);
    • 出队:从队列中移除元素(poll);

    8. 广义上的队列一般有以下几种

    1. 普通队列(FIFO链式队列)— 基于链表实现(单链表足够,尾插头删);
    2. 循环队列,可以从队尾走到队首,使用定长数组来实现;
    3. 优先级队列(非线性结构),一般都是先进先出,但是,优先级较高的元素可以优先出队;内部使用堆这个数据结构来实现(JDK默认使用的是最小堆 — 完全二叉树);

    9. 循环队列

    循环队列使用定长数组来实现,数组的首尾相连就构成了循环队列(就是走到数组最后一个元素时,下一个访问的就是数组的首地址);循环队列一般用在os生存消费者模型。

    10. 循环队列判断是否为空,判断队列是否已满

    判断循环队列是否为空:head == tail

    判断循环队列是否已满:(tail + 1)% num.length == head;

    • head索引指向队列队首第一个元素的下标,tail索引指向队列最后一个元素的下一个位置下标(新元素就可以使用tail直接入队列);
    • 无论循环队列是空还是已满,head == tail重合了;因此为了区分循环队列到底为空还是已满,循环队列会浪费一个空间;
      例:要保存一个最大支持5个元素的循环队列,则开辟一个长度为6的数组!多余的这个空间就是来判断队列是否已满。

    11. 无论是入队还是出队,在循环队列中,下标后移都需要进行取模操作

    • 入队:offer
      tail继续向后走,tail = (tail + 1)% num.length;
    • 出队:poll
      head继续向后走,head = (head + 1)% num.length;

    12. 循环队列不断入队和出队中,有效元素以及浪费的空间都在动态变化

    在循环队列中,浪费空间的位置不是固定的,每次(tail + 1)% num.length到head之前的那个空间是浪费的。

    在任意时刻
    int lastIndex = tail == 0 ?num.length - 1 : tail - 1;
    队列中的有效元素 [head…lastIndex];

    13. 双端队列(Deque)

    双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque是“double ended queue”的简称;

    • 元素可以从队头出队和入队,也可以从队尾出队和入队;
    • 在Java中,只要用栈和队列,统一使用Deque接口!根据实际使用时的场景选择基于数组实现的ArrayDeque或者基于双向链表实现的LinkedList;

    Deque接口的使用

    //使用Deque作为栈,方法名都不变 push pop peek
    Deque<Integer> stack = new LinkedList<>();
    stack.push(1);
    stack.push(3);
    stack.push(5);
    stack.push(7);
    System.out.println(stack);
    //输出7
    System.out.println(stack.pop());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    //使用Deque作为队列,方法名不变 offer poll peek
    Deque<Integer> queue = new ArrayDeque<>();
    queue.offer(2);
    queue.offer(4);
    queue.offer(6);
    queue.offer(8);
    System.out.println(queue);
    //输出2
    System.out.println(queue.poll());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    14. 常见的线性表:元素之间呈线性排列,一次只能添加一个同类型的元素;

    数组,链表,栈,队列,字符串(char[ ] 多个字符呈线性排列);

  • 相关阅读:
    30个必知必会React面试题汇总,附答案!
    JVS低代码权限管控方式
    HCNP Routing&Switching之组播技术-组播分发
    浅析建造者模式
    position属性练习:画一个爱心送给你。
    中能融合携手天翼云打造“能源大脑”
    掌握Three.js:学习路线,成为3D可视化开发的高手!
    Spring基础3——AOP,事务管理
    有没有什么大学生搜题比较好用的网站或APP
    python实验16_网络爬虫
  • 原文地址:https://blog.csdn.net/happy_life6/article/details/127984062