算法总结
thhuanghaizhe · · 算法·理论
dijkstra最短路算法
算法过程(不包含初始化)
- 从起点
s 开始,更新相连节点距离(其实就是如果有直接距离就更新直接距离) - 将起点
s 标记为 “已访问过” - 在“未访问的节点”上寻找离起点最近的节点
pos (相当于 1. 的起点) - 将节点
pos 标记为“已访问过” - 寻找与节点
pos 相连(有路径)的、未被标记为“已访问过”的节点j ,并更新节点j 到初始节点最近距离 - 重复 3. 4. 步骤
- 直到所有的节点都被标记为“已访问过”,跳出循环
- 输出距离
代码实现(链式前向星 + dijkstra)
链式前向星此代码不详解
#include <bits/stdc++.h>
using namespace std;
//我不编译所以变量名直接用简洁明了的意思了
const int N = 节点数 + 5, M = 边数 + 5, ma = INT_MAX;
//加5是为了防止数据溢出导致RE
struct Edge{
int to, cost, next;
};
Edge e[M * 2];
int head[N], ans[N], cnt = 1;
// 头节点集合 最短距离
bool flag[N];//是否被标记过
void add(int from, int to, int cost){
e[cnt].to = to;
e[cnt].cost = cost;
e[cnt].next = head[from];
head[from] = cnt;
cnt ++;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
//大数据输入输出优化
freopen("xxx.in", "r", stdin);
freopen("xxx.out", "w", stdout);
//读入文件类型数据(比赛用)
int n, m, s;//n 节点数 m 边数 s 初始节点
cin >> n >> m;
for(int i = 1; i <= m; ++ i){
int x, y, z;
cin >> x >> y >> z;
add(x, y, z);
add(y, x, z);
//存双向边
}
//以下为初始化过程
for(int i = 1; i <= n; ++ i)
ans[i] = ma;
//将每个节点到初始节点的距离设为无穷大,方便更新答案
for(int i = head[s]; i; i = e[i].next)
ans[e[i].to] = e[i].cost;
//步骤1
ans[s] = 0;//初始节点到初始节点的距离为0
flag[s] = true;
//步骤2
while(1){
//步骤 6
int tmp = ma, pos = s;
for(int i = 1; i <= n; ++ i)
if(!flag[i] && tmp > ans[i]){
tmp = ans[i];
pos = i;
}
//步骤3
if(pos == s) break;
//步骤7
flag[pos] = true;
//步骤4
for(int i = head[pos]; i; i = e[i].next)
if(!flag[e[i].to] && ans[e[i].to] > ans[pos] + e[i].cost)
ans[e[i].to] = ans[pos] + e[i].cost;
//步骤5
}
for(int i = 1; i <= n; ++ i)
cout<< ans[i] << " ";
//步骤8
return 0;
}
并查集(路径压缩)
算法思想
总结四个字:路径压缩
其实就是把每个点的终点直接连接到最老的祖宗上(起点),避免了深搜的高时间复杂度,好用!!!
代码实现(并查集)
//P3367 【模板】并查集
#include <bits/stdc++.h>
using namespace std;
const int N = 节点数;
int fa[N];
int find(int x){
if(fa[x] != x)
fa[x] = find(fa[x]);
return fa[x];
}//这个是找祖宗(核心代码)
int main() {
int n, m, x, y, z;
cin >> n >> m ;
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
//在最一开始将每个人的祖宗设为自己
for (int i = 1; i <= m; i++) {
cin >> z >> x >> y ;
int a = hfind(x), b = hfind(y);
if (z == 1) {
if (a != b) {
fa[b] = a;
//注意一定是改祖宗的祖宗!(有点别扭是怎么回事)(核心代码x2)
}
} else {
if (fa[x] == fa[y]) {
cout << "Y" << endl;
} else {
cout << "N" << endl;
}
}
}
return 0;
}
拓扑排序
算法思想
拓扑排序是在有向无环图里实现的!!!
-
首先讲清楚入度和出度
- 入度:就是有向的一对点对,被指(通向)的点
- 出度:和入度相反,指向的点
- 如 <a, b> (指的是 a -> b)
a 的入度就是 0 , 出度就是 1
b 的入度就是 1 , 出度就是 0
-
具体步骤
- 首先将入度和出度都初始化好
- 然后枚举入度,直到枚举到入度为0的点,进栈(栈好用)
- 取出栈顶的点,枚举以栈顶点为起点,i为终点(<栈顶点,i>)
然后将i的 入度-- ,判断若入度为0则进栈
重复 3. 步骤 - 直到栈为空时,跳出循环,拓扑排序已完成
-
注意:拓扑排序不唯一
代码实现
//B3644 【模板】拓扑排序 / 家谱树 #include <bits/stdc++.h>
using namespace std;
const int N = 105; vector<int> e[N];//邻接表存边 int ind[N], outd[N];//存入度和出度 stack<int> a;//栈实现思想 vector<int> ans;//存拓扑排序的顺序
int main() { ans.push_back(1);//方便从1输出 int n; cin >> n; for (int i = 1; i <= n; ++ i) { int x; cin >> x; while (x) { e[i].push_back(x); ind[x] ++; outd[i] ++;//入度出度初始化 cin >> x; } } for (int i = 1; i <= n; ++ i) if (!ind[i]) a.push(i);//步骤2 while (!a.empty()) {//步骤3 int u = a.top();//取出栈顶 a.pop();//弹出栈顶 ans.push_back(u); for (int i = 0; i < (int)e[u].size(); ++ i) { int v = e[u][i];//指向的点 ind[v]--; if (ind[v] == 0) a.push(v); } }
for (int i = 1; i < (int)ans.size(); ++ i)
cout << ans[i] << " ";//完美输出~
return 0;//功德圆满
}
# 动态规划(dp
- 奉上抄来的步骤
1. 定义子问题:将原问题划分为若干子问题,这些子问题应具有最优子结构的特性,即原问题的最优解可以通过子问题的最优解推导出来。
2. 构建状态转移方程:通过观察子问题之间的关系,定义状态和状态之间的转移方式。这个方程描述了问题的最优解如何由子问题的最优解计算得出。
3. 确定边界条件:确定最简单的子问题的解,也就是边界条件。这些边界条件将作为递归或迭代的终止条件。
4. 解决子问题:按照定义的状态转移方程,从最简单的子问题开始逐步解决更复杂的子问题,直到解决原问题。
5. 记忆化或建表:为了避免重复计算,可以使用记忆化技术将子问题的解存储起来,或者使用表格/数组记录子问题的解。
6. 求解原问题:通过解决子问题,得到原问题的最优解。
# 最小生成树算法
> 首先双手奉上[这个比较易懂的讲解](https://www.bilibili.com/video/BV1wG411z79G?vd_source=8b6498594bdc0b225db7065c99c525c1)
> ## Prim算法(加点)
> - 适合稠密图(视频里说的嘿嘿)