【数据结构】数据结构 知识总复习

1. 复杂度计算

复杂度计算用于评估算法的效率,通常关注两个方面:时间复杂度和空间复杂度。

1.1 时间复杂度

时间复杂度衡量算法运行所需的时间,通常用大 O 表示法来表示:

  • O(1):常数时间,算法执行时间不随输入规模的变化而变化。
  • O(log n):对数时间,执行时间随输入规模的对数增长,例如二分查找。
  • O(n):线性时间,执行时间与输入规模成正比,例如线性搜索。
  • O(n log n):线性对数时间,通常见于高效排序算法,如快速排序。
  • O(n^2):平方时间,执行时间与输入规模的平方成正比,例如冒泡排序。
  • O(2^n):指数时间,执行时间随输入规模指数增长,例如某些递归算法。
  • O(n!):阶乘时间,执行时间随输入规模阶乘增长,例如某些组合问题的算法。

1.2 空间复杂度

空间复杂度衡量算法运行时所需的内存,通常也用大 O 表示法:

  • O(1):常数空间,所需内存与输入规模无关。
  • O(n):线性空间,所需内存与输入规模成正比,例如存储输入数据的数组。
  • O(n^2):平方空间,所需内存与输入规模的平方成正比,例如二维矩阵。

1.3 如何计算

  1. 确定基本操作:找出算法中最频繁执行的操作。
  2. 分析循环结构:嵌套循环通常会增加复杂度的阶数。
  3. 简化表达式:忽略低阶项和常数系数,专注于主要增长趋势。

示例:
假设有一个简单的算法:

for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        // 常数时间操作
    }
}

这个算法的时间复杂度是 O(n^2),因为有两个嵌套的循环,每个循环都运行 n 次。

2. 常见线性结构

2.1 顺序表 和 链表

2.1.1 顺序表 (Array List)

  • 原理:使用连续内存空间,支持通过索引直接访问。
  • 优点:随机访问速度快 (O(1))。
  • 缺点:插入和删除操作较慢 (O(n)),可能需要数组扩展或缩减。

操作:

  • 查找:O(1)
  • 插入:O(n)
  • 删除:O(n)
  • 修改:O(1)

2.1.2 链表 (Linked List)

  • 原理:节点不连续存储,通过指针链接。
  • 优点:插入和删除操作快 (O(1)),动态大小。
  • 缺点:随机访问较慢 (O(n)),额外空间用于指针。

操作:

  • 查找:O(n)
  • 插入:O(1)(已知位置),O(n)(查找位置)
  • 删除:O(1)(已知位置),O(n)(查找位置)
  • 修改:O(n)(查找位置)

2.2 栈 和 队列

2.2.1 什么是栈?什么是队列?关系是什么?

栈 (Stack)

  • 定义:遵循“后进先出”(LIFO)原则。最后插入的元素最先被取出。
  • 主要操作:入栈、出栈、查看栈顶、判断空栈。
  • 应用:函数调用、浏览器历史记录。

队列 (Queue)

  • 定义:遵循“先进先出”(FIFO)原则。最早插入的元素最先被取出。
  • 主要操作:入队、出队、查看队头、判断空队列。
  • 应用:任务调度、数据缓冲。

关系

  • 操作方式:栈在一端操作(栈顶),队列在两端操作(队头和队尾)。
  • 实现:栈和队列可以通过数组或链表实现,但操作规则不同。

2.2.2 如何实现?数组链表原生实现?适配器?

栈 的实现:

  • 数组实现:
  • 入栈:添加到数组末尾。
  • 出栈:从数组末尾移除元素。
  • 查看栈顶:访问数组末尾元素。
  • 链表实现:
  • 入栈:在链表头部插入元素。
  • 出栈:从链表头部移除元素。
  • 查看栈顶:访问链表头部元素。

队列 的实现:

  • 数组实现:
  • 入队:添加到数组末尾。
  • 出队:从数组开头移除元素。
  • 循环数组:使用环形缓冲区优化性能。
  • 链表实现:
  • 入队:在链表尾部插入元素。
  • 出队:从链表头部移除元素。
  • 查看队头:访问链表头部元素。

