图论基础算法(持续更新)
2014-12-31 00:43
417 查看
1.dfs性质
2.求连通分量
3.判定一个图是否为二分图&&无向图构造二分图
二分图定义:对于无向图G=<V,E>,如果可以把点集分为互不相交的两部分,即X和Y=V-X,使得每一条边的其中一个端点在X中,另一个端点在Y中,则称该图是二分图
非连通图是二分图 <=> 每个连通分量都是是二分图
4.求割点/割顶(cut_vertex/articulation vertex)和桥(bridge)
割点定义:删除该点后,原图的连通分量数增加。对于连通图,就是删除该点后原图不再连通的点。
桥的定义:删除该边后,原图的连通分量数增加。对于连通图,就是删除该边后原图不在连通的边。
5.寻找双连通分量(biconnected component)---割顶属于最后一个标记它的bcc中
双连通分量定义:任意两点间至少存在两条“点不重复”路径/任意两点都在同一个简单环中
与割顶关系:等价定义:内部无割顶
6.寻找边双连通分量和桥(含重边)
将父亲的判断改为边和反向边的判断
7.寻找强连通分量,建立scc图
等价条件:相互可达,不存在割顶和桥的概念
附加函数:
sum[i]:i所在分量权值和,作为scc图中节点的权值
1.判断该图是否为强连通分量:scc_cnt==1 <=>该图是强连通分量
8.最短路算法
1)dij优先队列算法
2)spfa算法
我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且 v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
定理 只要最短路径存在,上述SPFA算法必定能求出最小值。
证明:每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。(证毕)
在平均情况下,SPFA算法的期望时间复杂度为O(E)
3)spfa判断图中是否存在负环
将2)中***1代码改为:
9.最小生成树算法
1)kruscal算法:计算并建立mst
10.KM算法
int nx,ny;
int linker
,lx
,ly
,slack
; //lx,ly为顶标,nx,ny分别为x点集y点集的个数
int visx
,visy
,w
;
int DFS(int x){
visx[x]=1;
for(int y=1;y<=ny;y++){
if(visy[y]) continue;
int tmp=lx[x]+ly[y]-w[x][y];
if(tmp==0){
visy[y]=1;
if(linker[y]==-1||DFS(linker[y])){
linker[y]=x; return 1;
}
}
else if(slack[y]>tmp) slack[y]=tmp;
}
return 0;
}
int KM(){
memset(linker,-1,sizeof(linker)),memset(ly,0,sizeof(ly));
int i;
for(i=1,lx[i]=INF;i<=nx;i++) for(int j=1;j<=ny;j++)
if(w[i][j]>lx[i]) lx[i]=w[i][j];
for(int x=1;x<=nx;x++){
for(i=1;i<=ny;i++) slack[i]=INF;
while(1){
memset(visx,0,sizeof(visx)),memset(visy,0,sizeof(visy));
if(DFS(x)) break;
int d=INF;
for(i=1;i<=ny;i++)
if(!visy[i]&&d>slack[i]) d=slack[i];
for(i=1;i<=nx;i++)
if(visx[i]) lx[i]-=d;
for(i=1;i<=ny;i++)
if(visy[i]) ly[i]+=d;
else slack[i]-=d;
}
}
int res=0;
for(i=1;i<=ny;i++)
if(linker[i]!=-1) res+=w[linker[i]][i];
return res;
}
<span style="font-size:18px;">int vis[maxn];//需要将vis数组清零 vector<int> g[maxn]; void dfs(int u){ vis[u]=1; previsit(u);<strong>//在第一次遍历u之前的操作</strong> for(int i=0;i<g[u].size();i++){ int v=g[u][i]; if(!vis[v]) dfs(v); } postvisit(u);<strong>//在遍历完u所在子树(与u相连的所有边)后的操作</strong> } </span>两个函数的书写对于dfs的拓展应用非常广泛
2.求连通分量
<span style="font-size:18px;">int current_cc,cc[maxn],vis[maxn]; vector<int> g[maxn]; void dfs(int u){ vis[u]=1;cc[u]=current_cc; for(int i=0;i<g[u].size();i++){ int v=g[u][i]; if(!vis[v]) dfs(v); } } void find_cc(int n){//求连通分量 current_cc=0,mem(vis,0); for(int u=1;u<=n;u++) if(!vis[u]) { current_cc++;dfs(u); } } </span>
3.判定一个图是否为二分图&&无向图构造二分图
二分图定义:对于无向图G=<V,E>,如果可以把点集分为互不相交的两部分,即X和Y=V-X,使得每一条边的其中一个端点在X中,另一个端点在Y中,则称该图是二分图
非连通图是二分图 <=> 每个连通分量都是是二分图
<span style="font-size:18px;">int color[maxn]; vector<int> g[maxn]; bool bipartite(int u){ for(int i=0;i<g[u].size();i++){ int v=g[u][i]; if(color[v]==color[u]) return false; if(!color[v]){ color[v]=3-color[u]; if(!bipartite(v)) return false; } } return true; } bool judge_bipartite(){ for(int i=1;i<=n;i++) if(!color[i]){ color[i]=1; if(!bipartite(i)) return false; } return true; } </span>
4.求割点/割顶(cut_vertex/articulation vertex)和桥(bridge)
割点定义:删除该点后,原图的连通分量数增加。对于连通图,就是删除该点后原图不再连通的点。
桥的定义:删除该边后,原图的连通分量数增加。对于连通图,就是删除该边后原图不在连通的边。
<span style="font-size:18px;">int dfs_clock=0,low[maxn],pre[maxn],is_cut[maxn],is_bridge[maxn][maxn]; vector<int> g[maxn]; void dfs(int u,int fa){ low[u]=pre[u]=++dfs_clock;//u一开始最远返向边指向自己 int child=0; for(int i=0;i<g[u].size();i++){ int v=g[u][i]; if(!pre[v]){ child++;//记录孩子个数 dfs(v,u); low[u]=min(low[u],low[v]);//u子树(除u外)的反向边 if(low[v]>=pre[u]) is_cut[u]=1;//u是割点 if(low[v]>pre[u]) is_bridge[u][i]=1;//u出发的第i条边是桥 } else if(pre[v]<pre[u]&&v!=fa) low[u]=min(low[u],pre[v]);//u自己的反向边 } if(fa<0&&child==1) is_cut[u]=0;//对根节点进行特判 } void find_cutvertex_bridge(int n){ mem(is_cut,0),mem(is_bridge,0),mem(pre,0); for(int i=1;i<=n;i++) if(!pre[i]) dfs(i,-1); } </span>
5.寻找双连通分量(biconnected component)---割顶属于最后一个标记它的bcc中
双连通分量定义:任意两点间至少存在两条“点不重复”路径/任意两点都在同一个简单环中
与割顶关系:等价定义:内部无割顶
<span style="font-size:18px;">int low[maxn],pre[maxn],bccno[maxn],bcc_cnt,dfs_clock,is_cut[maxn]; vector<int> g[maxn],bcc[maxn]; struct Edge{ int u,v; }; stack<Edge> S; void dfs(int u,int fa){ low[u]=pre[u]=++dfs_clock; int child=0; for(int i=0;i<g[u][i];i++){ int v=g[u][i]; Edge e=(Edge){u,v}; if(!pre[v]){ S.push(e),child++; dfs(v,u); low[u]=min(low[u],low[v]); if(pre[u]<=low[v]){ is_cut[u]=1; for(;;){ Edge x=S.top();S.pop(); if(bccno[x.u]!=bcc_cnt) { bcc[bcc_cnt].push_back(x.u),bccno[x.u]=bcc_cnt; } if(bccno[x.v]!=bcc_cnt) { bcc[bcc_cnt].push_back(x.v),bccno[x.v]=bcc_cnt; } if(x.u==u&&x.v==v) break; } } } else if(pre[v]<pre[u]&&v!=fa) { S.push(e);low[u]=min(low[u],pre[v]); }//pre[v]<pre[u]不能少 } if(fa<0&&child==1) is_cut[u]=0;//当根节点只有一个子节点时,不再是割点---不能少 } void find_bcc(int n){ mem(pre,0),mem(bccno,0),mem(is_cut,0),dfs_clock=bcc_cnt=0; for(int i=1;i<=n;i++) if(!pre[i]) dfs(i,-1); }</span>
6.寻找边双连通分量和桥(含重边)
将父亲的判断改为边和反向边的判断
<span style="font-size:18px;">int n,m; bool is_bridge[maxm]; int pre[maxn],low[maxn],bccno[maxn],bcc_cnt,dfs_clock,vis_e[maxn]; struct Edge{ int to,next; }edge[maxm*2]; int head[maxn],tot; void add(int u,int v){ edge[tot].to=v,edge[tot].next=head[u],head[u]=tot++; } void dfs(int u){ pre[u]=low[u]=++dfs_clock; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].to; if(!pre[v]){ vis_e[i]=vis_e[i^1]=1; dfs(v); low[u]=min(low[u],low[v]); if(pre[u]<low[v]) is_bridge[i]=is_bridge[i^1]=1;//判断是桥 } else if(!vis_e[i]){ vis_e[i]=vis_e[i^1]=1; low[u]=min(low[u],pre[v]); } } } void dfs2(int u){//标记edge_bcc的编号 bccno[u]=bcc_cnt; for(int i=head[u];i!=-1;i=edge[i].next){ if(!is_bridge[i]&&!bccno[edge[i].to]) dfs2(edge[i].to); } } void find_edge_bcc(){ mem(pre,0),mem(vis_e,0),mem(bccno,0),mem(is_bridge,0),dfs_clock=bcc_cnt=0; for(int i=1;i<=n;i++) if(!pre[i]) dfs(i); for(int i=1;i<=n;i++) if(!bccno[i]) { bcc_cnt++,dfs2(i); } } </span>
7.寻找强连通分量,建立scc图
等价条件:相互可达,不存在割顶和桥的概念
<span style="font-size:18px;">int pre[maxn],lowlink[maxn],dfs_clock,sccno[maxn],scc_cnt,sum[maxn];//lowlink[u]:u通过当前scc中的点可追溯到的最早祖先点v的pre[v]值 stack<int> S; vector<int> scc[maxn]; void dfs(int u){ pre[u]=lowlink[u]=++dfs_clock; for(int i=0;i<g[u].size();i++) if(!pre[g[u][i]]) { dfs(g[u][i]); lowlink[u]=min(lowlink[u],lowlink[g[u][i]]); } else if(!sccno[g[u][i]]) lowlink[u]=min(lowlink[u],pre[g[u][i]]); if(pre[u]==lowlink[u]){ scc_cnt++;scc[scc_cnt].clear(); do{ int x=S.top();S.pop(); sccno[x]=scc_cnt,scc[scc_cnt].push_back(x),sum[scc_cnt]+=quan[x]; }while(x!=u); } } void find_scc(int n){ mem(pre,0),mem(sccno,0),mem(sum,0),scc_cnt=dfs_clock=0; for(int i=1;i<=n;i++) if(!pre[i]) dfs(i); }</span>
<span style="font-size:18px;"></pre><pre name="code" class="cpp">void build_scc(){ for(int i=1;i<=scc_cnt;i++) scc_g[i].clear(); for(int u=1;u<=n;u++){ for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].to; if(sccno[u]!=sccno[v]) scc_g[sccno[u]].push_back(sccno[v]); } } }</span>
附加函数:
sum[i]:i所在分量权值和,作为scc图中节点的权值
1.判断该图是否为强连通分量:scc_cnt==1 <=>该图是强连通分量
8.最短路算法
1)dij优先队列算法
<span style="font-size:18px;">int vis[maxn],d1[maxn]; vector<int> g[maxn][2]; void add (int u,int v,int d){ g[u][0].push_back(v),g[u][1].push_back(d); } struct Heapnode{ int d,u; bool operator<(const Heapnode&rhs) const{ return d>rhs.d; } }; void dij(int s){ priority_queue<Heapnode> q; memset(vis,0,sizeof(vis)); for(int i=1;i<idnow;i++) d1[i]=(i==s)?0:INF; q.push((Heapnode){0,s}); while(!q.empty()){ Heapnode x=q.top();q.pop(); int u=x.u; if(vis[u]) continue; vis[u]=1; for(int i=0;i<g[u][0].size();i++){ int v=g[u][0][i],d2=g[u][1][i]; if(d1[v]>d1[u]+d2){ d1[v]=d1[u]+d2; q.push((Heapnode){d1[v],v}); } } } }</span>
2)spfa算法
我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且 v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
定理 只要最短路径存在,上述SPFA算法必定能求出最小值。
证明:每次将点放入队尾,都是经过松弛操作达到的。换言之,每次的优化将会有某个点v的最短路径估计值d[v]变小。所以算法的执行会使d越来越小。由于我们假定图中不存在负权回路,所以每个结点都有最短路径值。因此,算法不会无限执行下去,随着d值的逐渐变小,直到到达最短路径值时,算法结束,这时的最短路径估计值就是对应结点的最短路径值。(证毕)
在平均情况下,SPFA算法的期望时间复杂度为O(E)
int inq[maxn],cnt[maxn],d[maxn]; int spfa(int s,int t){ queue<int> q; memset(inq,0,sizeof(inq)); memset(cnt,0,sizeof(cnt)); for(int i=0;i<n;i++) { d[i]=(i==s)?0:INF; inq[i]=(i==s)?true:false; }//***1 q.push(s);//***1 while(!q.empty()){ int u=q,front(); q.pop(); inq[u]=false; for(int i=0;i<g[u].size();i++){ int v=g[u][0][i],w=g[u][1][i]; if(d[v]>d[u]+w){ d[v]=d[u]+w; if(!inq[v]{ q.push(v); inq[v]=1; if(++cnt[v]>n) return -INF;//进队n次,存在负环,输出一个极限值,表示此图不能求最短路!***2 } } } } return d[t];//***3
3)spfa判断图中是否存在负环
将2)中***1代码改为:
for(int i=0;i<n;i++) { d[i]=0;inq[0]=true;q.push(i); }***2代码改为:
return true;//存在负环**3代码改为:
return false;//不存在负环
9.最小生成树算法
1)kruscal算法:计算并建立mst
<span style="font-size:18px;">//初始边定义 struct Node{ int from,to,w; bool operator<(const &Node)const{ return w>rhs.w; } }node[maxn]; int p[maxn]; //mst定义 int head[maxn],tot; void add(int u,int v,int w){ edge[tot].to=v,edge[tot].next=head[u],edge[tot].w=w; head[tot++]=u; } int kruscal(){ for(int i=1;i<=n;i++) p[i]=i; sort(node,node+m); int mst=0; memset(vis,0,sizeof(vis)); memset(head,-1,sizeof(head),tot=0) for(int i=0;i<m;i++){ int u=node[i].from,v=node[i].to,w=node[i].w; int ru=Find(u),rv=Find(v); if(ru!=rv){ p[ru]=rv; vis[u][v]=vis[v][u]=1;//标记图中mst树边 add(u,v,w),add(v,u,w);//建立mst mst+=w;//更新mst值 } } return mst; }</span>
10.KM算法
int nx,ny;
int linker
,lx
,ly
,slack
; //lx,ly为顶标,nx,ny分别为x点集y点集的个数
int visx
,visy
,w
;
int DFS(int x){
visx[x]=1;
for(int y=1;y<=ny;y++){
if(visy[y]) continue;
int tmp=lx[x]+ly[y]-w[x][y];
if(tmp==0){
visy[y]=1;
if(linker[y]==-1||DFS(linker[y])){
linker[y]=x; return 1;
}
}
else if(slack[y]>tmp) slack[y]=tmp;
}
return 0;
}
int KM(){
memset(linker,-1,sizeof(linker)),memset(ly,0,sizeof(ly));
int i;
for(i=1,lx[i]=INF;i<=nx;i++) for(int j=1;j<=ny;j++)
if(w[i][j]>lx[i]) lx[i]=w[i][j];
for(int x=1;x<=nx;x++){
for(i=1;i<=ny;i++) slack[i]=INF;
while(1){
memset(visx,0,sizeof(visx)),memset(visy,0,sizeof(visy));
if(DFS(x)) break;
int d=INF;
for(i=1;i<=ny;i++)
if(!visy[i]&&d>slack[i]) d=slack[i];
for(i=1;i<=nx;i++)
if(visx[i]) lx[i]-=d;
for(i=1;i<=ny;i++)
if(visy[i]) ly[i]+=d;
else slack[i]-=d;
}
}
int res=0;
for(i=1;i<=ny;i++)
if(linker[i]!=-1) res+=w[linker[i]][i];
return res;
}
相关文章推荐
- 一些基础算法的模板(持续更新)
- 算法和数据结构基础题集(持续更新中)
- 【算法基础个人常用总结】<-------持续更新------->
- 背包问题小结--持续更新 算法基础篇(七)
- c 语言经典算法,持续更新
- 树的应用小算法大全--持续更新中
- 算法竞赛入门经典 训练指南 之 图论(完全版--持续更新)
- Javascript基础小知识---------持续更新中..............
- Java 基础学习笔记(持续更新中)
- 图论相关问题——持续更新ing
- 查找""排序""简单数学计算" "简单算法"[Java实现](数据结构和算法)(复习)(持续更新
- .NET基础知识问题汇总(持续更新中)
- C/C++ 基础(持续更新中)
- [原创]C#基础演练代码(1),持续更新中.......
- STL中常用的一些算法函数[持续更新
- JAVA的一些基础(持续更新)
- 算法学习文章收集(持续更新)
- Dijkstra求单源最短路径(图论基础算法)
- 网络基础,持续更新