为省赛整理的常用算法模板,图论部分
最短路 Bellman-Ford 算法
松弛操作
从起始点开始对图中的每一条边进行一次松弛操作,重复这样的操作直到所有的点都达到最小的路径值
Bellman-Ford算法中判断是否有负圈
在上述算法中,最多更新V-1次就可以算出每一个结点的最短路径,如果更新超过V-1次,就说明有负圈
kuangbin模板:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33int dist[MAXN];
struct Edge
{
int u,v;
int cost;
Edge(int _u=0,int _v=0,int _cost=0):u(_u),v(_v),cost(_cost){}
};
vector<Edge>E;
bool bellman_ford(int start,int n)//点的编号从1开始
{
for(int i=1;i<=n;i++)dist[i]=INF;
dist[start]=0;
for(int i=1;i<n;i++)//最多做n-1次
{
bool flag=false;
for(int j=0;j<E.size();j++)
{
int u=E[j].u;
int v=E[j].v;
int cost=E[j].cost;
if(dist[v]>dist[u]+cost)
{
dist[v]=dist[u]+cost;
flag=true;
}
}
if(!flag)return true;//没有负环回路
}
for(int j=0;j<E.size();j++)
if(dist[E[j].v]>dist[E[j].u]+E[j].cost)
return false;//有负环回路
return true;//没有负环回路
}
最短路 Dijkstra 算法
将结点分成两大类,一类已经确定最短路的,一类还没有确定最短路的。每次从没有确定最短路的结点集当中取出一个‘距离’已经确定最短路的结点集中最小的点,加入。
不能够处理负边的问题,遇到负边结果出错
邻接矩阵表示
1 | int cost[maxn][maxn]; //邻接矩阵建图 |
时间复杂度为O(V^2)
邻接链表(未验证,慎用)
1 |
|
时间复杂度就变成了:O(ElogV)
最短路 SPFA算法
链式前向星
链式前向星是一种保存图的非常好用的方法,他实际上是一个边的集合,在这个集合的基础上利用head数组来记录每个点的第一条边的信息,添加边的时候是逆序添加的
1 | //链式前向星建图 |
SPFA 算法
SPFA算法实在Bellman-Ford算法的基础上加了一个队列优化,用来减少冗余的松弛操作。
1 | int visit[maxn],dist[maxn]; |
判断负环用SPFA
kuangbinp判断负环SPFA:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50const int MAXN=1010;
const int INF=0x3f3f3f3f;
struct Edge
{
int v;
int cost;
Edge(int _v=0,int _cost=0):v(_v),cost(_cost){}
};
vector<Edge>E[MAXN];
void addedge(int u,int v,int w)
{
E[u].push_back(Edge(v,w));
}
bool vis[MAXN];
int cnt[MAXN];
int dist[MAXN];
bool SPFA(int start,int n)
{
memset(vis,false,sizeof(vis));
for(int i=1;i<=n;i++)dist[i]=INF;
dist[start]=0;
vis[start]=true;
queue<int>que;
while(!que.empty())que.pop();
que.push(start);
memset(cnt,0,sizeof(cnt));
cnt[start]=1;
while(!que.empty())
{
int u=que.front();
que.pop();
vis[u]=false;
for(int i=0;i<E[u].size();i++)
{
int v=E[u][i].v;
if(dist[v]>dist[u]+E[u][i].cost)
{
dist[v]=dist[u]+E[u][i].cost;
if(!vis[v])
{
vis[v]=true;
que.push(v);
if(++cnt[v]>n)return false;
//有负环回路
}
}
}
}
return true;
}
多源最短路 Floyd算法
动态规划思想,不断考虑每一个结点,其状态转移方程为:
map[i,j]:=min{map[i,k]+map[k,j],map[i,j]};
map[i,j]表示i到j的最短距离,K是穷举i,j的断点,map[n,n]初值应该为0,或者按照题目意思来做。
当然,如果这条路没有通的话,还必须特殊处理,比如没有map[i,k]这条路。
代码;
1 | int d[MAX_V][MAX_V]; |
最小生成树问题
从点出发的Prim算法
1 | int cost[maxn][maxn]; |
从边出发的Kruskal算法
1 | int pre[maxn]; |
拓扑排序
给出各个节点的先后顺序,输出一个拓扑序
1 | int head[maxn],ip,indegree[maxn]; //indegree为入度数组,ip为边计数变量 |
最小树型图——朱刘算法
1 | struct edge |
二分图的匹配问题(匈牙利算法)
给出一个二分图,求出这个二分图的最大匹配数
1 | /* ************************************************************************** |
二分图的完备匹配问题 (KM算法)
如果二分图的每条边都有一个权(可以是负数),要求一种完备匹配方案,使得所有匹配边的权和最大,记做最佳完美匹配。(特殊的,当所有边的权为1时,就是最大完备匹配问题)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73iint nx,ny; //两边的点数
int g[maxn][maxn]; //建图
int linker[maxn],lx[maxn],ly[maxn]; //y中欧各个点的匹配状态,x y 中各个点的标号
int slack[maxn]; //松弛量数组
bool visx[maxn],visy[maxn];
bool dfs(int x)
{
visx[x]=true;
for(int y=0;y<ny;y++)
{
if(visy[y]) continue;
int tmp=lx[x]+ly[y]-g[x][y];
if(tmp==0) //顶标符合要求,匈牙利算法寻找完备匹配
{
visy[y]=true;
if(linker[y]==-1 || dfs(linker[y]))
{
linker[y]=x;
return true;
}
}
else if(slack[y]>tmp)
slack[y] =tmp; //更新松弛量
}
return false;
}
int KM()
{
memset(linker,-1,sizeof(linker)); //初始化一开始匹配的点
memset(ly,0,sizeof(ly)); //y的顶标先全部变成0
//初始化顶标
for(int i=0;i<nx;i++)
{
lx[i]= -INF; //求最大值,首先初始化为最小值
for(int j=0;j<ny;j++)
if(g[i][j]>lx[i]) lx[i]=g[i][j];
}
for(int x=0;x<nx;x++)
{
for(int i=0;i<ny;i++)
slack[i]=INF;
//算法核心部分
while(true)
{
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));
if(dfs(x)) break; //找到完备匹配,退出
//没有找到完备匹配,在交错树中更改相关节点的顶标值
int d= INF;
//修改顶标,增加相等子图中的边
for(int i=0;i<ny;i++)
if(!visy[i] && d>slack[i])
d=slack[i]; //求出最小一个slack值
for(int i=0;i<nx;i++) //在交错树中的x的节点减少d
if(visx[i])
lx[i]-=d;
for(int i=0;i<ny;i++) //在交错树中的y的节点增加d
{
if(visy[i]) ly[i]+=d;
else
slack[i] -=d; //x值减小,则相对的slack的值就会增大
}
}
}
int res=0;
for(int i=0;i<ny;i++)
if(linker[i]!=-1)
res+=g[linker[i]][i];
return res;
}
最大图问题
通俗来讲最大团问题就是在一个无向图中找出一个点数最多的完全图。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42bool G[maxn][maxn];
int Max[maxn],Alt[maxn][maxn],ans,n;
bool dfs(int cur,int tot) //cur 当前层次集合大小 tot 所在的层次
{
if(cur==0)
{
if(tot>ans)
{
ans=tot;
return 1;
}
return 0;
}
for(int i=0;i<cur;i++)
{
if(cur-i+tot<=ans) return 0; //剪枝1
int u=Alt[tot][i]; //选取当前考虑的点
if(Max[u]+tot<=ans) return 0;
int next=0;
for(int j=i+1;j<cur;j++)
{
if(G[u][Alt[tot][j]]) Alt[tot+1][next++]=Alt[tot][j];
}
if(dfs(next,tot+1)) return 1;
}
return 0;
}
void MaxClique()
{
ans=0;
memset(Max,0,sizeof(Max));
for(int i=n-1;i>=0;i--) //逆向构建Max
{
int cur=0;
for(int j=i+1;j<n;j++)
if(G[i][j]) Alt[1][cur++]=j;
dfs(cur,1);
Max[i]=ans;
}
}
最大流问题
给定指定的一个有向图,其中有两个特殊的点源S(Sources)和汇T(Sinks),每条边有指定的容量(Capacity),求满足条件的从S到T的最大流(MaxFlow).
1 | int G[SIZE_V][SIZE_V]; |