图论常用算法总结

一、图的可行遍历

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 }    

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         //如果还有点未加入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 }

算法特点:一般看似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;
}

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

不确定根的情况

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

时间: 2024-10-11 16:58:16

图论常用算法总结的相关文章

图论常用算法之二 算法模板及建模总结

寒假的第二周,弥补了一下图论算法.在这里做一下总结,主要针对近期学到的一些建模技巧,同时也非常感谢有朋友能够给出图论算法相关的精彩讲解或者知识链接. 算法总结: 欧拉回路问题:判断图是否存在欧拉回路或者欧拉通路,输出一条欧拉回路. 学习Fleury算法输出一条欧拉回路. 1 /* G是连通无向图,则称经过G的每条边一次并且仅一次的路径为 2 欧拉通路,起点和终点是同一个顶点称为欧拉回路,具有欧拉回路的 3 无向图为欧拉图. 4 依次有有向欧拉通路,有向欧拉回路,有向欧拉图 5 定理:连通图G仅有

图论常用算法之一 POJ图论题集【转载】

POJ图论分类[转] 一个很不错的图论分类,非常感谢原版的作者!!!在这里分享给大家,爱好图论的ACMer不寂寞了... (很抱歉没有找到此题集整理的原创作者,感谢知情的朋友给个原创链接) POJ:http://poj.org/ 1062* 昂贵的聘礼 枚举等级限制+dijkstra 1087* A Plug for UNIX 2分匹配 1094 Sorting It All Out floyd 或 拓扑 1112* Team Them Up! 2分图染色+DP 1125 Stockbroker

机器学习定义及常用算法

转载自:http://www.cnblogs.com/shishanyuan/p/4747761.html?utm_source=tuicool 1 . 机器学习概念 1.1   机器学习的定义 在维基百科上对机器学习提出以下几种定义: l “ 机器学习是一门人工智能的科学,该领域的主要研究对象是人工智能,特别是如何在经验学习中改善具体算法的性能 ” . l “ 机器学习是对能通过经验自动改进的计算机算法的研究 ” . l “ 机器学习是用数据或以往的经验,以此优化计算机程序的性能标准. ” 一

机器学习的一些常用算法

下面是些泛泛的基础知识,但是真正搞机器学习的话,还是非常有用.像推荐系统.DSP等目前项目上机器学习的应用的关键,我认为数据处理非常非常重要,因为很多情况下,机器学习的算法是有前提条件的,对数据是有要求的. 机器学习强调三个关键词:算法.经验.性能,其处理过程如下图所示. 上图表明机器学习是数据通过算法构建出模型并对模型进行评估,评估的性能如果达到要求就拿这个模型来测试其他的数据,如果达不到要求就要调整算法来重新建立模型,再次进行评估,如此循环往复,最终获得满意的经验来处理其他的数据. 1.2 

数学建模学习笔记(建模中的十大常用算法总结)

数学建模中的十大常用算法 1.    蒙特卡洛方法: 又称计算机随机性模拟方法,也称统计实验方法.可以通过模拟来检验自己模型的正确性. 2.    数据拟合.参数估计.插值等数据处理 比赛中常遇到大量的数据需要处理,而处理的数据的关键就在于这些方法,通常使用matlab辅助,与图形结合时还可处理很多有关拟合的问题. 3.    规划类问题算法: 包括线性规划.整数规划.多元规划.二次规划等:竞赛中又很多问题都和规划有关,可以说不少的模型都可以归结为一组不等式作为约束条件,几个函数表达式作为目标函

五大常用算法

http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741370.html 分治算法 一.基本概念 在计算机科学中,分治法是一种很重要的算法.字面上的解释是"分而治之",就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题--直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并.这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)-- 任何一个可以用计

推荐系统中常用算法 以及优点缺点对比

推荐系统中常用算法 以及优点缺点对比 在 推荐系统简介中,我们给出了推荐系统的一般框架.很明显,推荐方法是整个推荐系统中最核心.最关键的部分,很大程度上决定了推荐系统性能的优劣.目前,主要的推荐方法包括:基于内容推荐.协同过滤推荐.基于关联规则推荐.基于效用推荐.基于知识推荐和组合推荐. 一.基于内容推荐 基于内容的推荐(Content-based Recommendation)是信息过滤技术的延续与发展,它是建立在项目的内容信息上作出推荐的,而不需要依据用户对项目的评价意见,更多地需要用机 器

图论(A*算法,K短路) :POJ 2449 Remmarguts&#39; Date

Remmarguts' Date Time Limit: 4000MS   Memory Limit: 65536K Total Submissions: 25216   Accepted: 6882 Description "Good man never makes girls wait or breaks an appointment!" said the mandarin duck father. Softly touching his little ducks' head, h

(转)常用算法(Algorithm)的用法介绍

2算法部分主要由头文件<algorithm>,<numeric>和<functional>组成. 2<algorithm>是所有STL头文件中最大的一个,其中常用到的功能范围涉及到比较.交换.查找.遍历操作.复制.修改.反转.排序.合并等等. 2<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作. 2<functional>中则定义了一些模板类,用以声明函数对象. 2STL提供