【复习】数据结构

观前注意:
本博客为作者复习时根据《你好,算法》,以自己对数据结构与算法的理解简记而成,概念定义并不保证完全标准。有异议以权威资料为准。
本文属于【数据结构】篇。

一、前序部分

  1. 复杂度分析:描述了随着输入数据大小的增加,算法所需时间空间的增长趋势。
  • 时间复杂度推算方法:
    • 统计操作数量T(n)
    • 取T(n)最高项
  • 空间复杂度推算方法:
    • 以最差输入数据为准
    • 以算法运行中的峰值内存为准
  1. 迭代:程序在满足一定条件下重复执行某段代码,直至不再满足。自下而上

    • for循环:预先知道迭代次数。
    • while循环:每轮先检查条件。自由度更高
  2. 递归:通过调用函数自身来解决问题。自上而下

    • 递:不断深入地调用自身,通常传入更小/更简化的参数,直至“终止条件”
    • 归:触发“终止条件”后,从最深层逐层返回,汇聚每层结果

    【递归三要素】

    • 终止条件(最先写)
    • 本轮要做的事(只干本轮,不想下层)
    • 递归调用(调用自己)
  • 普通递归:在“归”时求和,递归不是最后一步,后面还有其他运算操作
  • 尾递归:在“递”时求和,递归是最后一步
  • 递归树:一个调用产生两个调用分支,最终形成一棵树
  1. 数据结构

    • 逻辑结构:数据元素间的逻辑关系(线、非线(树/网)),组织方式

    • 物理结构:通过内存地址访问目标位置的数据,内容类型

      【逻辑上有关系,物理上可以查】

      所有数据结构都是基于数组、链表或二者结合实现的

  2. 基本数据类型:CPU可以直接进行运算的类型,以二进制形式存储,一个二进制位为一个比特

    一字节(byte)=八比特(bit),可以表示28 (256)个数字(XXXX XXXX)

    • 整型、浮点型、字符型、布尔型
  3. 数字编码(兼容负数和正负0运算的演进过程)

    • 原码:二进制最高位为符号位(0正1负)

    • 反码:正数反码与其原码一致;负数反码除最符号位,其余在原码基础上取反(解决负数原码不能直接计算的问题)

    • 补码:正数补码与其原码一致;负数补码是在其反码基础上加1(解决正负0的歧义)

      计算机中,数字以补码存储

二、数组与链表

  1. 数组:将相同类型的元素存储在连续的内存空间中。

    • 地址计算公式:元素内存地址=数组内存地址+元素长度×元素索引

      索引本质:内存地址的偏移量

  • 操作:

    插入:将元素依次后移k位(初始尾元素会丢失)i=i-1 ,O(n)

    删除:将元素依次迁移k位(删后尾元素无意义)i=i+1 ,O(n)

    扩容:创建更大的数组,将原数组复制过去,O(n)

  • 优点:

    • 空间效率高(连续紧凑)
    • 随机访问
    • 缓存局部,提高后续操作速度
  • 缺点:

    • 插删时间复杂度高
    • 空间不可变,需扩容,开销大
    • 空间可能浪费
  1. 链表:每个元素都是一个节点对象,之间用“引用”连接。各个节点可以分散存储在内存各处
/* 链表节点类 */
class ListNode {
int val;
// 节点值
ListNode next; // 指向下一节点的引用
ListNode(int x) { val = x; } // 构造函数
}

相比数组,链表节点需要占用更多的内存空间。

  • 操作:

    插删:O(1)

    查:O(n)

  1. 列表:基于数组/链表实现

    实际上,许多编程语言中的标准库提供的列表是基于动态数组实现的,例如Python中的ArrayList 、C++ 中的vector 和 C#中的list 、Java 中的List 等。在接下来的讨论中,我们将把“列表”和“动态数组”视为等同的概念。

    • 操作:

      增:add(val) addAll(list)

      删:remove(index) clear()

      改:set(index,val)

      查:get(index)

      排序:sort(list)

  2. 内存与缓存(物理结构在很大程度上决定了程序对内存和缓存的使用效率)

    计算机中包括三种类型的存储设备硬盘内存(RAM)、缓存(cache memory)。硬盘用于长期存储大量数据,内存用于临时存储程序运行中正在处理的数据,而缓存则用于存储
    经常访问的数据和指令。

    数组具有更高的缓存命中率,因此它在操作效率上通常优于链表。在选择数据结构时,应根据具体需求和场景做出恰当选择。

三、栈与队列

  1. 栈:先到后得

    • 组成:

      • 栈顶:后来的
      • 栈底:先来的
    • 操作:

      入栈:添加到栈顶。push(val)

      出栈:将栈顶弹出。pop()

      访问栈顶:peek()

      获取大小:size()

      是否为空:isEmpty()

  2. 队列:先到先得

    • 组成:

      • 队首:先来的
      • 队尾:后来的
    • 操作:

      入队:入队尾。offer(val)

      出队:队首出。poll()

      访问队首:peek()

      获取大小:size()

      是否为空:isEmpty()

