数据结构复习笔记

简答题 (3)

顺序表和链表的概念及异同

  • 顺序表: 把逻辑上相邻的结点储存在物理位置上的相邻储存单元中,结点的逻辑关系由储存单元的邻接关系来体现.
  • 链表: 逻辑上相邻的结点存储再物理位置上非连续非顺序的存储单元中, 结点的逻辑关系由指向下一个结点的指针确保.
  • 相同点
    • 都是线性表结构.
    • 结点逻辑上都是连续的.
    • 每个结点都有唯一的前驱和唯一的后续(首结点无前驱, 尾结点无后继).
  • 不同点
    • 物理存储方式不同: 物理空间上连续 vs 不连续.
    • 插入和删除方式不同: 任意位置插入或删除, 需要搬运大量元素, 时间复杂度 O ( n ) O(n) O(n) vs 无需搬运元素, 时间复杂度 O ( 1 ) O(1) O(1).
    • 随机访问不同: 支持随机访问, 时间复杂度 O ( 1 ) O(1) O(1) vs 只能顺序访问, 时间复杂度 O ( m ) O(m) O(m).
    • 是否需要扩容: 容量不足时, 开辟新空间拷贝元素, 平均时间复杂度为 O ( 1 ) O(1) O(1) vs 无需扩容.
    • 空间利用率不同: 利用率高 vs 利用率低, 额外存储指针信息, 空间碎片化.
    • 缓存利用率不同: 利用率高, 空间局部性好 vs 利用率低, 空间局部性差.
    • 应用场景不同: 适合随机访问频繁, 插入删除少 vs 适合插入删除频繁, 随机访问少.

栈和队列的概念及异同

  • 栈: 只允许在一端插入或删除的线性表.
  • 队列: 只允许在一段插入, 而在另一端删除的线性表.
  • 相同点
    • 都是受限的线性表结构.
    • 插入操作都限定在表尾进行.
    • 都可以通过顺序结构或链式结构实现.
    • 插入与删除的时间复杂度均为 O ( 1 ) O(1) O(1), 空间复杂度一致.
  • 不同点
    • 删除结点位置不同: 在表尾进行, FILO(先入后出) vs 在表头进行, FIFO(先入先出).
    • 是否支持空间共享: 单调栈可以实现多栈空间共享 vs 顺序队列不能.
    • 应用场景不同: 括号匹配, 表达式转换与求值, 函数调用和递归实现, DFS搜索或遍历 vs 系统资源管理, 消息缓冲管理(TCP), BFS搜索或遍历.

广义表存储方式及深度计算

  • 存储方式
    • 链式存储; 表头或表尾只考虑当前层.
    • 表头-表尾表示法
      • 头指针指向表头.
      • 结点间需依靠额外的表头结点相连.
      • 表结点: 标志(值为 1 1 1), 数据指针, 后继指针(已为表尾时指向 NULL).
      • 原子结点: 标志(值为 0 0 0), 值.
      • 空表为 NULL.
    • 表头-后继表示法
      • 头结点(特殊表结点)指向表头.
      • 表结点: 标志(值为 1 1 1), 表头指针, 后继指针(已为表尾时为 NULL).
      • 原子结点: 标志(值为 0 0 0), 值, 后继指针(已为表尾时为 NULL).
      • 空表为空原子结点.
  • 长度: 最顶层元素个数.
  • 深度: 括号最大重数.
    • 向下遍历时 + 1 +1 +1, 向上回溯时 − 1 -1 1, 动态记录最大值.
    • 空表深度为 1 1 1.

表头-表尾表示法

表头-后继表示法

