码迷,mamicode.com
首页 > 其他好文 > 详细

图论常用算法总结

时间:2014-09-09 18:16:09      阅读:392      评论:0      收藏:0      [点我收藏+]

标签:style   blog   http   color   os   io   使用   ar   for   

一、图的可行遍历

    1)欧拉图

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

        算法(一次dfs)

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

bubuko.com,布布扣
 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)

bubuko.com,布布扣
 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         //如果还有点未加入ans
54         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)的差就是次小生成树与最小生成树的差

bubuko.com,布布扣
//图的次小生成树:
//从生成的最小生成树中加入一条后,去掉环中除新加边外最大的边
//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、展开收缩点。(若只求最小权可不做)

         

确定根的情况         

bubuko.com,布布扣
#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

 

不确定根的情况

bubuko.com,布布扣
#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

 

         

图论常用算法总结

标签:style   blog   http   color   os   io   使用   ar   for   

原文地址:http://www.cnblogs.com/keam37/p/3963022.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!