适配器实现

  • 栈基于队列:
  • 单队列:通过旋转队列模拟栈。
  • 双队列:使用两个队列交替操作。
  • 队列基于栈:
  • 双栈:使用两个栈交替操作实现队列。

3. 常见非线性结构

3.1 二叉树

3.2 堆

3.2.1 性质和概念

是一种完全二叉树数据结构,分为最大堆和最小堆:

  • 最大堆:每个节点的值大于或等于其子节点的值,根节点是最大值。
  • 最小堆:每个节点的值小于或等于其子节点的值,根节点是最小值。

3.2.2 实现

数组表示:

  • 左子节点索引:2i
  • 右子节点索引:2i + 1
  • 父节点索引:i / 2

操作:

  • 插入:将新元素添加到末尾,然后上浮调整。
  • 删除:删除根节点(最大或最小值),用末尾元素替换,然后下沉调整。
  • 堆化:将数组调整为堆。

3.2.3 topk

Top-K 问题:

  • 使用最小堆保持k个最大元素,时间复杂度O(nlogk)

3.2.4 堆排序

堆排序:

  • 构建最大堆
  • 排序:将根节点(最大值)与最后一个元素交换,调整堆。
  • 时间复杂度O(n log n)空间复杂度O(1)

3.3 二叉树常见问题

3.4 搜索树

搜索树是一种用于存储有序数据并支持高效查找、插入和删除操作的数据结构。主要包括二叉搜索树(BST)、AVL树和红黑树。

3.4.1 概念性质

  • 二叉搜索树(BST)
  • 性质:对于每个节点,其左子树包含所有小于该节点的值,右子树包含所有大于该节点的值。
  • 查找:平均时间复杂度 O(log n),最坏情况 O(n)(如链表)。

3.4.2 增删查改

  • 查找:从根节点开始,根据键值向左或右子树递归查找。
  • 插入:插入新节点后,维护 BST 的性质。
  • 删除:删除节点后,可能需要重构树以保持 BST 的性质。三种情况:
  1. 删除叶节点。
  2. 删除只有一个子节点的节点。
  3. 删除有两个子节点的节点(用右子树的最小值或左子树的最大值替换)。

3.4.3 缺陷

  • 缺陷:BST 在最坏情况下退化为链表,导致操作效率降低。

3.4.4 缺陷解决方案:平衡树

平衡树通过自平衡机制保持树的高度尽可能低,保证操作效率。主要包括 AVL 树和红黑树。

  • AVL树
  • 性质:每个节点的左子树和右子树的高度差(平衡因子)至多为 1。
  • 效率:查找、插入、删除操作的时间复杂度均为 O(log n)。
  • 实现逻辑
  • 旋转操作:单旋转(左旋、右旋)和双旋转(左-右旋、右-左旋),用于恢复平衡。
  • 插入:插入后进行旋转调整。
  • 删除:删除后进行旋转调整以恢复平衡。
  • 红黑树
  • 性质:每个节点是红色或黑色,满足以下条件:
  1. 根节点是黑色。
  2. 个叶子节点(NIL)是黑色。
  3. 如果一个节点是红色,则它的两个子节点都是黑色。
  4. 从每个节点到其每个叶子节点的路径包含相同数量的黑色节点。
  • 效率:查找、插入、删除操作的时间复杂度均为 O(log n)。
  • 实现逻辑
  • 旋转操作:类似 AVL 树。
  • 颜色调整:插入或删除后调整节点颜色和进行旋转以维护红黑树性质。
  • AVL树 与 红黑树 的区别
  • 平衡性:
  • AVL 树:更严格的平衡,确保树高度差不超过 1,查找操作更快,但插入和删除需要更多的旋转操作。
  • 红黑树:较宽松的平衡,允许更多的高度差,插入和删除操作相对较快,但查找操作可能稍慢。
  • 实现复杂性:
  • AVL 树:旋转操作较多,复杂度高。
  • 红黑树:相对较少的旋转操作和颜色调整,易于实现。
  • 应用场景:
  • AVL 树:适用于查找频繁的场景。
  • 红黑树:适用于插入和删除操作频繁的场景,如 C++ STL 的 mapset 使用红黑树。

3.5 哈希

3.5.1 什么是哈希?

哈希是一种将数据映射到固定大小的值(哈希值)的过程,用于快速查找和存储数据。