散列表定义/要素/优缺点

  • 散列表: 利用连续内存的随机访问时间复杂度为 O ( 1 ) O(1) O(1), 将键值通过散列函数映射到数据索引, 实现快速查找/插入/删除的数组查找结构.
  • 要素: 散列函数.
    • 确定性: 同一键值, 散列函数总是得到相同的散列值.
    • 高效性: 散列值计算速度要快, 以减少查找/插入/删除操作时的耗时.
    • 冲突最小化: 不同的键值尽量映射到不同的区域中, 以避免解决冲突导致的性能下降.
  • 优点
    • 数据处理迅速.
    • 能够以 O ( 1 ) O(1) O(1) 时间复杂度实现查找/插入/删除操作.
    • 实现相对简单(关键只在于散列函数).
  • 缺点
    • 散列表中数据无序.
    • 不允许出现重复数据.
    • 为减少冲突率, 开辟数组大小需超过数据量很多, 容易造成空间浪费.
    • 当发生冲突时, 解决冲突较为复杂.

森林转换为二叉树

  • 树转换为二叉树
    • 孩子结点从左到右顺序编号.
    • 所有兄弟结点之间加边.
    • 每个结点只保留与第一个孩子的边, 删掉和其余孩子间的边.
  • 森林转换为二叉树
    • 森林中所有树均转换为二叉树.
    • 从第二棵二叉树开始, 依次将根结点作为前一棵二叉树的根结点的右孩子(即成为右子树).
    • 所有二叉树都相连后即得.

图的最小生成树

  • 最小生成树: 权值最小的生成(保留全部顶点)子树.
  • Kruskal: 从边集中逐步选取不会形成回路的最小权边; 时间复杂度为 O ( m log ⁡ m ) O(m\log m) O(mlogm).
  • Prim: 从某结点出发逐步选取不会形成回路的最小权边邻接的顶点; 时间复杂度为 O ( n 2 ) O(n^2) O(n2).

应用题 (5)

给定二元算术表达式, 画出对应的树, 写出前缀表达式和后缀表达式

  • 二元算术表达式构造树
    • 叶子结点均为数字; 内部结点均为运算符(不包含括号).
    • 按照运算优先级从叶子结点反向生长.
  • 树的遍历
    • 中序遍历 - 中缀表达式: 左根右.
    • 前序遍历 - 前缀表达式: 根左右.
    • 后序遍历 - 后缀表达式: 左右根.

给定字符及频率, 画出 Huffman 树, 写出 Huffman 编码

  • Huffman 树构造
    • 每个字符及频率视为一个结点, 即有结点集.
    • 依次选择频率最小的两个结点, 构造分别以两结点作为左孩和右孩的新结点替换到集合中, 新结点频率为两结点频率和.
    • 直至结点集中只剩一个结点, 即 Huffman 树.
  • Huffman 编码: 从根结点出发, 左孩在末尾补 0 0 0, 右孩在末尾补 1 1 1, 直至叶子结点, 即得到该字符编码.
  • 前缀码: 不存在某编码为另一编码的前缀的情况.

给定数组, 分别构造二叉排序树和 AVL 树(不平衡时旋转方式), 分别计算平均查找长度

  • 构造二叉排序树
    • 第一个数作为根结点.
    • 小于当前结点成为左孩或进入左子树; 大于当前结点成为右孩或进入右子树.
  • 构造 AVL 树
    • 平衡: 每个结点平衡因子(左右子树高度差)的绝对值不超过 1 1 1.
    • 插入后不平衡时递归旋转调整(旋转中心为子树的根结点).
不平衡情况平衡因子旋转类型旋转方式
LL: 插入较高的左子树的左子树根结点 2 2 2; 左子树根结点 1 1 1右单旋原左子树根结点成为新根结点, 原左子树的右子树成为原根结点左子树, 原根结点成为原左子树根结点的右子树
RR: 插入较高的右子树的右子树根结点 − 2 -2 2; 右子树根结点 − 1 -1 1左单旋原右子树根结点成为新根结点, 原右子树的左子树成为原根结点的右子树, 原根结点成为原右子树根结点的左子树
LR: 插入较高的左子树的右子树根结点 2 2 2; 左子树根结点 − 1 -1 1左右双旋左子树左旋, 整体右旋
RL: 插入较高的右子树的左子树根结点 − 2 -2 2; 右子树根结点 1 1 1右左双旋右子树右旋, 整体左旋
  • 平均查找成功长度 = ( ∑ =(\sum =( 本层深度 × \times × 本层结点数 ) / )/ )/ 总结点数.
  • 平均查找失败长度 = ( ∑ =(\sum =( 本层深度 × \times × 本层结点补全空叶子结点数 ) / )/ )/ 补全叶子结点总数.