四、哈希表

哈希表(hash table)又称散列表,通过简历key和value的映射关系,实现高效查询。

本质:数组+链表/红黑树

  • 操作:

    • 增删查:O(1)。put(k,v) remove(k) get(k)
    • 遍历:通过键值对、键、值。entrySet() keySet() values()
  • 哈希冲突

    哈希函数的作用是将所有key构成的输入空间映射到数组所有索引构成的输出空间,而输入空间往往远大于输出空间。因此,理论上一定存在“多个输入对应相同输出”的情况。

    key是唯一的,只有一个对应值,所以可根据key来查找值

    • 解决:

      1. 改良哈希表数据结构,使得哈希表可以在出现哈希冲突时正常工作。

        • 链式地址

          值得注意的是,当链表很长时,查询效率𝑂(𝑛)很差。此时可以将链表转换为“AVL树”或“红黑树”,从而将查询操作的时间复杂度优化至𝑂(log𝑛)。

        • 开放寻址

          开放寻址不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测平方探测多次哈希等。

      2. 仅在必要时,即当哈希冲突比较严重时,才执行扩容操作。

        java中HashMap的数组长度(capacity)永远是2n

        扩容:表大小(size)>= 0.75 * 数组长度(capacity)

    • 各编程语言采取了不同的哈希表实现策略,下面举几个例子。

      • Python采用开放寻址。字典dict使用伪随机数进行探测。
      • Java采用链式地址。自JDK1.8以来,当HashMap 内数组长度达到64且链表长度达到8时,链表会转换为红黑树以提升查找性能。
      • Go采用链式地址。Go规定每个桶最多存储8个键值对,超出容量则连接一个溢出桶;当溢出桶过多时,会执行一次特殊的等量扩容操作,以确保性能。
  • 哈希算法

    键值对的分布情况由哈希函数决定

    • 目标:

      • 确定性

      • 高效性

      • 均匀分布

五、树和堆

  1. 树:

    二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子节点引用。体现了“一分为二”

    的分治逻辑。

/* 二叉树节点类 */
class TreeNode {
int val;
// 节点值
TreeNode left; // 左子节点引用
TreeNode right; // 右子节点引用
TreeNode(int x) { val = x; }
}
  • 常用术语:
    • 根节点(rootnode):位于二叉树顶层的节点,没有父节点。
    • 叶节点(leafnode):没有子节点的节点,其两个指针均指向None 。
    • 边(edge):连接两个节点的线段,即节点引用(指针)。
    • 节点所在的层(level):从顶至底递增,根节点所在层为1。
    • 节点的度(degree):节点的子节点的数量。在二叉树中,度的取值范围是0、1、2。
    • 二叉树的高度(height):从根节点到最远叶节点所经过的边的数量。
    • 节点的深度(depth):从根节点到该节点所经过的边的数量。
    • 节点的高度(height):从距离该节点最远的叶节点到该节点所经过的边的数量。
  • 二叉树类型:

    • 满/完美二叉树:所有层的节点都被填满

    • 完全二叉树:只有底层未满且靠左填(适合用数组表示

    • 平衡二叉树:|左子树高度- 右子树高度|<=1

    • 二叉搜索树(BST):左树节点值<根<右

    • AVL树:严格平衡的BST。适合查询,但由于左右旋复杂(换根,调整子节点归属),不适合插删。

      节点平衡因子:节点左子树的高度 - 右子树的高度,

    • 红黑树:较宽松的AVL树。适合高频增删。

  • 遍历:

    • 广度优先:层序遍历(借助队列和数组列表)
    • 深度优先:前中后序遍历(递归)
      • 前:根左右
      • 中:左根右
      • 后:左右根
  1. 堆:满足特定条件的完全二叉树
  • 类型:
    • 大顶堆:任意节点值 >= 其子节点值。根大
    • 小顶堆:任意节点值 <= 其子节点值。根小

六、图

由顶点和边组成。或说节点和引用(可以看成一种从链表扩展而来的数据结构)

  • 常用术语:

    • 邻接(adjacency):当两顶点之间存在边相连时,称这两顶点“邻接”。

    • 路径(path):从顶点A到顶点B经过的边构成的序列被称为从A到B的“路径”。

    • 度(degree):一个顶点拥有的边数。

      对于有向图,入度表示有多少条边指向该顶点,出度表示有多少条边从该顶点指出。

  • 图的表示:

    • 邻接矩阵:二维数组
  • 邻接表:链表(与哈希表的链式地址相似)
  • 效率对比
  • 图的遍历

    • 广度优先
  • 深度优先
posted @ 2026-03-08 13:53  nddddd  阅读(2)  评论(0)    收藏  举报