算法基础模板整理(基础图论篇)
拓扑排序
bool topo(){
queue<int> q;
for(int u = 1; u <= n; u ++ )
if(!ind[u]) q.push(u);
int cnt = 0;
while(!q.empty()){
int u = q.front();
q.pop();
cnt ++ ;
ans.push_back(u);
for(int i = h[u]; i != -1; i = ne[i]){
int v = e[i];
if(!(--ind[v])) q.push(v);
}
}
return cnt == n;
}
最短路
朴素Dijkstra
int dijkstra(){
memset(dist, 0x3f, sizeof(dist)); dist[1] = 0;
//循环n次 将n个点都加入最短路径网络(树)
for(int i = 0; i < n; i ++ ){
int t = -1; //找到网络外距离源点最近的点
for(int v = 1; v <= n; v ++ )
if(!st[v] && (t == -1 || dist[v] < dist[t]))
t = v;
st[t] = true; //加入最短路径网络 作为扩展点
for(int j = h[t]; j != -1; j = ne[j]){
int to = e[j], c = w[j];
if(!st[to]) dist[to] = min(dist[to], dist[t] + c);
}
}
return dist[n] == inf ? -1 : dist[n];
}
堆优化Dijkstra
int dijkstra(){
memset(dist, 0x3f, sizeof(dist)); dist[1] = 0;
priority_queue<pii,vector<pii>,greater<pii>> q; q.push({0, 1});
while(!q.empty()){
auto [d, t] = q.top();
q.pop();
if(st[t]) continue; //有的点可能被重复加入
st[t] = true;
for(int i = h[t]; i != -1; i = ne[i]){
int v = e[i], c = w[i];
if(dist[v] > d + c){
dist[v] = d + c;
q.push({dist[v], v});
}
}
}
return dist[n] == inf ? -1 : dist[n];
}
SPFA
int spfa(){
memset(dist, 0x3f, sizeof(dist));
queue<int> q; q.push(1);
dist[1] = 0, st[1] = true;
while(!q.empty()){
int t = q.front();
q.pop();
st[t] = false; //重新标记为false 之后有可能再次入队
for(int i = h[t]; i != -1; i = ne[i]){
int v = e[i], c = w[i];
if(dist[v] > dist[t] + c){
dist[v] = dist[t] + c;
if(!st[v]) q.push(v), st[v] = true;
}
}
}
return dist[n];
}
SPFA判断负环
int spfa(){
queue<int> q;
for(int i = 1; i <= n; i ++ ) q.push(i), st[i] = true;//所有点放入点集
//只遍历已经更新过距离的点,避免Bellman_Ford中每次遍历所有边的情况
while(!q.empty()){
int t = q.front();
q.pop();
st[t] = false; //重新标记为false 之后有可能再次入队
for(int i = h[t]; i != -1; i = ne[i]){
int v = e[i], c = w[i];
if(dist[v] > dist[t] + c){
dist[v] = dist[t] + c;
cnt[v] = cnt[t] + 1; //记录cnt
if(cnt[v] >= n) return true; //存在负环
if(!st[v]) q.push(v), st[v] = true;
}
}
}
return false;
}
SPFA SLF优化
void spfa(){
memset(dist, 0x3f, sizeof(dist));
deque<int> q; q.push_back(src);
st[src] = true, dist[src] = 0;
while(!q.empty()){
...
//SLF优化
for(int i = h[t]; i != -1; i = ne[i])
...
if(!q.empty() && dist[v] < dist[q.front()])
q.push_front(v);
else q.push_back(v);
st[v] = true;
...
}
}
}
Floyd
void init(){
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= n; j ++ )
if(i == j) continue;
else dist[i][j] = inf;
}
void floyd(){
for(int k = 1; k <= n; k ++ )
for(int i = 1; i <= n; i ++ )
for(int j = 1; j <= n; j ++ )
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}
最小生成树
Prim
int prim(){
memset(dist, 0x3f, sizeof(dist));
int ans = 0;
for(int i = 0; i < n; i ++ ){
int t = -1; //集合外最近的点作为扩展点
for(int v = 1; v <= n; v ++ )
if(!st[v] && (t == -1 || dist[v] < dist[t]))
t = v;
if(i && dist[t] == inf) return inf; //不存在最小生成树
if(i) ans += dist[t];
st[t] = true;
for(int v = 1; v <= n; v ++ )
dist[v] = min(dist[v], g[t][v]);
}
return ans;
}
Kruskal
struct edge{
int u, v, w;
}e[M];
int fa[N], n, m, cnt, ans;
int find(int x){
return fa[x] == x ? x : (fa[x] = find(fa[x]));
}
int kruskal(){
for(int i = 1; i <= n; i ++ ) fa[i] = i;
sort(e + 1, e + m + 1, [](struct edge &a, struct edge &b){
return a.w < b.w;
});
for(int i = 1; i <= m; i ++ ){
int fu = find(e[i].u), fv = find(e[i].v);
if(fu == fv) continue; //在同一个集合中 会形成环
fa[fv] = fu;
ans += e[i].w;
cnt ++ ;
}
if (cnt < n - 1) return inf;
return ans;
}
Tarjan
Tarjan求LCA
void tarjan(int u){
st[u] = 1;
for(int i = h[u]; i != -1; i = ne[i]){
int v = e[i];
if(!st[v]){
tarjan(v);
fa[v] = u;
}
}
for(auto [v, id] : query[u]){
if(st[v]){
int anc = find(v); //最近公共祖先 find和并查集一样
if(anc == u) res[id] = 1;
else if(anc == v) res[id] = 2;
else res[id] = 0;
}
}
}
Tarjan求树上两点最近距离
//深搜求出节点到根节点的距离
void dfs(int u, int fa){
for(int i = h[u]; i != -1; i = ne[i]){
int v = e[i];
if(fa == v) continue;
dist[v] = dist[u] + w[i];
dfs(v, u);
}
}
void tarjan(int u){
st[u] = true;
...
for(auto [v, id] : query[u]){
if(st[v]){
int anc = find(v); //如果被访问过 那么LCA为find(v)
res[id] = dist[u] + dist[v] - 2 * dist[anc];
//两点距离为两点到根节点的距离和减去两倍的LCA根节点的距离
}
}
}
Tarjan判断割边
void tarjan(int u, int in_edge){
dfn[u] = low[u] = ++ num;
st[ ++ top] = u;
for(int i = h[u]; i != -1; i = ne[i]){
int v = e[i];
if(!dfn[v]){
tarjan(v, i); //自底向上
low[u] = min(low[u], low[v]); //更新当前节点的最小时间戳
if(low[v] > dfn[u]) //无法回退到u
bridge[i] = bridge[i ^ 1] = true; //割边
}else if(i != (in_edge ^ 1))
low[u] = min(low[u], dfn[v]); //处理回退边
}
if(dfn[u] == low[u]){ //双连通分量起点u
dcc_cnt ++ ; int v;
do{ v = st[top -- ]; id[v] = dcc_cnt; //割边
}while(v != u);
}
}
int ans(){
for(int i = 0; i < idx; i ++ ) if(bridge[i]) d[id[e[i]]] ++ ;
//边i为割边 那么两边度数 + 1
for(int i = 1; i <= dcc_cnt; i ++ ) if(d[i] == 1) cnt ++ ;
return (cnt + 1) / 2;
}
Tarjan判断割点
void tarjan(int u, int in_edge){
dfn[u] = low[u] = ++ num;
int flag = 0;
for(int i = h[u]; i != -1; i = ne[i]){
int v = e[i];
if(!dfn[v]){
tarjan(v, i); //自底向上
low[u] = min(low[u], low[v]); //更新当前节点的最小时间戳
if(low[v] >= dfn[u]){
flag ++ ;
if(u != root || flag > 1) cut[u] = true;
}
}else low[u] = min(low[u], dfn[v]);
}
}
二分图
染色法判定二分图
bool dfs(int u, int c){
color[u] = c;
for(int i = h[u]; i != -1; i = ne[i]){
int v = e[i];
if(!color[v]){ //如果没有被染过色
if(!dfs(v, 3 - c)) return false; //自底向上 深度染色关联节点
}else if(color[v] == c) return false; //已经染色 但是相邻颜色相同
}
return true;
}
bool isBigraph(){
bool flag = true;
for(int i = 1; i <= n; i ++ )
if(!color[i])
if(!dfs(i, 1)){
flag = false;
break;
}
return flag;
}
匈牙利算法 二分图的最大匹配
bool find(int u){
for(int i = h[u]; i != -1; i = ne[i]){ //询问所有与u相关联的男生
int v = e[i];
if(!st[v]){ //男生还没被预定
st[v] = true;
if(match[v] == 0 || find(match[v])){
//如果男生还没有女朋友 或者 男生更换原配换成现在的女孩
match[v] = u; //v的女朋友是u
return true;
}
}
}
return false;
}
int max_match(){
for(int i = 1; i <= n1; i ++ ){
memset(st, 0, sizeof(st));
if(find(i)) res ++ ;
}
return res;
}
一切都是命运石之门的选择,本文章来源于博客园,作者:MarisaMagic,出处:https://www.cnblogs.com/MarisaMagic/p/17318016.html,未经允许严禁转载