写出图的邻接矩阵和邻接表表示, 对图分别进行 BFS 和 DFS; 对有向图进行拓扑排序

  • 邻接矩阵: 对角线为 0 0 0, 无边连接记为 ∞ \infty .
  • 邻接表: 遍历时需额外记录顶点邻接点链表中当前的指针.
  • BFS: 使用队列辅助.
    • 初始化一个队列, 起始顶点入队.
    • 出队一个顶点, 标记已遍历, 将未遍历过的邻接点入队.
    • 直至队列空.
  • DFS: 使用栈辅助或递归.
    • 初始化一个栈, 起始顶点入栈.
    • 读栈顶, 若栈顶顶点仍有未被遍历的邻接顶点, 邻接顶点入栈; 若没有邻接点或邻接点已全部遍历, 出栈, 并标记为已遍历.
    • 直至栈空.
  • 拓扑排序: 对有向无环图的顶点线性排序; 每个顶点只出现一次; 若两顶点间存在路径, 则出发点必在终点前.
    • 在图中选择一个入度为 0 0 0(即无前驱顶点)的顶点输出, 并在图中删除该顶点及边.
    • 直到图中无顶点.
    • 若图中仍有顶点但已无入度为 0 0 0 的顶点, 则说明有环, 不存在拓扑排序.

Dijkstra 算法和 Floyd 算法求图的最短路径

  • Dijkstra 算法: 一点到其余各点的最短路径; 时间复杂度为 O ( n 2 ) O(n^2) O(n2).
    • 初始化距离数组 D [ n ] D[n] D[n], 出发点 s s s 赋值为 0 0 0, 其余全部赋值为 ∞ \infty .
    • 更新当前顶点所有邻接点的距离 D [ i ] = min ⁡ ( D [ i ] , D [ j ] + D i s t [ j ] [ i ] ) D[i]=\min(D[i],D[j]+{\rm Dist[j][i]}) D[i]=min(D[i],D[j]+Dist[j][i]) ( j j j 为当前顶点, D i s t [ n ] [ n ] {\rm Dist}[n][n] Dist[n][n] 为邻接矩阵); 当前顶点标记已遍历; 选择未遍历过且 D [ i ] D[i] D[i] 中值最小的顶点作为下一顶点.
    • 直至遍历完全部顶点.
  • Floyd 算法: 各点间最短路径; 时间复杂度为 O ( n 3 ) O(n^3) O(n3).
    • 初始化距离矩阵 D [ n ] [ n ] = D i s t [ n ] [ n ] D[n][n]={\rm Dist}[n][n] D[n][n]=Dist[n][n].
    • 依次以一顶点作为中介顶点更新距离矩阵 D [ x ] [ y ] = min ⁡ ( D [ x ] [ y ] , D [ x ] [ j ] + D [ j ] [ y ] ) D[x][y]=\min(D[x][y],D[x][j]+D[j][y]) D[x][y]=min(D[x][y],D[x][j]+D[j][y]) ( j j j 为当前顶点).
    • 直至遍历完全部顶点.

选择排序/堆排序/冒泡排序/快速排序/归并排序的步骤/算法复杂度/数据比较次数

  • 选择排序
    • 依次选出尾序列中的最值放在序列起始位置.
    • 从头遍历到尾的前一个.
  • 堆排序
    • 原地构造大根堆.
    • 交换当前末尾与根, 固定末尾将头序列调整为大根堆.
    • 从尾遍历到头的后一个.
  • 冒泡排序
    • 相邻的两两比较并交换.
    • 从头遍历到尾的前一个.
  • 快速排序
    • 分区中随机选择一个基准, 所有小于基准的放在一侧, 大于基准的放在另一侧.
    • 对左右小分区递归操作
  • 归并排序
    • 开辟临时空间, 有序子序列合并后存入临时空间.
    • 递归操作.
  • Shell 排序
    • 初始间隔 d 0 = ⌊ n 2 ⌋ d_0 = \lfloor \frac{n}{2} \rfloor d0=2n 的元素构成子表, 每个子表插入排序.
    • 缩小间隔 d i + 1 = ⌊ d i 2 ⌋ d_{i+1} = \lfloor \frac{d_i}{2}\rfloor di+1=2di 并对当前子表插入排序, 直至间隔为 1 1 1.

