数组&链表&队列&栈

259 阅读4分钟

🔥 四种数据结构中,链表是较为复杂的。

数组

概念&特性

  1. 概念(数据结构意义上的):一种线性表数据结构,用一组连续的内存空间存储一组具有相同类型的数据。

  2. 特性

    • 只有前后两个方向
    • 随机访问
  3. 一维数组寻址公式

    a[i]_address=base_address+idata_type_sizea[i]\_address = base\_address + i * data\_type\_size

  4. javascript 中的数组

    • 数组被改的面目全非了,与数据结构中的数组行为不一致。
    • 不一定连续,支持变长数组
    • 提供 ArrayBuffer ,这种数据结构符合标准数据结构的定义

时间复杂度

  1. 复杂度:根据下标随机访问元素的时间复杂度是O(1),插入删除的平均复杂度为 O(n)

  2. 插入、删除比较低效,因为数组的连续性

  3. 不考虑排序的话,插入指定位置,可以将时间复杂度降为 O(1)

    不大规模移动,只将指定位置的元素放到队尾,新元素再放到该指定位置

  4. 🔥 删除的操作可以将多次删除操作一起执行,删除效率较高

    标记清除

总结&思考

  1. 为什么数组下标从 0 开始?

  2. 数组暴露的缺点

    • 数组需要一块连续的内存空间来存储,对内存要求比较高。当内存中没有连续的、足够大的存储空间时,会申请失败。

🔥 链表

应用

  1. LRU缓存淘汰算法

    常见的淘汰算法:先进先出(FIFO), 最少使用(LFY), 最近最少使用(LRU)

  2. java LinkedHashMap 用到了双向链表

  3. 约瑟夫问题 循环链表

概念&特性

  1. 存储空间上不连续,利用指针(引用)来表示顺序

时间复杂度

  1. 访问第 k 个数据的时间复杂度为 O(n)

  2. 插入,删除操作(已知前驱节点的情况下)的时间复杂度为 O(1), 但需要先进行遍历查找其前驱节点。

  3. 双向链表可以解决插入、删除,需要遍历找出前驱节点的问题。将时间复杂度降低为 O(1)

链表的变形结构

  1. 循环链表

  2. 双向链表

  3. 双向循环链表

链表的奇淫技巧

  1. 理解指针和应用

  2. 警惕指针丢失和内存泄漏

  3. 🍅 利用 "哨兵" 简化代码

  4. 🍅 留意边界情况和特殊条件

    • 链表空时
    • 链表只有一个节点时
    • 链表只有两个节点时
    • 要处理的节点是头节点、尾节点时
  5. 画图

  6. 🍎 多多练习

    • 单链表反转
    • 链表中环的检测
    • 两个有序的链表合并
    • 删除链表倒数第 k 个节点
    • 寻找链表中的中间节点
    • LRU算法实现

思考&总结

  1. 双向链表空间换时间

  2. 链表 VS 数组

  • 数组扩容麻烦,链表天然支持动态扩容

  • 数组适合内存紧缺的开发场景

  • 链表容易产生内存碎片

知识

  1. LRU(Least recently used)缓存淘汰算法

    根据数据的历史访问记录来进行淘汰数据,其核心思想是如果数据最近被访问过,那么将来被访问的几率也更高

训练题目

  1. 有红苹果标记的

  2. 基础的链表

应用场景

  1. 浏览器前进后退

  2. 函数的调用栈

  3. 🍎 表达式求值

概念&特性

  1. 顺序栈数组实现
  2. 链式栈链表实现

时间复杂度

  1. 出栈、入栈都是 O(1)

  2. 🍎 动态扩容的入栈,最好 O(1) 最坏O(n) 均摊O(1)

训练题目

  1. 红苹果

  2. 括号匹配

  3. 链式栈

  4. 顺序栈

队列

概念&特性

  1. 先进先出
  2. 顺序队列数组实现
  3. 链式队列链表实现

时间复杂度

有一个问题需要注意,连个指针(tail, head)不断后移的过程中,tail移到最后,就没法入队了,此时需要,向前移动。

  1. 每次出队列都移动 O(n)

  2. tail 在最右时整体移动 均摊O(1)

🍎 🔥 循环队列

  1. 关键:确定队列空和队列满的判定条件

  2. 当队列满的时候 head 和 tail 关系

    (tail+1)%n=head(tail + 1)\%n=head

  3. tail 指针指向的位置,不存数据

阻塞队列和并发队列

  1. 阻塞队列实现 "生产者-消费者模型"

  2. 线程安全的队列又叫并发队列 上锁

  3. 基于数组的循环队列,利用 CAS 原子操作,可以实现非常高效的无锁并发队列,这也是循环队列比链式队列应用更广泛的原因

问题&思考

  1. 问题:当项固定大小的线程池请求一个线程时,如果线程池没有空闲资源,这个时候线程池如何处理这个请求

    • 直接拒绝
    • 阻塞队列 (数组实现有界排队队列,链表实现一个支持无界的排队队列)

🍎 练习

  1. 顺序队列
  2. 链式队列
  3. 循环队列