算法基础模板整理(基础图论篇)

拓扑排序

bool topo(){
    queue<int> q;
    for(int u = 1; u <= n; u ++ )
        if(!ind[u]) q.push(u);
    
    int cnt = 0;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        cnt ++ ;
        ans.push_back(u);
        for(int i = h[u]; i != -1; i = ne[i]){
            int v = e[i];
            if(!(--ind[v])) q.push(v);
        }
    }
    
    return cnt == n;
}


最短路

朴素Dijkstra

int dijkstra(){
    memset(dist, 0x3f, sizeof(dist));  dist[1] = 0;

    //循环n次 将n个点都加入最短路径网络(树)
    for(int i = 0; i < n; i ++ ){
        int t = -1;   //找到网络外距离源点最近的点
        for(int v = 1; v <= n; v ++ )
            if(!st[v] && (t == -1 || dist[v] < dist[t]))
                t = v;

        st[t] = true;   //加入最短路径网络 作为扩展点

        for(int j = h[t]; j != -1; j = ne[j]){
            int to = e[j], c = w[j];   
            if(!st[to]) dist[to] = min(dist[to], dist[t] + c);
        }
    }

    return dist[n] == inf ? -1 : dist[n];
}

堆优化Dijkstra

int dijkstra(){
    memset(dist, 0x3f, sizeof(dist));   dist[1] = 0;
    priority_queue<pii,vector<pii>,greater<pii>> q;  q.push({0, 1});    
    while(!q.empty()){
        auto [d, t] = q.top();
        q.pop();

        if(st[t]) continue;   //有的点可能被重复加入
        st[t] = true;

        for(int i = h[t]; i != -1; i = ne[i]){
            int v = e[i], c = w[i];
            if(dist[v] > d + c){
                dist[v] = d + c;
                q.push({dist[v], v});
            }
        }
    }
    return dist[n] == inf ? -1 : dist[n];
}


SPFA

int spfa(){
    memset(dist, 0x3f, sizeof(dist));
    queue<int> q; q.push(1);
    dist[1] = 0, st[1] = true;

    while(!q.empty()){
        int t = q.front();
        q.pop();

        st[t] = false;  //重新标记为false 之后有可能再次入队

        for(int i = h[t]; i != -1; i = ne[i]){
            int v = e[i], c = w[i];
            if(dist[v] > dist[t] + c){
                dist[v] = dist[t] + c;
                if(!st[v]) q.push(v), st[v] = true;
            }
        }
    }
    return dist[n];
}

SPFA判断负环

int spfa(){
    queue<int> q;
    for(int i = 1; i <= n; i ++ ) q.push(i), st[i] = true;//所有点放入点集

    //只遍历已经更新过距离的点,避免Bellman_Ford中每次遍历所有边的情况
    while(!q.empty()){
        int t = q.front();
        q.pop();

        st[t] = false;    //重新标记为false 之后有可能再次入队

        for(int i = h[t]; i != -1; i = ne[i]){
            int v = e[i], c = w[i];
            if(dist[v] > dist[t] + c){
                dist[v] = dist[t] + c;
                cnt[v] = cnt[t] + 1;          //记录cnt
                if(cnt[v] >= n) return true;  //存在负环

                if(!st[v]) q.push(v), st[v] = true;
            }
        }
    }
    return false;
}

SPFA SLF优化

void spfa(){
    memset(dist, 0x3f, sizeof(dist));
    deque<int> q; q.push_back(src);
    st[src] = true, dist[src] = 0;

    while(!q.empty()){
        ...
        //SLF优化
        for(int i = h[t]; i != -1; i = ne[i])
            ...
                    if(!q.empty() && dist[v] < dist[q.front()]) 
q.push_front(v);
                    else q.push_back(v);
                    st[v] = true;
            ...
        }
    }
}

Floyd

void init(){
    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= n; j ++ )
            if(i == j) continue;
            else dist[i][j] = inf;
}

void floyd(){
    for(int k = 1; k <= n; k ++ )
        for(int i = 1; i <= n; i ++ )
            for(int j = 1; j <= n; j ++ )
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}



最小生成树

Prim

int prim(){
    memset(dist, 0x3f, sizeof(dist));
    int ans = 0;
    for(int i = 0; i < n; i ++ ){
        int t = -1;    //集合外最近的点作为扩展点
        for(int v = 1; v <= n; v ++ )
            if(!st[v] && (t == -1 || dist[v] < dist[t]))
                t = v;

        if(i && dist[t] == inf) return inf;  //不存在最小生成树
        if(i) ans += dist[t];

        st[t] = true;

        for(int v = 1; v <= n; v ++ )
            dist[v] = min(dist[v], g[t][v]);
    }
    return ans;
}

Kruskal

struct edge{
    int u, v, w;
}e[M];
int fa[N], n, m, cnt, ans;

int find(int x){
    return fa[x] == x ? x : (fa[x] = find(fa[x]));
}

