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

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[N],lx[N],ly[N],slack[N];  //lx,ly为顶标,nx,ny分别为x点集y点集的个数
int visx[N],visy[N],w[N][N];

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;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值