本文主观性较强,主要是将我不熟悉的一些部分记录一下。
目录
二叉树
二叉排序树
中序遍历(LDR)是顺序的。
插入k时,若k小于根节点,则左子树;若k大于根节点,右子树。
删除k时,若k有左右子树,则将右子树上中序遍历的第一个结点作为根节点。
查找效率取决于树的高度
平均查找长度的计算=(
∑
\sum
∑层*该层的结点数)/总结点数目。
拓展问题:关于已知前序、中序、后序遍历,构造二叉树的问题
前序:DLR,中序:LDR,后序:LRD
其中 前+中 或者 中+后 可唯一的确定一棵树
前+中:前序的第一个为根结点,根据根节点在中序中的位置,找到左子树和右子树。反复该步骤即可获得二叉树。
后+中:后序的最后一个为根节点,其他同上。
1.在二叉排序树上找到的序列要求:
a,b,c,d,e,f,g:其中a大于/小于 bcdefg,b大于/小于 cdefg
平衡二叉树
左右子树高度差不超过1
平均查找长度为O(logn)
1.高度为h的AVL,最少具有的结点数: f ( h ) = f ( h − 1 ) + f ( h − 2 ) + 1 f(h)=f(h-1)+f(h-2)+1 f(h)=f(h−1)+f(h−2)+1
2.非空AVL T1,删除v形成AVL T2,将v插入T2得到AVL T3,则:
若v是T1的叶节点,则T1和T3可能不同
若v不是T1的叶节点,则T1和T3可能不同
哈夫曼树
哈夫曼树(最优二叉树):带权路径长度(WPL)最小的二叉树
WPL计算方式:
∑
\sum
∑权值*路径长度。
1.n个叶子结点的哈夫曼树共有2n-1个结点。
2.对应一组权值构造出来的哈夫曼树一般不是唯一的。
图
图的遍历
存储方式:邻接表和邻接矩阵
BFS
空间复杂度:最坏情况下
O
(
V
)
O(V)
O(V) (队列)
时间复杂度:邻接表:
O
(
V
+
E
)
O(V+E)
O(V+E)
邻接矩阵:
O
(
V
2
)
O(V^2)
O(V2)
DFS
空间复杂度:
O
(
V
)
O(V)
O(V) (递归工作栈)
时间复杂度:邻接表:
O
(
V
+
E
)
O(V+E)
O(V+E)
邻接矩阵:
O
(
V
2
)
O(V^2)
O(V2)
最小生成树算法
Prim:
O
(
V
2
)
O(V^2)
O(V2) 顶点较少边稠密的图
Kruskal:
O
(
E
l
o
g
E
)
O(ElogE)
O(ElogE) 边稀疏而顶点较多的图
其中使用到了并查集,并查集的两个优化方法:路径压缩+按秩合并
最短路算法
Dijkstral :
O
(
V
l
o
g
E
)
O(VlogE)
O(VlogE) 负边权不适用
Bellman-Ford:
O
(
V
E
)
O(VE)
O(VE)
是通过循环 n 次,每次循环都遍历每条边,进而更新结点的距离
假如当前迭代次数为k次,d[]数组的含义为源点s到每个点的不超过k条边的最短距离
SPFA:可以处理负边权,判断负环,本质是广度优先算法。
平均时间复杂度是
O
(
E
)
O(E)
O(E),最坏时间复杂度是
O
(
V
E
)
O(VE)
O(VE),其中m是图的边数。
二分图问题
一开始每个点都是独立的为一条路径,总共有n条不相交路径。我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数。
强连通分量
Tarjan算法: O ( V + E ) O(V+E) O(V+E)
AOE 关键路径求法
(1)正拓扑
(2)逆拓扑
(3)判断正逆值是否相等,相等则该弧为关键路径
排序算法
插入排序
直接插入排序
对于第
i
i
i个元素,
[
1
,
i
−
1
]
[1,i-1]
[1,i−1]为有序序列,将
i
i
i插入前方序列
比较次数和移动次数与初始序列有关。
最好情况下:比较n次,移动0次
最坏情况下:比较次数
n
×
(
n
−
1
)
2
\frac{n\times(n-1)}{2}
2n×(n−1) ,移动次数
∑
i
=
2
n
i
+
1
\sum_{i=2}^{n}i+1
∑i=2ni+1
稳定
折半插入排序
对于第
i
i
i个元素,
[
1
,
i
−
1
]
[1,i-1]
[1,i−1]为有序序列,将
i
i
i插入前方序列
插入时,使用折半查找寻找插入位置。
比较次数和移动次数与初始序列有关。
比直接插入排序比较次数少。
稳定
希尔排序
对于距离为
d
i
d_i
di的记录放在同一组,组内插入排序
一般来说
d
1
=
n
/
2
,
d
i
+
1
=
d
i
/
2
d_1=n/2, d_{i+1}=d_{i}/2
d1=n/2,di+1=di/2
最坏时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),平均约为
O
(
n
1.3
)
O(n^{1.3})
O(n1.3)
不稳定
交换排序(冒泡+快速)
冒泡排序
每次将最小的元素移到序列头上(或者最大的元素放在尾上)
比较次数和移动次数与初始序列有关。
最好情况下:比较n-1次,移动0次,只需要
最坏情况下:比较次数
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1) ,移动次数
3
n
(
n
−
1
)
2
\frac{3n(n-1)}{2}
23n(n−1)
快速排序
基于分治法,选择枢轴元素,每趟让枢轴元素在正确的位置上,左侧为小于枢轴元素的数,右侧为大于枢轴元素的数字。
空间效率:递归实现,平均情况下
O
(
l
o
g
n
)
O(logn)
O(logn),最大递归深度为
O
(
n
)
O(n)
O(n),最小递归深度为
O
(
l
o
g
n
)
O(logn)
O(logn)
递归次数与排列顺序有关,若每次划分后,分区比较平衡,则递归次数少。
时间效率:最坏情况下
O
(
n
2
)
O(n^2)
O(n2),最好情况下
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
不稳定
快速排序 是所有 内部排序算法 中 平均性能最优 的算法
选择排序(简单选择排序+堆排序)
简单选择排序
对于第
i
i
i个元素,
[
1
,
i
−
1
]
[1,i-1]
[1,i−1]已为最终有序序列,从i至n选择当前最小作为第i个元素。
与冒泡排序不同在于,选择排序为从i到n选择最小元素,而冒泡是通过从后往前两两比较和移动来实现最小元素到第i个位置。
时间效率:移动操作 最多
3
(
n
−
1
)
3(n-1)
3(n−1) 次,最少0次。但比较次数与序列初始状态无关,始终为
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1)次
堆排序
建立大根堆或者小根堆,是一个完全二叉树,可顺序存储,其中第
i
i
i个元素,左儿子
2
i
2i
2i,右儿子
2
i
+
1
2i+1
2i+1
时间效率:建堆
O
(
n
)
O(n)
O(n),每次调整
O
(
h
)
O(h)
O(h),故时间复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
不稳定
归并和基数排序
归并排序
性能与元素序列无关,排序的趟数为
l
o
g
2
n
log_2n
log2n,每趟排序复杂度为
O
(
n
)
O(n)
O(n)
缺点在于需要
O
(
n
)
O(n)
O(n)的额外存储空间
多路归并排序为外部排序
基数排序
在n很大,且记录的关键字位数较少且可分解的情况下。
基于关键字大小排序
空间复杂度
O
(
r
)
O(r)
O(r)
时间效率为
O
(
d
(
n
+
r
)
)
O(d(n+r))
O(d(n+r)),与序列的初始无关。总共d趟,一趟分配需要
O
(
n
)
O(n)
O(n),一趟收集需要
O
(
r
)
O(r)
O(r)
其中
d
d
d为
a
i
a_i
ai的最多位数,r=10[0,1,2,3…9]总共10个队列
Summary
名字 | 最坏时间复杂度 | 最好时间复杂度 | 平均时间复杂度 | 是否稳定 | 是否为内部排序 |
---|---|---|---|---|---|
直接插入排序 | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | √ | √ |
希尔排序 | O ( n 2 ) O(n^{2}) O(n2) | O ( n 1.3 ) O(n^{1.3}) O(n1.3) | × | √ | |
冒泡排序 | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | √ | √ |
简单选择排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | × | √ |
快速排序 | O ( n 2 ) O(n^2) O(n2) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | × | √ |
堆排序 | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | × | √ |
基数排序 | O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) | O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) | O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) | √ | √ |
2路归并排序 | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | √ | √ |
1.对任意序列进行基于比较的排序,求最少的比较次数:最坏的情况下: ⌈ l o g 2 ( n ! ) ⌉ \lceil log_2(n!) \rceil ⌈log2(n!)⌉
2.堆排序:每趟后有元素在正确位置上(具体哪个元素,待有空再详细说明)
快速排序:每趟后枢轴元素在正确位置上
简单选择排序:每趟后第i个位置的数为最终数字
冒泡排序:第i趟后第i个位置的数为最终数字
字符串算法
KMP:模式匹配
O
(
m
+
n
)
O(m+n)
O(m+n)
算法详情
马拉车:求最长回文子串 :
O
(
n
)
O(n)
O(n)
动态规划
引言
动态规划是算法和数据结构的重难点之一,其包含了 分治思想、空间换时间、最优解,等多种基石算法思想。常作为笔试面试中的中等困难题出现。
动态规划特点
分治是算法中的一种基本思想,其通过将问题进行分解为子问题,不断进行递归将子问题分解为更小的问题,通过组合子问题的解来得到原问题的解。
类似于分治算法,动态规划也通过组合子问题的解得到原问题的解。 不同的是,适合动态规划解决的问题具有重叠子问题和最优子结构两大特征。
重叠子问题
动态规划的子问题具有重叠的,即各个子问题中包含重复的更小的子问题。若使用暴力法进行穷举,求解这些相同子问题会查收大量的重复计算,效率抵下。
动态规划在第一次求解某个子问题时,会将子问题的解保存至矩阵中,后续遇到子问题时,则直接通过查表获取解,保证每个独立子问题制备计算一次,从而降低算法的时间复杂度。
最优子结构
如果一个问题的最优解可以由其子问题的最优解组合构成,那么称此问题具有最优解结构。动态规划从基础问题的解开始,不断进行迭代组合、选择子问题的最优解,最终得到原问题的最优解。
无后效性
将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。