常见简单算法归纳

- 前言
- 字符串匹配
- 求最小生成树
- 最短路径问题
- 查找算法与数据结构
- 排序算法与数据结构
- 声明
前言
以下将以常见基础问题的对应解答方法进行算法实例与数据结构的总结
字符串匹配
朴素模式匹配算法(暴力解决)
原理
主串长度为n,模式串长度为 m
将主串所有长度为m的子串依次与模式串对比,直到找到一个完全匹配的子串或所有的子串都不匹配为止
最多有n-m+1个子串
代码实现
时间复杂度
KMP算法
原理
根据next数组,不经过回溯主串指针达到减小时间复杂度
代码实现
求next数组
手算
根据当前字符的前端进行查找
下标j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
模式串 | a | b | a | a | b | c | a | b | a |
next[j] | -1 | 0 | 0 | 1 | 1 | 2 | 0 | 1 | 3 |
始终满足next[0]=-1;next[1]=0
计算next看前面的字符串和离自己最近的几个有相同的
next[2]看前面的”ab”,b没有和前面匹配的,则连1都没有
next[3]看前面的”aba”,a有相同的能够匹配
next[4]看前面的”abaa”,a有相同的能够匹配,但是aa没有能匹配的
next[5]看前面的”abaab”,b有相同的能够匹配,ab也有能匹配的,但aab没有能匹配的
next[6]看前面的”abaabc”,c没有能匹配的,则连1都没有
next[7]看前面的”abaabca”,a有相同的能够匹配,但是ca没有能匹配的
next[8]看前面的”abaabcab”,b有相同的能够匹配,ab也有能匹配的,但cab没有能匹配的
next[9]看前面的”abaabcaba”,a有相同的能够匹配,ba也有能匹配的,aba也有能匹配的,但caba没有能匹配的
注意:”ababa”的next值为3,后面的aba与前面的aba能够匹配
机算
1 | void GetNext(char* t, int* next) { |
算法优化
将next值进行替换,减少不必要的时间,得到nextval数组再进行匹配
下标j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
模式串 | a | b | a | a | b | c | a | b | a |
next[j] | -1 | 0 | 0 | 1 | 1 | 2 | 0 | 1 | 3 |
nextval[j] | -1 | 0 | -1 | 1 | 0 | 2 | -1 | 0 | 1 |
从后往前看(从前往后也可以),next[8]值为3,找到j为3,若二者的模式串字符相等,则将next[8]该改为next[3],以此类推,得到优化有的nextval数组
比较
求最小生成树
最小生成树不止一种
Prim算法(普里姆算法)
简单记忆:根据节点找权值
原理
从某一个顶点开始构建生成树;每次将代价最小的新顶点纳入生成树,直到所有顶点都纳入为止
理解
开始选中一点,找到距离其代价最小的顶点纳入树中,再找到这两个点的下一个最优点并纳入树中,知道全部纳入为止
图实例
1、最开始只有一个“P城”点
2、找到距离其代价最小的顶点纳入树中
如此往复继续查找这两个点所有代价中最小代价的点并纳入树中
直到最后找完即可(最小生成树不只一种)
时间复杂度
Kruskal算法(克鲁斯卡尔算法)
简单记忆:根据权值画节点
原理
每次选择一条权值最小的边,使这条边的两头连通(原本已经连通的就不选),直到所有结点都连通
理解
找到整个体系中权值最小的边,将该边的两头纳入树中,再选择剩下的权值最小的边将该边的两头纳入树中,如果出现一边的两头已纳入的情况则忽略该边,直到所有结点都连通
图示例
找到整个体系中权值最小的边
将该边的两头纳入树中
再选择剩下的权值最小的边将该边的两头纳入树中
剩余的连接需要看是否连通(间接连通也是连通了)
若没有连通则进行连通
时间复杂度
比较
Prim算法时间复杂度为O(|V|^2^)——适合边稠密图,即|E|大的
Kruskal算法时间复杂度为O(|E|log2|E|)——适合边稀疏图,即|E|小的
最短路径问题
BFS算法(广度优先算法)
无权图
代码实现
核心思想
利用队列对图进行层次遍历,加上pre前驱指针找到最短路径
局限性
Dijkstra算法(迪杰斯特拉算法)
图示例
1、初始化3个数组,最短路径找不到就存为无穷,且其前驱设为-1表示没有前驱
2、遍历所有结点,找到还没有确定最短路径的点,且其dist值最小的点,将其标记为找到最短路径了
3、检查所有邻接自V的顶点,若其 final 值为false,则更新 dist 和 path 信息
4、遍历所有结点,找到还没有确定最短路径的点,且其dist值最小的点,将其标记为找到最短路径了
5、检查所有邻接自V的顶点,若其 final 值为false,则更新 dist 和 path 信息
6、遍历所有结点,找到还没有确定最短路径的点,且其dist值最小的点,将其标记为找到最短路径了
7、检查所有邻接自V的顶点,若其 final 值为false,则更新 dist 和 path 信息
8、遍历所有结点,找到还没有确定最短路径的点,且其dist值最小的点,将其标记为找到最短路径了
算法结束
代码实现
时间复杂度
与Prim算法类似
局限性
Floyd算法(弗洛伊德算法——动态规划)
图示例
代码实现
时间、空间复杂度
比较
查找算法与数据结构
ASL概念
概念
查找算法的评价指标
查找成功
查找失败
顺序查找
原理
顺序查找,又叫“线性查找”,通常用于线性表。
就是将整个数据结构进行遍历,从头到尾或者从尾到头
代码实现
算法优化
原理
设置“哨兵”元素,无需判断是否越界,执行效率更高
代码实现
ASL
顺序查找有序表优化
原理
对有序表进行的顺序查找
ASL
查找失败
查找成功
顺序查找被查概率不相等优化
查找失败也是从头到尾全部遍历——根据实际应用查找的情况来考虑
总结
折半查找(二分查找)
原理
对半砍来查找,但只适用于有顺序的,不然无法进行二分查找
简单来说就是数字炸弹
例:找78->0~100(50)->50~100(75)->75~100(87)->75~87(81)->75->81(78)->78
折半查找判定树
性质
失败结点
ASL
代码实现
ASL
总结
分块查找
原理
将需要查找的原始表进行分块,构造索引表
ASL
已知情况
未知情况
顺序查找常考
总结
BST(二叉排序树)
概念
一个二叉树满足左子树结点数值<根结点数值<右子树结点数值
查找操作
非递归代码实现
递归代码实现
比较
由于递归调用函数会在函数调用栈里面多分配空间,会浪费更多空间,所以一般使用非递归调用
插入操作
插入后的新结点一定是叶子结点
删除操作
删除结点为叶子结点
直接删除,不会破坏二叉排序树的性质
删除结点只有一棵左子树或者右子树
让z的子树成为z父结点的子树,替代z的位置
删除结点有左右子树
若结点z有左、右两棵子树,则令z的直接后继(或直接前驱)替代z然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一或第二种情况
ASL
查找成功
查找失败
总结
AVL(平衡二叉树)
定义
调整最小不平衡二叉树
LL(左孩子右上旋)
RR(右孩子左上旋)
LR(左孩子的右孩子先左上旋再右上旋)
RL(右孩子的左孩子先右上旋再左上旋)
代码实现
LL与RR
总结
平衡二叉树的删除
ASL
RBT(红黑树)
区别
红黑树更适合需要频繁进行修改的场景
定义
性质
时间复杂度
插入操作
提示
染色对应前面的具体结点
比如父换爷+染色——对应就是原本的父节点和爷结点进行染色
变新意思为将该叔结点经过染色后再当做是新插入的结点再将其以插入操作从上到下进行操作
总结
B树
定义
特征性质
高度范围
插入操作
前提要求
过程
1、通过B树的查找,确定插入的位置,只能是终端节点
2、若关键字个数超出范围,则将中间位置([m/2]
向上取整)向上提,其后面的关键字放到新的结点中去
3、若父节点的关键字个数超了,也同上一样提中间数其后关键字放新结点
小总结
删除操作
过程
1、若被删除关键字在终端节点,则直接删除(注意:不能低于B数要求下限)
2、若被删除关键字在非终端节点,则用其前驱或者后继来替代其位置
后继
3、若被删除关键字在终端节点,且此时低于B树下限,则拉父提兄(将父节点拉下来,兄弟节点提上去)
拉右兄弟
拉左兄弟
4、若被删除关键字在终端节点,且兄弟都不富裕,则进行合并操作
合并操作
若合并拉父后,父节点不满足B树,则继续进行拉父提兄,若兄依旧不足,则同样进行合并操作
小总结
插入删除操作总结
B+树
定义
类似于分块查找
查找
多路查找
顺序查找
对比B树
关键字对应分叉不同
节点关键字范围不同
叶子结点与关键字的关系
节点作用
存储本质区别
总结对比
散列查找(空间换时间)
概念
散列函数
ASL
装填因子
常见散列函数设计方法(哈希函数)
除留余数法
直接定址法
数字分析法
平方取中法
处理冲突方法
拉链法
开放定址法
线性探测法
探测该位置后续位置是否有冲突
查找操作
删除操作
平方探测法
伪随机序列法
再散列法(再哈希法)
总结
排序算法与数据结构
插入排序
原理
将每个元素前面的所有更大的元素往后移,将该元素移到比该元素大的元素的最前面,即逐次前移
代码实现
也可以带哨兵
时间复杂度
优化——折半查找
优化代码实现
时间复杂度
总结
希尔排序
定义
原理
1、对一个序列进行排序,加入一个增量d,进行多趟排序,d表示两个元素之间的距离
2、进行第一趟排序,找到增量对应的子表,对每一个子表进行一次插入排序
2、进行第二趟处理
3、进行第3趟处理
综合
代码实现
从中间往后选择元素,并选择在其之前的子表
时间复杂度与空间复杂度
稳定性与适用性
总结
交换排序
冒泡排序
原理
每一趟都两两比较互换位置,每一次选出一个最小(最大)的数,需要进行n-1趟,每一趟需要比较(n-当前趟次)的数
算法实现
时间复杂度与空间复杂度
总结
快速排序(最快)
定义
原理
选择每一块的基准进行递归排序,基准元素一般选取子表的第一个元素
1、选择基准,将其他数进行左右分配
2、当low、high指针相遇,确定基准元素位置
3、递归对左右子表进行基准划分
左子表
右子表
算法实现
需要用到函数递归调用栈(但时间复杂度忽略不计)
时间复杂度与空间复杂度
总结
选择排序
简单选择排序
定义
原理
对未进行排序的元素进行选择一个最小(最大)的元素,并将其放入有序子序列
代码实现
时间复杂度与空间复杂度
总结
不会因为原序列初始状态而改变时间复杂度
堆排序
定义堆
大根堆:即所有根节点大于其所有左右子树的结点
小根堆:即所有根节点小于其所有左右子树的结点
建立大根堆
1、找到序号最大(后续一次减小)的根结点,比较看是否满足大根堆,不满足则替换
2、找到序号倒数第二大的根结点,比较看是否满足大根堆,不满足则替换
3、当出现更小的元素“下坠”时,进行向下调整,直至无法下调
代码实现
堆的插入与删除
基于大根堆排序
原理
最大的87已被移除大根堆
调整,使让09不断下坠
代码实现
移除后再建立新堆
时间复杂度与空间复杂度
总结
归并排序
定义
把两个或多个已经有序的序列合并成一个(递归)
(二路)
(四路)
原理(二路)
1、将序列中两两归并
2、将序列中两两归并后的两组两两归并
3、将两组归并后的4个1组的再进行归并,最后得到一个完整的序列
代码实现
使用一个数组进行全部复制,使用两个指针进行对原数组的复写
完整代码(递归)
时间复杂度与空间复杂度
总结
基数排序
定义
(不基于关键字进行对比,而是对其关键字的每一位数进行对比然后排序)
原理
1、对数字的个位进行分配(首次无需管顺序)
2、对第一次分配的进行收集(从前往后连成一条新的序列)
3、对数字的十位进行分配(个位数越大的越先入队)
4、对第二次分配进行收集(从前往后连成一条新的序列)
5、对数字的百位进行分配(十位数越大的越先入队)
6、对第三趟分配进行收集(从前往后连成一条新的序列)(基本已顺序排列)
综合
代码实现
时间复杂度与空间复杂度
应用角度
总结
外部排序
原理
数据元素太多,需要借助外存和内存的缓冲区进行在内存实现归并后写入外存并复写原数据
时间开销
优化
多路归并
优化:减少初始归并段
总结
k越大不一定越好(可通过败者树优化)
败者树(k路归并优化)
优化原因
定义
原理
构建败者树
选出每一个比赛的失败者记录其所在段号,最后的胜利者单独记录段号
败者树更替
归并段3的6对树中结点进行比较
选出最小元素
对比次数
总结
置换-选择排序
原理
可以让每个初始归并段的长度超越内存工作区大小的限制
通过内存工作区,得到多个归并段
当遇到与当前归并段不符合的顺序数时进行停留,待工作区满后开启新的归并段
最终得到多个归并段
具体文字描述
最佳归并树
定义
构造最佳归并树(类哈夫曼树)
先选择最小的两个,组合后再选择两个小的,依次选择即可
多路归并的注意事项
当k叉归并时,节点数不满足完美归并树,则添加虚段进行归并
求虚段数量
总结
声明
以上示例图来自王道数据结构
==本博客所有内容均为学习所用,未经允许不可进行商业转载==
- Title: 常见简单算法归纳
- Author: ZJ
- Created at : 2024-10-15 00:00:00
- Updated at : 2025-01-17 01:35:56
- Link: https://blog.overlordzj.cn/2024/10/15/数据结构/常见算法归纳/
- License: This work is licensed under CC BY-NC-SA 4.0.