3.5.2 如何解决哈希冲突

闭散列(开放地址法)是处理哈希冲突的一种方法。主要有线性探测二次探测两种方式:

  • 线性探测(Linear Probing)
  • 方法:发生冲突时,顺序查找下一个位置。
  • 缺陷
  • 主聚集:连续的被占用区域,导致性能下降。
  • 性能退化:表满时,查找和插入效率降低。
  • 二次探测(Quadratic Probing)
  • 方法:冲突后,使用二次方程计算下一个位置。
  • 缺陷
  • 二次聚集:探测序列可能会形成聚集。
  • 表大小要求:通常要求表大小为质数,增加实现复杂性。
  • 总结:线性探测简单但易聚集,二次探测减少了聚集但复杂度更高。选择适当方法需根据实际需求。

开散列(拉链法)是一种解决哈希表冲突的方法,通过在每个哈希槽上维护一个链表来存储冲突的元素。它的优点包括:

  1. 简单易实现:链表操作简单,易于处理动态增长的元素。
  2. 性能稳定:负载因子增加时,性能相对稳定。
  3. 灵活的内存使用:链表可以动态增长,无需重新分配整个哈希表。
  4. 删除操作简单:删除元素只需从链表中移除即可。

应对冲突严重的方法:

  1. 优化哈希函数:设计良好的哈希函数以减少冲突。
  2. 调整表的大小:通过扩展哈希表和重新哈希减少链表长度。
  3. 使用其他数据结构:长链表可用平衡树替代,以提高效率。
  4. 合理设置负载因子:动态调整负载因子,定期扩展哈希表。
  5. 监控性能:定期检查哈希表性能,以便进行优化。

3.5.3 位图

位图是一种用于高效存储和快速查询的二进制数据结构,主要用于表示集合中的元素是否存在。以下是位图的主要特点:

  1. 存储方式:位图使用一个数组,每个元素用一个比特位表示。如果元素存在,则对应的位设置为1,不存在则设置为0。
  2. 直接定址:元素的值直接作为数组索引,通过该索引访问或修改对应的位,因此没有哈希冲突。
  3. 节省空间:位图每个元素只用一个比特位,相比其他数据结构(如整型数组),能够显著减少内存使用,尤其适合处理大量数据的存在性检测。
  4. 查询效率高:由于位图的查询和更新操作都可以在常数时间内完成,适合需要快速判断元素是否存在的场景。

总结:位图是一种高效的空间利用和查询的数据结构,用于表示和操作大量元素的存在性。

3.5.4 布隆过滤器

布隆过滤器是一种高效的空间优化数据结构,用于判断一个元素是否在一个集合中。其特点包括:

  1. 空间节省:使用多个哈希函数和一个位图来表示集合,显著减少存储需求,适合处理大规模数据。
  2. 误判:布隆过滤器可以准确地确定元素不在集合中,但不能100%保证元素集合中,可能存在误判(即误报),因为不同的元素可能会映射到相同的位。
  3. 操作效率:元素的加入和查询操作都很快,时间复杂度为常数时间 O(k),其中 k 是哈希函数的数量。
  4. 删除问题:布隆过滤器无法直接删除元素,因为其位图中的位可能被多个元素共享,解决方案包括使用计数型布隆过滤器或周期性重新生成过滤器。

总结:布隆过滤器通过使用多个哈希函数和位图,能够高效地存储和查询大规模数据,但存在一定的误判概率。

3.5.5 海量数据处理的问题

在海量数据处理问题中,以下三种技术可以有效解决数据存储和查询问题:

  1. 位图
  • 解决方法:使用一个大位数组(或位图)来记录数据的存在性,每一位表示一个数据项的状态(存在或不存在)。适用于数据范- 围较小且可以用位表示的场景。
  • 优点:查询效率高,适合存储和处理布尔值数据。
  • 缺点:对于范围非常大的数据(例如大范围的整数),位图可能占用过多内存。
  1. 隆过滤器
  • 解决方法:使用多个哈希函数和一个位图来高效地检测某个元素是否在集合中。适用于需要处理大量数据的情况。
  • 优点:节省存储空间,查询速度快。
  • 缺点:可能产生误判(误报),不能直接删除元素。
  1. 哈希切分(Hash Partitioning):
  • 解决方法:通过哈希函数将数据分散到多个存储分区中,以便更均匀地分配存储负载和提高并行处理能力。
  • 优点:能处理超大规模数据,通过分区管理避免单个节点的负载过重。
  • 缺点:需要设计合理的哈希函数和分区策略,以避免数据倾斜和负载不均。

