图论算法.docx
《图论算法.docx》由会员分享,可在线阅读,更多相关《图论算法.docx(18页珍藏版)》请在冰豆网上搜索。
图论算法
1.Dijkstra
1)适用条件&范围:
a)单源最短路径(从源点s到其它所有顶点v);
b)有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图)
c)所有边权非负(任取(i,j)∈E都有Wij≥0);
constintmaxn=160;
#defineIFN9999999
intmap[maxn][maxn];
intdis[maxn];
voiddijkstra(ints,intn)
{
inti,j,k;
intmin;
boolp[maxn];
for(i=1;i<=n;i++)
{
p[i]=false;
dis[i]=map[s][i];
}
dis[s]=0;
p[s]=true;
for(i=1;i<=n;i++)
{
min=IFN;
k=0;
for(j=1;j<=n;j++)
{
if(!
p[j]&&dis[j]{
min=dis[j];
k=j;
}
}
p[k]=true;
for(j=1;j<=n;j++)
{if(!
p[j]&&map[k][j]!
=IFN&&dis[j]>dis[k]+map[k][j])
{
dis[j]=dis[k]+map[k][j];
}
}
}}
intmap[maxn][maxn];
intc[maxn][maxn];
intdis[maxn];
intcost[maxn];
intn,m;
voiddijkstra(ints,intend)
{
inti,j,k;
intmin;
boolp[maxn];
for(i=1;i<=n;i++)
{
p[i]=false;
dis[i]=map[s][i];
cost[i]=c[s][i];
}
dis[s]=0;
cost[s]=0;
p[s]=true;
for(i=1;i<=n;i++)
{
min=IFN;
k=0;
for(j=1;j<=n;j++)
{
if(!
p[j]&&dis[j]{
min=dis[j];
k=j;
}
}
p[k]=true;
for(j=1;j<=n;j++)
{
if(!
p[j]&&map[k][j]!
=IFN&&dis[j]>dis[k]+map[k][j])
{
dis[j]=dis[k]+map[k][j];
cost[j]=cost[k]+c[k][j];
}
elseif(!
p[j]&&map[k][j]!
=IFN&&dis[j]==dis[k]+map[k][j]&&cost[j]>cost[k]+c[k][j])
{
cost[j]=cost[k]+c[k][j];
}
}
}
printf("%d%d\n",dis[end],cost[end]);
2.Floyd-Warshall
1)适用范围:
a)APSP(AllPairsShortestPaths)
b)稠密图效果最佳
c)边权可正可负
intFloyd()
{
inti,j,k;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
for(k=1;k<=n;k++)
if(p[j][k]
intflag=1;
for(i=1;i<=n;i++)
if(p[i][i]>1.0){flag=0;return1;}
if(flag)return0;
}}
最大流算法:
Dinic算法:
#defineINF0x1f1f1f1f
#defineMIN(a,b)((a)<(b)?
(a):
(b))
#defineMAX(a,b)((a)>(b)?
(a):
(b))
#defineN500
intcap[N][N];//容量
intflow[N][N];//流量
intlev[N];//层次
boolvis[N];//标记
intque[100000];//队列
//BSF找层次网络,一次寻找多条增广路径
//st最小顶点标号,ed最大顶点标号,src源点标号,tar汇点标号
boolbfs(intst,inted,intsrc,inttar)//st最小点ed最大点src源点tar汇点
{
intfront;//队首
intrear;//队尾
front=rear=0;
que[front++]=src;
lev[src]=0;
memset(vis,0,sizeof(vis));
vis[src]=1;
while(rear{
intt=que[rear];
rear++;
for(inti=st;i<=ed;i++)
{
if(!
vis[i]&&cap[t][i]>flow[t][i])
{
vis[i]=1;
lev[i]=lev[t]+1;
que[front++]=i;
}
}
}
returnlev[tar]}
//利用层次网络进行增广,每次DFS寻找的是从该节点出发进行DFS增加的总流量
//mn表示从源点至该节点可增广流量
intdfs(intv,intst,inted,inttar,intfl)//fl表示源点到当前顶点的流量
{
intret=0;
if(v==tar||fl==0)returnfl;
for(inti=st;i<=ed;i++)
{
if(fl==0)break;
if(cap[v][i]>flow[v][i]&&lev[v]+1==lev[i])
{
intf=MIN(fl,cap[v][i]-flow[v][i]);//沿i点向下可用最大流量
inttt=dfs(i,st,ed,tar,f);//沿i点向下实际增广的流量
if(tt<=0)continue;
ret+=tt;
fl-=tt;//每次修改fl
flow[v][i]+=tt;
flow[i][v]-=tt;
}
}
returnret;
}
intdinic(intst,inted,intsrc,inttar)
{
intret=0;
while(bfs(st,ed,src,tar))//存在可增广路
{
intr=dfs(src,st,ed,tar,INF);
if(r==0)break;
ret+=r;
}
returnret;
}
-49-
3.11最小费用最大流编写:
程宪庆校核:
周洲
3.11.1基本原理
最小费用最大流问题是在普通的最大流问题上加了另一个条件,即要每条边单位流量所需的费用,要在求得最大流的情况下所需费用最小。
求最小费用可以转化成求解一个最短路问题,而求最大流需要广度搜索,所以可以使用SPFA最短路算法和EK最大流算法结合的方法求解最小费用最大流问题。
3.11.2模板代码
#defineINF0x3f3f3f3f
#defineMIN(a,b)((a)<(b)?
(a):
(b))
intmat[55][55];
intn,k;
inthead[5005];
structArc
{
intnext_arc;
intpoint;
intadj;
intcost;
intcap;
};
structArcarc[25000];
intpre[5005];
intdis[5005];
boolfl[5005];
intmax_flow;
intmin_cost;
intedge_cnt;
voidadd(intu,intv,intcst,intcp)
{
arc[edge_cnt].next_arc=head[u];
arc[edge_cnt].point=v;
arc[edge_cnt].adj=u;
arc[edge_cnt].cost=cst;
arc[edge_cnt].cap=cp;
head[u]=edge_cnt;
}
voidcost_flow(intsrc,inttar)
{
while
(1)
{
memset(pre,-1,sizeof(pre));
memset(dis,0x3f,sizeof(dis));
memset(fl,0,sizeof(fl));
queueq;
q.push(src);
dis[src]=0;
while(!
q.empty())
{
intu=q.front();
q.pop();
fl[u]=0;
for(inte=head[u];e!
=-1;e=arc[e].next_arc)
{
if(arc[e].cap>0&&dis[u]+arc[e].cost{
dis[arc[e].point]=dis[u]+arc[e].cost;
pre[arc[e].point]=e;
if(!
fl[arc[e].point])
{
fl[arc[e].point]=1;
q.push(arc[e].point);
}
}
}
}
if(pre[tar]==-1)break;
intmin=INF;
for(inti=tar;pre[i]!
=-1;i=arc[pre[i]].adj)
{
min=MIN(min,arc[pre[i]].cap);
}
for(inti=tar;pre[i]!
=-1;i=arc[pre[i]].adj)
{
arc[pre[i]].cap-=min;
arc[pre[i]^1].cap+=min;
}
max_flow+=min;
min_cost+=min*dis[tar];
}
}
2.树的最小支配集,最小点覆盖与最大独立集
深度优先遍历,得到深度优先遍历序列。
intp[maxn];
boolselect[maxn];
intnewpos[maxn];
intnow;
intn,m;
voidDFS(intx)
{
newpos[now++]=x;
intk;
for(k=head[x];k!
=-1;k=edge[k].next)
{
if(!
select[edge[k].to])
{
select[edge[k].to]=true;
p[edge[k].to]=x;
DFS(edge[k].to);
}
}
}
对于最小支配集,贪心函数如下:
intgreedy()
{
bools[maxn]={0};
boolset[maxn]={0};
intans=0;
inti;
for(i=n-1;i>=0;i--)
{
intt=newpos[i];
if(!
s[t])
{
if(!
set[p[t]])
{
set[p[t]]=true;
ans++;
}
s[t]=true;
s[p[t]]=true;
s[p[p[t]]]=true;
}
}
returnans;
}
对于最小点覆盖,贪心函数如下:
intgreedy()
{
bools[maxn]={0};
boolset[maxn]={0};
intans=0;
inti;
for(i=n-1;i>=1;i--)
{
intt=newpos[i];
if(!
s[t]&&!
s[p[t]])
{
set[p[t]]=true;
ans++;
s[t]=true;
s[p[t]]=true;
}
}
returnans;
}
对于最大独立集,贪心函数如下:
intgreedy()
{
bools[maxn]={0};
boolset[maxn]={0};
intans=0;
inti;
for(i=n-1;i>=0;i--)
{
intt=newpos[i];
if(!
s[t])
{
set[t]=true;
ans++;
s[t]=true;
s[p[t]]=true;
}
}
returnans;
}
使用样例:
intmain()
{
/*读入图信息*/
memset(select,0,sizeof(select));
now=0;
select[1]=true;
p[1]=1;
DFS
(1);
printf("%d\n",greedy());
}
该方法经过一次深度优先遍历和一次贪心得到最终解,第一步的时间复杂度是
O(m),由于这是一棵树,m=n-1。
第二步是O(n),一共是O(n)。
在下面的代码中,u表示当前正在处理的节点,p表示u节点的父节点。
对于最小支配集,动态规函数如下:
voidDP(intu,intp)
{
dp[u][2]=0;
dp[u][0]=1;
bools=false;
intsum=0,inc=INF;
intk;
for(k=head[u];k!
=-1;k=edge[k].next)
{
intto=edge[k].to;
if(to==p)
continue;
DP(to,u);
dp[u][0]+=min(dp[to][0],min(dp[0][1],dp[to][2]));
if(dp[to][0]<=dp[to][1])
{
sum+=dp[to][0];
s=true;
}
else
{
sum+=dp[to][1];
inc=min(inc,dp[to][0]-dp[to][1]);
}
if(dp[to][1]!
=INF&&dp[u][2]!
=INF)
dp[u][2]+=dp[to][1];
elsedp[u][2]=INF;
}
if(inc==INF&&!
s)
dp[u][1]=INF;
else
{
dp[u][1]=sum;
if(!
s)
dp[u][1]+=inc;
}
}
对于最小点覆盖,动态规划函数如下:
voidDP()
{
dp[u][0]=1;
dp[u][1]=0;
intk,to;
for(k=head[u];k!
=-1;k=edge[k].next)
{
to=edge[k].to;
if(to==p)
continue;
DP(to,u);
dp[u][0]+=min(dp[to][0],dp[to][1]);
dp[u][1]+=dp[to][0];
}
}
对于最大独立集,动态规划函数如下:
voidDP()
{
dp[u][0]=1;
dp[u][1]=0;
intk,to;
for(k=head[u];k!
=-1;k=edge[k].next)
{
to=edge[k].to;
if(to==p)
continue;
DP(to,u);
dp[u][0]+=dp[to][1];
dp[u][1]+=max(dp[to][0],dp[to][1]);
}
}
二分图最大匹配
模板代码
#include
#include
usingnamespacestd;
intn,k;//n矩阵规格,k星体数量
intV1,V2;//二分图顶点集
/*矩阵的行列分别属于二分图的两个顶点集V1、V2,其中行x∈V1,列y∈V2*/
boolgrid[501][501];//存储数据方式:
可达矩阵
boolvis[501];//记录V2的点每个点是否已被搜索过
intlink[501];//记录V2中的点y在V1中所匹配的点x的编号
intm;//最大匹配数
booldfs(intx)
{
for(inty=1;y<=V2;y++)
if(grid[x][y]&&!
vis[y])//x到y相邻(有边)且节点y未被搜索
{
vis[y]=true;//标记节点y已被搜索
if(link[y]==0||dfs(link[y]))//link[y]==0:
如果y不属于前一个匹配M
{//find(link[y]:
如果被y匹配到的节点可以寻找到增广路
link[y]=x;//那么可以更新匹配M'(用M替代M')
returntrue;//返回匹配成功的标志
}
}
returnfalse;//继续查找V1下一个x的邻接节点
}
voidsearch(void)
{
for(intx=1;x<=V1;x++)
{
memset(vis,false,sizeof(vis));//清空上次搜索时的标记
if(dfs(x))//从V1中的节点x开始寻找增广路
m++;
}
return;
}
intmain(void)
{
cin>>n>>k;
V1=V2=n;
intx,y;//坐标(临时变量)
for(inti=1;i<=k;i++)
{
cin>>x>>y;
grid[x][y]=true;//相邻节点标记
}
search();
cout<return0;
}
强连通
voidtarjan(inti)
{
intj;
DFN[i]=LOW[i]=++Dindex;
instack[i]=true;
Stap[++Stop]=i;
for(edge*e=V[i];e;e=e->next)
{
j=e->t;
if(!
DFN[j])
{
tarjan(j);
if(LOW[j]LOW[i]=LOW[j];
}
elseif(instack[j]&&DFN[j]LOW[i]=DFN[j];
}
if(DFN[i]==LOW[i])
{
Bcnt++;
do
{
j=Stap[Stop--];
instack[j]=false;
Belong[j]=Bcnt;
}
while(j!
=i);
}
}
voidsolve()
{
inti;
Stop=Bcnt=Dindex=0;
memset(DFN,0,sizeof(DFN));
for(i=1;i<=N;i++)
if(!
DFN[i])
tarjan(i);
}