int kruskal(){
    for(int i = 1; i <= n; i ++ ) fa[i] = i;
    sort(e + 1, e + m + 1, [](struct edge &a, struct edge &b){
        return a.w < b.w;
    });

    for(int i = 1; i <= m; i ++ ){
        int fu = find(e[i].u), fv = find(e[i].v);
        if(fu == fv) continue;    //在同一个集合中 会形成环
        fa[fv] = fu;
        ans += e[i].w;
        cnt ++ ;
    }

    if (cnt < n - 1) return inf;
    return ans;
}


Tarjan

Tarjan求LCA

void tarjan(int u){
    st[u] = 1;
    for(int i = h[u]; i != -1; i = ne[i]){
        int v = e[i];
        if(!st[v]){
            tarjan(v);
            fa[v] = u;
        }
    }

    for(auto [v, id] : query[u]){
        if(st[v]){
            int anc = find(v);    //最近公共祖先  find和并查集一样
            if(anc == u) res[id] = 1;   
            else if(anc == v) res[id] = 2;
            else res[id] = 0;
        }
    }
}

Tarjan求树上两点最近距离

//深搜求出节点到根节点的距离
void dfs(int u, int fa){
    for(int i = h[u]; i != -1; i = ne[i]){
        int v = e[i];
        if(fa == v) continue;
        dist[v] = dist[u] + w[i];   
        dfs(v, u);
    }
}

void tarjan(int u){
    st[u] = true;
...
    for(auto [v, id] : query[u]){
        if(st[v]){
            int anc = find(v);  //如果被访问过 那么LCA为find(v)
            res[id] = dist[u] + dist[v] - 2 * dist[anc];  
            //两点距离为两点到根节点的距离和减去两倍的LCA根节点的距离
        }
    }
}

Tarjan判断割边

void tarjan(int u, int in_edge){
    dfn[u] = low[u] = ++ num;
    st[ ++ top] = u;
    for(int i = h[u]; i != -1; i = ne[i]){
        int v = e[i];
        if(!dfn[v]){
            tarjan(v, i);                  //自底向上
            low[u] = min(low[u], low[v]);  //更新当前节点的最小时间戳
            if(low[v] > dfn[u])         //无法回退到u
                bridge[i] = bridge[i ^ 1] = true;  //割边
        }else if(i != (in_edge ^ 1))
            low[u] = min(low[u], dfn[v]);   //处理回退边
    }
    
    if(dfn[u] == low[u]){  //双连通分量起点u
        dcc_cnt ++ ; int v;
        do{ v = st[top -- ];   id[v] = dcc_cnt;   //割边
        }while(v != u);
    }
}

int ans(){
for(int i = 0; i < idx; i ++ ) if(bridge[i]) d[id[e[i]]] ++ ;   
//边i为割边 那么两边度数 + 1
for(int i = 1; i <= dcc_cnt; i ++ ) if(d[i] == 1) cnt ++ ;  
return (cnt + 1) / 2;
}

Tarjan判断割点

void tarjan(int u, int in_edge){
    dfn[u] = low[u] = ++ num;
    int flag = 0;
    for(int i = h[u]; i != -1; i = ne[i]){
        int v = e[i];
        if(!dfn[v]){
            tarjan(v, i);                  //自底向上
            low[u] = min(low[u], low[v]);  //更新当前节点的最小时间戳
            if(low[v] >= dfn[u]){        
                flag ++ ;
                if(u != root || flag > 1) cut[u] = true;
            }
        }else low[u] = min(low[u], dfn[v]);
    }
}


二分图

染色法判定二分图

bool dfs(int u, int c){
    color[u] = c;
    for(int i = h[u]; i != -1; i = ne[i]){
        int v = e[i];
        if(!color[v]){  //如果没有被染过色
            if(!dfs(v, 3 - c)) return false;  //自底向上 深度染色关联节点
        }else if(color[v] == c) return false; //已经染色 但是相邻颜色相同
    }
    return true;
}

bool isBigraph(){
    bool flag = true;
    for(int i = 1; i <= n; i ++ )
        if(!color[i])
            if(!dfs(i, 1)){
                flag = false;
                break;
            }
    return flag;
}

匈牙利算法 二分图的最大匹配

bool find(int u){
    for(int i = h[u]; i != -1; i = ne[i]){ //询问所有与u相关联的男生
        int v = e[i];
        if(!st[v]){        //男生还没被预定
            st[v] = true;
            if(match[v] == 0 || find(match[v])){
                //如果男生还没有女朋友 或者 男生更换原配换成现在的女孩
                match[v] = u;   //v的女朋友是u
                return true;
            }
        }
    }
    return false;
}

int max_match(){
    for(int i = 1; i <= n1; i ++ ){
        memset(st, 0, sizeof(st));
        if(find(i)) res ++ ;
    }
    return res;   
}
posted @ 2023-04-14 13:23  MarisaMagic  阅读(16)  评论(0编辑  收藏  举报