排序算法特点
表格来自: 各类排序算法特性+比较次数+交换次数对比表格.

*多项式广义表表示

多项式广义表表示1
多项式广义表表示2

*堆的存储结构和小根堆构建

  • 小根堆: 结点值总不小于其父结点值的完全二叉树.
  • 存储结构: 链式存储; 顺序存储(数组模拟).
  • 小根堆构建: 数组模拟
    • 从最底层子树开始, 递归调整父结点和子结点, 使得父结点小于等于子结点.
    • 调整后还需递归检查并调整子树, 确保每个子树中父结点均小于等于子结点.
    • 直至调整完根节点, 递归检查并调整所有子树.

*先序遍历和中序遍历重构二叉树; 层次遍历和中序遍历重构二叉树

  • 先序遍历和中序遍历重构二叉树
    • 先序遍历为"根左右", 中序遍历为"左根右", 由此可确定根结点和左右子树.
    • 递归地, 可一直确定到左右叶子结点及其父结点.
  • 层次遍历和中序遍历重构二叉树
    • 层次遍历第一个结点必为根结点, 中序遍历为"左根右", 由此可确定左右子树.
    • 层次遍历第二个结点开始为第二层的根结点, 递归地, 可以确定每一层的根结点和左右子树.

*四则运算求值

  • 中缀表达式变为后缀表达式
    • 数字直接列出.
    • 优先级高于或等于栈顶符号的运算符/左括号入栈; 优先级低于栈顶符号/右括号时, 栈顶运算符/左括号出栈, 直至高于或等于栈顶运算符/左括号出栈后, 再入栈.
  • 后缀表达式求值
    • 数字入栈.
    • 遇到运算符时出栈两个数字, 并将运算结果入栈.
    • 最后栈中只剩一个数字, 即为运算结果.

算法设计 (2)

两个递增单链表原地合并为递增/递减链表

  • 双指针法
    • 两个指针分别指向两个单链表头部.
    • 比较指针指向结点的数据: 数据较小的结点插入新链表, 指针后移; 相等时插入第一个单链表的结点, 两个指针同时后移.
    • 递增合并为递增时采用尾插法; 递增合并为递减时采用头插法.
    • 直至一个指针来到单链表末尾.
    • 继续将另一指针(若未指向单链表末尾)指向结点插入新链表并后移, 直至也来到单链表末尾.

二叉树查找结点并递归返回路径

  • 后序遍历
    • 找到结点时, 输出当前结点并返回 1 1 1; 抵达空结点时返回 0 0 0.
    • 左右子递归返回 1 1 1 时, 输出当前结点.
    • 递归返回左右子递归返回值.

二叉树中相距最远的距离

  • 后序遍历
    • 空结点高度为 0 0 0, 最远距离为 0 0 0.
    • 当前根结点最远距离: m a x D i s t = max ⁡ ( m a x D i s t L e f t , m a x D i s t R i g h t , h e i t g h t L e f t + h e i g h t R i g h t ) {\rm maxDist}=\max({\rm maxDistLeft}, {\rm maxDistRight}, {\rm heitghtLeft}+{\rm heightRight}) maxDist=max(maxDistLeft,maxDistRight,heitghtLeft+heightRight); 高度 + 1 +1 +1.
    • 递归返回最远距离及高度(或结点维护高度只递归返回最远距离).

一步可以迈一个或两个台阶, 走十级台阶有多少种走法

  • 动态规划
    • 状态转移方程: f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n)=f(n-1)+f(n-2) f(n)=f(n1)+f(n2).
    • 初始值: f ( 1 ) = 1 , f ( 2 ) = 2 f(1)=1,f(2)=2 f(1)=1,f(2)=2.
  • 24
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值