这些技术可以根据具体的数据处理需求和应用场景,单独或组合使用,以优化数据存储和查询性能。

4. 算法

4.1 排序

  1. 冒泡排序(Bubble Sort):
  • 实现:重复遍历待排序列,比较相邻元素并交换,使得较大的元素逐渐“冒泡”到末尾。
  • 复杂度:最坏和平均情况 O(n²),最佳情况 O(n)(当已排序时)。
  • 稳定性:稳定。
  • 适用场景:数据量较小或对稳定性有高要求时使用。
  1. 选择排序(Selection Sort):
  • 实现:每次选择未排序部分的最小(或最大)元素,将其放到已排序部分的末尾。
  • 复杂度:O(n²)。
  • 稳定性:不稳定。
  • 适用场景:对内存占用有严格要求且数据量小的情况。
  1. 插入排序(Insertion Sort):
  • 实现:将待排序元素插入到已排序部分的适当位置,像手工排序扑克牌一样。
  • 复杂度:最坏和平均情况 O(n²),最佳情况 O(n)(当已排序时)。
  • 稳定性:稳定。
  • 适用场景:数据量较小或部分已排序时使用。
  1. 归并排序(Merge Sort):
  • 实现:将数据分割成更小的部分,递归地排序这些部分,然后合并已排序的部分。
  • 复杂度:O(n log n)。
  • 稳定性:稳定。
  • 适用场景:大数据量,且需要稳定排序时。
  1. 快速排序(Quick Sort):
  • 实现:选择一个“基准”元素,将数据分为两个部分,递归地对这两部分进行排序。
  • 复杂度:平均情况 O(n log n),最坏情况 O(n²)(例如基准选择不当)。
  • 稳定性:不稳定。
  • 适用场景:大数据量时,快速排序通常表现优越。
  1. 堆排序(Heap Sort):
  • 实现:将数据构建成一个堆,每次取出堆顶元素,并重建堆。
  • 复杂度:O(n log n)。
  • 稳定性:不稳定。
  • 适用场景:需要优良时间复杂度且不要求稳定性时。
  1. 计数排序(Counting Sort):
  • 实现:统计每个元素的出现次数,然后按顺序重建排序后的数据。
  • 复杂度:O(n + k),其中 k 是元素范围。
  • 稳定性:稳定。
  • 适用场景:元素范围有限且数据量大的情况。
  1. 基数排序(Radix Sort):
  • 实现:按每一位的值进行多轮排序(通常结合计数排序作为稳定的子排序)。
  • 复杂度:O(nk),其中 k 是位数。
  • 稳定性:稳定。
  • 适用场景:处理整数数据,尤其是位数较少时效果良好。
  1. 希尔排序(Shell Sort):
  • 实现:通过多轮插入排序,逐步减少间隔(增量),最终进行标准插入排序。
  • 复杂度:最坏情况 O(n²),增量序列优化后可达 O(n log n)。
  • 稳定性:不稳定。
  • 适用场景:中等规模的数据集,内存有限时。

4.2 二分查找

二分查找是一种高效的查找算法,适用于已排序的数组。其基本思路是将数组分成两半,通过比较中间元素与目标值,决定继续在哪一半进行查找。算法步骤如下:

  1. 初始化:设定两个指针,left 指向数组起始位置,right 指向数组末尾位置。
  2. 循环:计算中间位置 mid,比较 mid 位置的元素与目标值。
  • 如果相等,返回 mid 位置。
  • 如果目标值小于 mid 位置的元素,将 right 指向 mid - 1
  • 如果目标值大于 mid 位置的元素,将 left 指向 mid + 1
  1. 结束:当 left 超过 right 时,查找结束,如果未找到目标值,返回 -1 或表示未找到的标识。

时间复杂度:O(log n),空间复杂度:O(1)

  • 16
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Q_hd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值