408数据结构选择题考点回顾总结
一、绪论
1.1 求时间复杂度?【选择题】【算法分析】
1.类型一:时间复杂度为对数(while)
- 设置循环体循环次数为T(n)
- 根据循环体特点依次写出前几项,写到T(n)
- 观察值与循环次数的关系
- 代入退出循环条件
void func(int n){
int i = 1;
while(i<=n){
i = i*2;
}
}
设循环体次数
T(n):
i = 1,2,4,....,2的T(n)方
2的T(n)方 <= n
T(n) = log2(n)
void func(int n){
int i = 0,sum = 0;
while(sum<n)sum+=++i;
return i;
}
设循环体次数
T(n):
i = 0,1,2,4,....,T(n)
sum = 0,1,3,7,....,(T(n)*(T(n)+1))/2
(T(n)*(T(n)+1))/2<= n
T(n)² = 2n
T(n) = o(n的1/2)
2.类型二:嵌套循环(for)
- 设置循环体循环次数为T(n)
- 根据循环特点依次写出前几项,写到T(n)
- 观察值与循环次数的关系
- 代入退出循环条件
void func(int n){
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < 2*i; ++j)
{
m++;
}
}
}
-----------------
n 2i n
∑ ∑ 1 = 2∑ i = n(n+1)
i=1 j=1 i=1
//只看阶数最高
T(n) = O(n²)
void func(int n){
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < m; ++j)
{
a[i][j] = 0;
}
}
}
--------
n-1 m-1 n-1
∑ ∑ 1 = ∑ m = n*m
i=0 j=0 i=0
T(n) = O(n*m)
3.类型三:混合(不单纯for)
分别求出相乘
count = 0;
for (int k = 0; k < n; k*=2)
{
for (int j = 0; j < n; ++j)
{
count++;
}
}
--------
(最外层)设循环体次数
T(n):
K = 1,2,4,....,2的T(n)方
2的T(n)方 <= n
T(n) = log2(n)
n
∑ 1 = n;
j=1
T(n)= n*log2(n);
4.类型四:递归(较难,选择题考的少,暂不总结)
【真题统计】
2022.1 求时间复杂度:类型二
2019.1 求时间复杂度:类型一
二、链表
个人觉得链表选择题较少,循环链表可以关注一下
三、栈与队列
3.1 进出栈序列【选择题】
一般送分,不予详解。
- 考法1:出栈序列顺序
- 考法2:栈的大小
- 考法3:出入栈有几种
3.2 循环队列的进队入队,队满判断【选择题】
队空 | Q.front == Q.rear |
队满 | Q.front == (Q.rear + 1) % maxSize |
队内元素数 | (rear-front+maxSize )%maxSize(rear大于front时)|| (front + maxSize - rear) % maxSize (front大于rear时) |
入队 | Q.rear = (Q.rear + 1) % maxSize; //rear指针前移 |
出队 | Q.front = ((Q.front + 1) % maxSize); //front指针前移 |
四、特殊矩阵的压缩【选择题】
五、树与二叉树
5.1 树的性质【选择题】
总结点数 | n0+n1+n2+…+nm |
总分支数 | 1n1+2n2+…+m*nm |
总结点数 | 总分支数 +1 |
5.2 完全二叉树的性质【选择题】
5.3 树的遍历
递归和迭代的算法
- 两个遍历可以确定是一棵二叉树的序列组合都需要中序遍历
- 根据前后序列可以确定祖先与孙子的关系(祖先节点与子孙结点在前序序列和后序序列中的顺序一定相反)
5.4 树、二叉树、森林的转换【必考】【选择题】
5.4.1 树 转 二叉树
注意:树转换的二叉树没有右子树
- 给兄弟加线
- 给除长子外的孩子去线
- 层次调整,以根为中心顺时针45°
5.4.2 森林 转 二叉树
将所有树的根节点视为兄弟节点
- 森林中的每棵树=>二叉树
- 将所有二叉树的根相连
- 以第一棵为轴心顺时针45°
5.4.3 二叉树 转 树
树转二叉树的逆过程
5.4.4 二叉树 转 森林
5.4 线索二叉树
引入的目的:为了加快查找结点前去和后继的速度
0左右孩子1前驱后继
5.5 哈夫曼树与哈夫曼编码
5.5.1带权路径长度(WPL)
- 从根结点到任务结点的长度(经过的边数)与该点的权值的乘积称为该点的带权路径长度,所有叶结点的长度之和为该树的带权路径长度
- 哈夫曼树的WPL最小
5.5.2 构造方法
左小右大
哈夫曼树中不存在度为1的结点
5.5.3 哈夫曼编码
左0右1
任一结点的编码不会是其他的前缀
5.6 二叉排序树
定义:
- 左子树小于根节点
- 右子树大于根节点
- 左右字数又是一颗二叉排序树
5.6.1 二叉排序树的平均查找长度ASL
5.6.2 二叉排序树的插入和删除
插入
左小右大,从根结点开始比较
删除
- 删除结点为叶子结点:直接删除即可
- 删除结点有一棵左子树或者右子树
3.删除结点有左右子树 :
直接前驱:找到左子树(最右下)的结点来替换
直接后继:找到右子树中序第一个子女来替换(最左下)
5.7 二叉平衡树
定义:
- 平衡二叉树(AVL)上的任一个结点的左右子树的高度差的绝对值不超过1(左子树高度减右子树高度,即平衡因子(-1,0,1)
- 平衡二叉树不一定是二叉排序树
5.7.1 二叉平衡树的插入和构建
参考视频,此处不做详细总结
插入:
- LL型
- RR型
- LR型
- RL型
删除:
5.7.2 红黑树
六、图
6.1 图的基本概念
6.1.1 连通和强连通
无向图–连通:
- 两个顶点之间路径存在,则这两个顶点连通
- 如果整个无向图中任意两个顶点都是连通的,则为连通图
- n个顶点的无向图G
有向图–强连通:
- 顶点a到顶点b路径存在,且顶点b到顶点a路径存在,则这两个顶点强连通
- 如果整个有向图中任意两个顶点都是强连通的,则为强连通图
- n个顶点的有向图G:若G为强连通图,最少为n条边(构成回路)
6.2 图的存储
6.2.1 领接矩阵
- 适合稠密的图
- 空间复杂度O(n²)
- 无向图可以压缩矩阵
6.2.2 领接表
- 存储稀疏图
6.2.3 十字链表
6.2.4 邻接多重表
6.3 图的操作
6.3.1 图的广度优先搜索(BFS)
6.3.2 图的深度度优先搜索(DFS)
- 邻接矩阵唯一→BFS序列和DFS序列唯一
- 邻接表不唯一→BFS序列和DFS序列不唯一
6.3.3 最小生成树
- 最小生成树不唯一
- 权值之和最小
- 边数为定点数-1
Prim算法
类似于DFS;适合边稠密图
Kruskal算法
不断选取当前位被选中的最小的边;适合边稀疏图
6.3.4 最短路径
- 单源最短路径问题
- 每队定点间最短问题
[单源最短路径]BFS算法(无权图)
BFS算法求最短路径的局限:仅能求无权图或者各条边权值都相同的图
[单源最短路径]Dijkstra算法(带权图、无权图)
数组final[]-------标记是否已经找到最短路径
辅助数组dist[] -----最短路径长度
辅助数组path[]-----路径上的前驱
第一轮:从第一个顶点开始寻找所有前驱为自身且可到达的结点以及权值,然后修改dist[]和path[],并将final[]自己的索引置为true.
第二轮:在final[]找未作为起点寻找过的且dist[]最小的顶点(上一个顶点找到的最小权值顶点),重复第一轮。
[各顶点间最短路径]Floyd算法(带权图、无权图)
动态规划思想,n轮求解
- 各顶点之间最短路径长度的邻接矩阵
- 两个顶点之间中转点矩阵
6.3.5 有向无环图描述表达式
6.3.6 拓扑排序
AOV网(有向无环图)
- 从AOV网中选择一个没有前驱( 入度为零)的顶点并输出
- 从网中删除该顶点和所有以它为起点的有向边
- 重复1、2,知道当前 AOE网为空或
当前网中不存在无前驱结点为止
6.3.7 关键路径
AOE网
- 所有路径中,具有最大路径长度的路径称为关键路径
- 关键路径上的活动称为关键活动
过程:
- 求所有事件的最早发生时间ve:取直接指向该它的V的VE(V)+ 此权值,多个取MAX(取决于指向点的所有点的VE)
- 求所有事件的最晚发生时间vl :取它直接指向的V的VL(V)- 此权值,多个取MIN(取决于该点指向的所有点的VL)
- 求所有活动的最早发生时间e:此边起始的V的VE(V)(直接照抄VE)
- 求所有活动的最晚发生时间l:此边终点的VL(V)- 此权值
- 求所有活动的时间余量
- 关键活动耗时增加,则整个工期增长
- 关键活动时间降低,整个工期时间降低,但是,关键活动的时间降低到一定程度时,关键活动可能会变成非关键活动
- 关键路径可能有多条,只提高一条关键路径上的关键活动可能并不能降低整个工程的时间,但是降低所有关键路径上都存在的关键活动一定能降低整个工程的时间
七、查找
7.1 顺序查找和折半查找
7.1.1 顺序查找
代码
for循环查询
- 查找成功时,平均查找长度为ASL =(n+1)/2
- 查找失败时,平均查找长度为ASL = n + 1
- 时间复杂度O(n)
7.1.2 折半查找
顺序查找的优化,前提是序列有序
- 平均查找长度:log2(n+1)-1
- 时间复杂度O(log2n)
序列:(7,10,13,16,19,29,32,33,37,41,43) 查找33
第一轮:low = 0;high =10;mid =(low+high)/2 = 5;找到29
第二轮:low = 5+1;high =10;mid =(low+high)/2 = 8;找到37
第三轮:low = 5+1;high =7;mid =(low+high)/2 = 6;找到32
第三四轮:low = 7;high =7;mid =(low+high)/2 = 6;找到33,查找成功
7.1.3 分块查找
特点:块内无序、块间有序
10 20 30 40 50 有序
(7,10)(13,19,16,20)(27,22,30)(40,36)(43.50.48) 无序
找22,在第三个分块里找,遍历
7.2 B树、B+树、
7.3 散列表
7.3.1 散列表基本概念
装填因子:表中记录数/散列表长度(装填因子越大,发生冲突概率越高,散列表查找效率越低)
平均成功查找长度:
平均失败查找长度:
7.3.2 散列表构造方法
1.除留余数法—H(key) = key % p
- 取一个不大于 m 但最接近 m 或等于 m 的质数
2.直接定址法—H(key) = key 或者 H(key) = a *key+b
- 适用于关键字基本连续(不会产生冲突)
3.数字分析法
4.平方取中法
7.3.3 散列表处理冲突
1.开放定址法 Hi = (H(key)+di)%m
- 线性探测法:di = 0,1,2,3…;发生冲突,每次往后探测相邻下一个单位是否为空
- 平方探测法:较好处理聚集;di =0²,1²,-1²,2²,-2²…K²,-k²(K<=m/2)散列表长度必须是4j+3的素数
- 伪随机法:di随机序列
- 再散列法
2.拉链法
八、排序
8.1 插入类排序
8.1.1 直接插入排序
算法思想:每次将该元素按照其大小插入到前面已有序的序列中(将数组中第一个元素视为有序,因此从第二个元素开始)
//序列变化
5| 2 4 6 1 3
2 5| 4 6 1 3
2 4 5| 6 1 3
2 4 5 6| 1 3
1 2 4 5 6| 3
1 2 3 4 5 6|
8.1.2 折半插入排序
插入到前面已有序的序列中采用折半查找法
8.1.3 希尔排序
插入排序的优化升级
49,38,65,97,76,13,27,49
gap=length/2=4
{49,76}{13,36}{27,65}{49,97}--- 49,13,27,49,76,38,65,97
gap=length/2=2
{27,49,65,76}{13,8,49,97}--- 27,13,49,38,65,49,76,97(已基本有序)
gap=length/2=1
13,27,38,49,49,65,76,97
8.2 交换类排序
8.2.1 冒泡排序
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
每轮确定一个数的最终位置
8.2.2 快速排序
//以序列 46 30 82 90 56 17 95 15 共8个元素
初始状态:
46 30 82 90 56 17 95 15 选择46 作为基准值,i = 0, j = 7
i = 0 j = 7
15 30 82 90 56 17 95 46 15 < 46, 交换 15 和 46,移动 i, i = 1
i = 1 j = 7
15 30 82 90 56 17 95 46 30 < 46, 不需要交换,移动 i , i = 2
i = 2 j = 7
15 30 46 90 56 17 95 82 82 > 46, 交换82 和 46,移动 j , j = 6
i = 2 j = 6
15 30 46 90 56 17 95 82 95 > 46, 不需要交换,移动 j , j = 5
i = 2 j = 5
15 30 17 90 56 46 95 82 17 < 46, 交换46 和 17,移动 i, i = 3
i = 3 j = 5
15 30 17 46 56 90 95 82 90 > 46, 交换90 和 46,移动 j , j = 4
3 = i j = 4
15 30 17 46 56 90 95 82 56 > 46, 不需要交换,移动 j , j = 3
i = j = 3
8.3 选择类排序
8.3.1 简单选择排序
- 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
- 以此类推,直到所有元素均排序完毕
8.3.2 堆排序
堆本质上是一颗完全二叉树
- 大根堆:根 >= 左右子树 L(i) >= L (2i) && L(i) >= L(2i + 1) (2i 和 2i + 1 为根节点的左右子树)
- 小根堆:根 <= 左右子树 L(i) <= L (2i) && L(i) <= L(2i + 1)
从左到右,从下到上
一轮确定一个,和数组尾元素互换
8.4 归并排序
算法思想:将相邻的两个有序表归并为一个有序表
49,38,65,97,76,13,27
一次归并:{38,49}{65.97}{13.76}{27}
二次归并:{38,49,65,97,}{13,27,76}
三次归并:{13,27,38,49,65,76,97}
8.5 基数排序
- 初始化r个队列,按照关键字的权值递增的顺序(个十百),进行关键字总位数分配和收集(9,8,7,6,5,4,3,2,1,0)
- 分配:遍历各个元素,根据当前轮次的关键字和当前元素的关键字位插入相应的链表中
- 收集:将各个队列中的元素依次出队,并首尾相接形成新的序列
基数排序:
原始序列:211,438,888,007,111,985,666,996,233,168,
以个位递减:
438,888,168,007,666,996,985,233,211,111,520,
以十位递减:
996,888,985,168,666,438,233,530,211,111,007
以百位递减:
996,985,888,666,520,438,233,211,168,111,007
8.6 内部排序性能分析
时间复杂度(最好) | 时间复杂度(平均) | 时间复杂度(最坏) | 空间复杂度 | 是否稳定 | |
---|---|---|---|---|---|
直接插入排序 | O(n) | O(n²) | O(n²) | O(1) | 是 |
冒泡排序 | O(n) | O(n²) | O(n²) | O(1) | 是 |
简单选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 否 |
希尔排序 | O(1) | 否 | |||
快速排序 | O(nlog2(n)) | O(nlog2(n)) | O(n²) | O(log2(n)) | 否 |
堆排序 | O(nlog2(n)) | O(nlog2(n)) | O(nlog2(n)) | O(1) | 否 |
二路归并排序 | O(nlog2(n)) | O(nlog2(n)) | O(nlog2(n)) | O(n) | 是 |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O® | 是 |
九、外部排序
待做:
- 稀疏矩阵
- 并查集
- 红黑树
- B树