数据结构详细介绍

数据结构规定数据怎么放,算法规定在这份数据上怎么算。两者一起决定一段程序能处理多大规模、要花多少时间和内存。

衡量标准是大 O,看的是数据量 n 变大之后的增长趋势,常数和系数先忽略。

记法 数据量翻倍时 典型操作
O(1) 耗时几乎不变 数组按下标取值、哈希表查找
O(log n) 只多一步左右 二分查找、平衡树查找
O(n) 大约翻倍 遍历链表、线性扫描
O(n log n) 略超翻倍 归并排序、堆排序
O(n²) 大约变成 4 倍 冒泡排序、两层循环比较

线性结构

数组把元素放在连续内存里。知道下标就能直接算出地址,读取是 O(1)。在中间插入或删除要搬动后面的元素,是 O(n)。适合按下标访问、长度变化不多的场景。

链表每个节点只保存值和下一节点的地址,节点在内存里不必相邻。已知位置时,插入和删除只改指针,是 O(1)。查找第 k 个元素必须从头走,是 O(n)。适合频繁插删、很少随机访问的场景。

栈只在同一端放入和取出,后进先出。函数调用、括号匹配、撤销操作都是栈:调用一层就压入一层,返回时弹出。

队列在一端放入、另一端取出,先进先出。任务排队、广度优先搜索用它。双端队列两端都能进出,滑动窗口的最值常用它。

哈希表

哈希表用一个函数把键变成数组下标,平均情况下查找、插入、删除都是 O(1)。两个不同的键算出同一个下标叫做冲突,常用链地址法把冲突的键串成链表,或用开放寻址法找下一个空位。

哈希表不保留键的顺序。需要「有没有」「对应的值是什么」时用它;需要按顺序遍历时用树或排序数组。

树

树从根节点向下分叉,每个节点可以有多个子节点。描述层级、保持有序、做优先选择,大多用树。

二叉树每个节点最多两个孩子。二叉搜索树约定左子树的值都更小,右子树的值都更大。查找时每次丢掉一半,平衡时是 O(log n)。如果插入顺序已经有序,树会退化成一条链,退回 O(n)。红黑树、AVL 树在插入和删除后调整形态,把高度维持在 O(log n)。

堆是一棵完全二叉树,存在数组里:下标 i 的左孩子是 2i+1,右孩子是 2i+2。大顶堆的每个父节点都不小于孩子。取最大值是 O(1),放入或删除后向上、向下调整是 O(log n)。优先队列和定时任务用堆。堆排序先建成堆,再反复取出堆顶,总时间 O(n log n)。

前缀树按字符逐层往下走,一条从根到节点的路径就是一个字符串前缀。查某个词是否出现、做前缀补全时,时间和词的长度成正比,与词典里有多少词关系不大。

图

图由顶点和边组成。边可以有方向,也可以带权重。社交关系、路线、依赖关系都是图。

两种常见存法:

  • 邻接矩阵用二维数组,matrix[u][v] 表示 u 到 v 有没有边。判断两点是否相连是 O(1),空间是 O(n²),适合顶点少、边密的图。
  • 邻接表每个顶点挂一条自己的边。空间和顶点数加边数成正比,适合边稀疏的图。遍历某个点的邻居更快。

广度优先搜索用队列,先访问离起点近的点,适合无权图的最短路径。深度优先搜索用栈或递归,沿一条路走到头再回头,适合检测环、拓扑里的连通块。

Dijkstra 在边权非负时,用堆每次取出当前最近的点,求单源最短路径。拓扑排序只适用于有向无环图,每次取出入度为 0 的点,用来排任务依赖。

常用算法

遍历是所有算法的底子:线性结构从头走到尾;树用前序、中序、后序;图用广度和深度优先。

二分查找要求序列已经有序。每次取中点,目标更小就去左半,更大就去右半,O(log n)。写的时候中点用 lo + (hi - lo) / 2,避免两个大下标相加溢出。

排序里,冒泡、选择、插入是 O(n²),数据量大时不用。归并排序把数组分成两半分别排好再合并,稳定,O(n log n),需要额外 O(n) 空间。快速排序选一个基准,小的放左边、大的放右边,平均 O(n log n),基准每次都选到最值时会退化成 O(n²)。堆排序是 O(n log n),原地,但不稳定。

递归把问题写成「同样的问题,规模更小」。必须有一个不再往下拆的终止条件。调用深度就是栈的深度,太深会栈溢出。尾递归和「用栈模拟递归」可以把深度换到自己控制的内存里。

动态规划处理「大问题的答案能由更小的同类问题拼出来」,并且子问题会重复计算。先写出状态表示什么,再写出转移公式,最后确定计算顺序。爬楼梯、背包、编辑距离都属于这一类。能画成有向无环图的重叠子问题,一般就能写成动态规划。

贪心每一步都取当前看起来最好的选择,而且可以证明后面不需要反悔。活动安排、霍夫曼编码适用。证明不了「局部最优能推出全局最优」时,就改用动态规划或搜索。

怎么选

先看操作,再选结构:

  • 只按下标读、批量扫描:数组
  • 头尾频繁插入删除:链表,或语言里基于数组扩容的双端队列
  • 后进先出、先进先出:栈、队列
  • 按键查找:哈希表
  • 既要查找又要有序:平衡二叉搜索树
  • 反复取当前最大或最小:堆
  • 点和点之间有连接:图,边少用邻接表,边多用邻接矩阵

同一个问题往往有多层做法。例如在无序数组里找一个值是 O(n);先排序再二分是 O(n log n) 的准备加上 O(log n) 的查找;如果之后还要反复查,把数据放进哈希表,准备一次,之后每次 O(1)。算法的差别主要在这笔准备成本和之后每次查询的成本怎么换

相关推荐
CQU_JIAKE4 小时前
10.9【A】
数据结构
第25小时记录4 小时前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·python
Logic1018 小时前
C语言/数据结构数位DP题解:平滑排列数字统计——区间内相邻位差不超过K的数字个数
c语言·数据结构·动态规划·时间复杂度·数位dp·算法题·前导零处理
wang09079 小时前
硬着头皮学数据结构和算法之Myers算法
数据结构·算法
垆边人似月.10 小时前
华为机试题 :最长递增子序列题目
数据结构·c++·算法·华为
yi01121 小时前
LeetCode 219:存在重复元素 II——哈希表记录“最近一次出现的位置”
数据结构·人工智能·笔记·python·算法·leetcode·哈希表
禾小西1 天前
Redis 数据结构:快速的 Redis 有哪些慢操作?
数据结构·数据库·redis
H.莓飛1 天前
【数据结构】二叉树_OJ题
linux·开发语言·数据结构·算法