您的位置:首页 > 其它

图论基础算法(持续更新)

2014-12-31 00:43 417 查看
1.dfs性质

<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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: