首页 > 代码库 > 图论常用算法总结

图论常用算法总结

一、图的可行遍历

    1)欧拉图

        条件:1、图连通;2、奇度点数为0或2;

        算法(一次dfs)

        时间复杂度O(E),空间复杂度O(E)

 1 //前向星,vis[]标记走过的边,cnt初始为1,i的反向边为i^1 2 void addedge (int u, int v) { 3     edge[++cnt].v = v,edge[cnt].ne = head[u]; 4     head[u] = cnt; 5 } 6 void EulerianP (int x) { 7     for (int i = head[x]; i != 0; i = edge[i].ne) { 8         if (!vis[i]) { 9             vis[i] = vis[i^1]=1;10             EulerianP (edge[i].v);11         }12     }13         //输出顺序即为路径顺序14     printf ("%d\n", x);15 }    
View Code

    2)哈密顿图

        哈密顿图的判断是一个NP问题。

        当n个点的图当任意不同两点的度数和大于n时,一定存在哈密顿回路。

        算法思路:

        1、从任意两个相邻的节点S,T开始,拓展出一条尽量长的链,令两端节点为S,T;

        2、 若S,T相邻,则出现回路,-》4

        3、若没有回路则构造,链中找到节点相邻节点u,v,u与T相连,v与S相连,将原路径变为S->u->T->v,即出现回路;

        4、如果回路中有n个点,找到了哈密顿路,否则找到回路中与外一点想连的点,断开。又得到一条链,重复步骤1.

        时间复杂度O(n^2),空间复杂度(n^2)

 1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring> 5 #include <vector> 6 using namespace std; 7 #define pb push_back 8 #define sz(a)  (int)(a).size() 9 10 const int INF = 500;11 bool edge[INF][INF];12 typedef vector<int> vi;13 vi ans;14 //求哈密顿回路O(n^2)15 void Hamilton (vi& ans, bool edge[INF][INF], int n) {16     int s = 1, tol = 2, t, i, j;17     bool vis[INF] = {0};18     for (i = 1; i <= n; i++) if (edge[s][i]) break;19     t = i;20     vis[s] = vis[t] = 1;21     ans.pb (s); ans.pb (t);22     while (1) {23         //头尾拓展24         while (1) {25             for (i = 1; i <= n; i++) 26                 if (edge[t][i] && !vis[i]) {27                     vis[i] = 1; t = i;28                     ans.pb (i);29                     break;30                 }31             if (i > n) break;32         }33         reverse (ans.begin(), ans.end() );34         swap (s, t);35         while (1) {36             for (i = 1; i <= n; i++) 37                 if (edge[t][i] && !vis[i]) {38                     vis[i] = 1; t = i;39                     ans.pb (i);40                     break;41                 }42             if (i > n) break;43         }44         //如果S和T不相连45         if (!edge[s][t]) {46             for (i = 1; i < sz (ans) - 2; i++)47                 if (edge[ans[i]][t] && edge[ans[i + 1]][s]) break;48             reverse (ans.begin() + i + 1, ans.end() );49             t = * (ans.end() - 1);50         }51         tol = sz (ans);52         if (tol == n) return;53         //如果还有点未加入ans54         for (j = 1; j <= n; j++) {55             if (vis[j]) continue;56             //找到与这个点相连的点57             for (i = 1; i < tol - 1; i++) if (edge[ans[i]][j]) break;58             if (edge[ans[i]][j]) break;59         }60         s = ans[i - 1], t = j;61         reverse (ans.begin(), ans.begin() + i );62         reverse (ans.begin() + i, ans.end() );63         ans.pb (j), vis[j] = 1;64     }65 }
View Code

 

      算法特点:一般看似NP的哈密顿路的判断问题可以通过变化成为欧拉路问题,也是解题的关键。

二、图的生成树

     1)最小生成树  

         prim(堆优化)时间复杂度O((n+m)logN)

         kruskal          时间复杂度O(mlogm+m)

      2)次小生成树

         思路:从生成的最小生成树中加入一条后,去掉环中除新加边外最大的边

         使用prim,思路更加直观:每加入一个点,更新当前集合中任意两点间的最长边的长度。

         生成最小生成树后,加入从未使用的过的最短边(u,v),u,v间的最长边与(u,v)的差就是次小生成树与最小生成树的差

//图的次小生成树://从生成的最小生成树中加入一条后,去掉环中除新加边外最大的边//1.prim实现时间复杂度O(N*N+M*logM)#include <iostream>#include <cstring>using namespace std;const int INF = 111;//记录两点间路径上的最长边长度int maxl[INF][INF];//前向星存边struct node {    int u, v, len, ne, id;} edge[INF*INF];//vise标记选择了哪些边//dis记录从当前选择节点中到其他节点的最短距离以及边的编号.int cnt, head[INF], dis[INF][2], vise[INF*INF];void addedge (int u, int v, int d) {    edge[++cnt].v = v, edge[cnt].u = u;    edge[cnt].len = d, edge[cnt].ne = head[u];    edge[cnt].id = head[u] = cnt;}void update (int x) {    for (int i = head[x]; i != 0; i = edge[i].ne) {        int v = edge[i].v, c = edge[i].len;        if (dis[v][0] > c)    dis[v][0] = c, dis[v][1] = i;    }}int second_MST_prim (int n) {    bool vis[INF] = {0};    bool vise[INF * INF] = {0};    memset (dis, 0x3f, sizeof dis);    memset (maxl, 0, sizeof maxl);    vis[1] = 1, update (1);    int ans = 0, tol = 1, j, now = 1;    while (tol < n) {        int minl = 0x3f3f3f3f;        for (int i = 1; i <= n; i++)            if (!vis[i] && minl > dis[i][0])                minl = dis[i][0], j = i;        for (int k = 1; k <= n; k++)            maxl[k][j] = maxl[j][k] = max (maxl[now][k], minl);        maxl[j][j] = 0, update (j), vis[j] = 1;        vise[dis[j][1]] = vise[dis[j][1] ^ 1] = 1;        now = j, tol++, ans += minl;    }    int tem = 0x7fffffff;    for (int i = 2; i <= cnt; i++)        if (!vise[edge[i].id])            tem = min (tem, edge[i].len - maxl[edge[i].u][edge[i].v]);    if (tem != 0) return ans;    else        return -1;}
View Code

      3)有向图的最小树形图

         朱刘算法

         思路:

         1、从图G找到以每个点为终点的最小弧。构成子图G’

         2、若无有向环和收缩点,算法结束。 

         3、收缩G’的有向环为,改变到环中点的边权。

         4、展开收缩点。(若只求最小权可不做)

         

确定根的情况         

#include <iostream>#include <utility>#include <cstdio>#include <cstring>#include <cmath>#define sqr(a) (a)*(a)using namespace std;const int INF = 109;const int Max=(1<<30);int n, m, x, y;typedef pair<int , int > ii;struct node {    int u, v;    double c;} E[INF*INF];ii f[INF];//最小树形图,朱刘算法,确定根double Directed_MST (int root, int n, int m, node E[INF]) {    int Din[INF], ret = 0; //记录最短入边    int pre[INF], Np[INF], vis[INF];//记录最小边的出顶点    for (int i = 1; i <= n; i++) Np[i] = i;    while (1) {        int cnt = 0;//统计有入边的点        for(int i=1;i<=n;i++) Din[i]=-1;        //得到每个点的最小入边和出顶点        for (int i = 1; i <= m; i++) {            if ( E[i].v == E[i].u || E[i].v==root) continue;            if (Din[E[i].v] > E[i].c || (Din[E[i].v]<0 && ++cnt) ) Din[E[i].v] = E[i].c, pre[E[i].v] = E[i].u;        }        //图不联通,不存在最小树形图        if (cnt < n - 1) return -1;        memset (Np, -1, sizeof Np);        memset (vis, -1, sizeof vis);        Din[root] = 0;        //找环        int cntnode = 1;        for (int i = 1; i <= n; i++) {            ret += Din[i];            int v = i;            while (vis[v] != i && Np[v] == -1 && v != root) {                vis[v] = i;                v = pre[v];            }            if (v != root && Np[v] == -1) {                for (int u = pre[v]; u != v; u = pre[u])                    Np[u] = cntnode;                Np[v] = cntnode++;            }        }        if (cntnode == 1) break; //无环        for (int i = 1; i <= n; i++)            if (Np[i] == -1)                Np[i] = cntnode++;        //缩点,改变边长        for (int i = 1; i <= m; i++) {            int u = E[i].u, v = E[i].v;            E[i].u = Np[u];            E[i].v = Np[v];            if (E[i].u != E[i].v) {                E[i].c -= Din[v];            }        }        n = cntnode-1;        root = Np[root];    }    return ret;}
View Code

 

不确定根的情况

#include <iostream>#include <utility>#include <cstdio>#include <cstring>#include <cmath>#define ll __int64#define sqr(a) (a)*(a)using namespace std;const int INF = 1009;const int Max = 1 << 30;int n, m, r, tol = 1;struct node {    int u, v, c;} E[INF*INF];/*    最小树形图,朱刘算法,适用不确定根    需要记录 tol(图中所有边长和+1,可初始为1)    4个参数分别为(节点数,边数,E边集数组,根地址)    时间复杂度O(VE)*/ll Directed_MST (int n, int m, node E[INF], int &p) {    //添加“假根”的出边    for (int i = 1; i <= n; i++) {        E[++m].u = n + 1, E[m].v = i, E[m].c = tol;    }    int pre[INF], Np[INF], vis[INF], root = ++n; //记录最小边的出顶点    ll Din[INF], ret = 0; //记录最短入边    while (1) {        int cnt = 0;//统计有入边的点        memset(Din,-1,sizeof Din);        //得到每个点的最小入边和出顶点        for (int i = 1; i <= m; i++) {            if ( E[i].v == E[i].u || E[i].v == root) continue;            if (Din[E[i].v] > E[i].c || (Din[E[i].v]<0 && ++cnt) ) {                                   Din[E[i].v] = E[i].c, pre[E[i].v] = E[i].u;                                   if (E[i].u == root) p = i;//用到了假根的第几个边即真正根的编号            }        }        if (cnt < n - 1) return -1;//图不联通,不存在最小树形图        memset (Np, -1, sizeof Np);        memset (vis, -1, sizeof vis);        Din[root] = 0;        //找环        int cntnode = 1;        for (int i = 1; i <= n; i++) {            ret += Din[i];            int v = i;            while (vis[v] != i && Np[v] == -1 && v != root) {                vis[v] = i;                v = pre[v];            }            if (v != root && Np[v] == -1) {                for (int u = pre[v]; u != v; u = pre[u])                    Np[u] = cntnode;                Np[v] = cntnode++;            }        }        if (cntnode == 1) break; //无环        for (int i = 1; i <= n; i++)            if (Np[i] == -1)                Np[i] = cntnode++;        //缩点,改变边长        for (int i = 1; i <= m; i++) {            int u = E[i].u, v = E[i].v;            E[i].u = Np[u], E[i].v = Np[v];            if (E[i].u != E[i].v)                E[i].c -= Din[v];        }        n = cntnode - 1;        root = Np[root];    }    return ret;}int main() {    while (~scanf ("%d %d", &n, &m) ) {        int x, y, z;        for (int i = 1; i <= m; i++) {            scanf ("%d %d %d", &x, &y, &z);            E[i].u = x + 1, E[i].v = y + 1, E[i].c = z;            tol += z; //累计所有边长,赋值给“假根”        }        ll ans = Directed_MST (n , m, E, r);        if (ans == -1 || ans >= 2 * tol)  puts ("impossible");        else                     printf ("%I64d %d\n", ans - tol, r - m - 1);        putchar (10);    }    return 0;}
View Code

 

         

图论常用算法总结