关于图论的若干巴拉巴拉

最近课堂上正在讲图论

先安利MIT课程:http://open.163.com/special/opencourse/algorithms.html

因为本人对图论的概念并不是很清楚,所以还是整理一下吧。

1.图论的基本概念

几种常见的图的分类:

类型 允许多重边 允许环
简单图 无向   否  否
多重图 无向   是  否
伪图 无向   是  是
有向图 有向   否    是
有向多重图 有向   是  是

完全图:n个顶点上的完全图是在每对不同顶点之间都恰有一条边的简单图。

二分图:若把简单图G的顶点集合分为两个不相交的非空集合V1和V2,使得图里的每一条边都连接着V1里的一个顶点和V2里的一个顶点(因此G里面没有边是连接着V1里的两个顶点或V2里的两个顶点),则G称为二分图(从定义中可以看出,二分图必无环,但可以有多重边)。

  用标号法可以方便地判断一个图是否是二分图:

  首先给任意一个顶点标上A,与标记A的顶点邻接的顶点标上B,再将标记为B的顶点邻接的顶点标记上A,续行此法,如果这个过程可以完成,使得没有相邻的顶点标上相同的字母,则该图是二分图,否则它就不是二分图。

  另:如果标记成功,可以将两个顶点子集X与Y中的顶点分开画,与原图形形成一个同构图,这样可以直接地看出它是一个二分图。

完全二分图:设G是一个二分图,若G是一个简单图,并且X中的每个顶点与Y中的每个顶点均邻接,则称G为完全二分图。

  易知,完全二分图中共有|X|·|Y|条边。



图的表示和图的同构:

常见的图的表示方式:边表、相邻矩阵(简单图的相邻矩阵是对称的0-1矩阵,当出现环或多重边时,矩阵对称但不是0-1矩阵,此时第(i, j)项是与{vi, vj}关联的边数。另外,有向图的相邻矩阵不必是对称的)、关联矩阵(顶点与边的n*m矩阵,用相等项的列来表示多重边,恰有一项等于1的列来表示环)。

同构:当两个简单图同构时,两个图的顶点之间具有保持相邻关系的一一对应(注意:同构的图是等价的图,只是画的方式不同而已)。

  容易看出,两图同构的必要条件有:顶点数相等、边数相等、度数相同的顶点数相等(总的顶点度更得相等)。遗憾的是,目前尚没有用来判定简单图是否同构的不变量集。



连通性:

通路:在无向图里从顶点u到v的路径。

回路:若一条通路在相同的顶点上开始和结束,则它是一条回路。

简单通路(回路):若一条通路(或回路)不重复地包含相同的边,则它是简单的。

无向图连通性:若无向图里每一对不同的顶点之间都有通路,则该图称为连通的。

有向图连通性:根据是否考虑边的方向:有向图里由两种连通性概念。

  强连通:每当a和b都是一个有向图里的顶点时,就有从a到b和从b到a的通路。

  弱连通:在有向图的底图(底图:有向图忽略边的方向后得出的无向图)里,任何两个顶点之间都有通路。

    显然,任何强连通的有向图也是弱连通的。

连通分支:不连通的图是两个或两个以上连通子图之并,每一对子图都没有公共顶点,这些不相交的连通子图称为图的连通分支。

由连通性引发的几个概念:

  割点:如果删除一个顶点和它所关联的边,该图的连通分支增加,这样的顶点就叫做割点。

  割边:把一旦删除就产生比原图更多的连通分支的子图的边称为割边(或桥)。

参考博客:http://www.cnblogs.com/xpjiang/p/4401426.html

2.详细讲述一下这星期学的知识点

①图的表示

1 邻接矩阵 ,对于N个点的图,需要N×N的矩阵表示点与点之间是否有边的存在。这种表示法的缺点是浪费空间,尤其是对于N×N的矩阵是稀疏矩阵,即边的数目远远小于N×N的时候,浪费了巨大的存储空间。

2 邻接链表,对于任何一个node A,外挂一个邻接链表,如果存在 A->X这样的边,就将X链入链表。 这种表示方法的优点是节省空间,缺点是所有链表都存在的缺点,地址空间的不连续造成缓存命中降低,性能有不如临界矩阵这样的数组。

②图的DFS遍历

基本思想:
    从图中某顶点V0出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到;
    若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点;
    重复上述过程,直至图中所有顶点都被访问到为止。

分析:

在遍历图时,对图中每个顶点至多调用一次DFS函数,因为一旦某个顶点被标志成已被访问,就不再从它出发进行搜索。
    因此,遍历图的过程实质上是对每个顶点查找其邻接点的过程。其耗费的时间则取决于所采用的存储结构。 当使用二维数组表示邻接矩阵作图的存储结构时,查找每个顶点的邻接点所需时间为O(n^2),其中n为顶点数。而当以邻接表作图的存储结构时,找邻接点所需时间为O(e),其中e为无向图中边的数目或有向图中弧的数目。由此,当以邻接表作存储结构时,深度优先搜遍遍历图的时间复杂度为O(n+e)

1 int v[N]={0};
2 void dfs(int k)
3 {
4     v[k]=1;
5     for(i=0;i<N;i++)
6         if(a[k][i]==1&&!v[i])
7             dfs(i);
8 }

3.有根树的LCA(Tarjan)

这边有几个较为详细解释的链接(叫我安利小天使):

http://blog.csdn.net/ywcpig/article/details/52336496

http://blog.csdn.net/u014679804/article/details/48000523

解法一:暴力

 1 int query(Node t, Node u, Node v)
 2 {
 3     int left = u.value;
 4     int right = v.value;
 5     //二叉查找树内,如果左结点大于右结点,不对,交换
 6     if (left > right)
 7     {
 8         int temp = left;
 9         left = right;
10         right = temp;
11     }
12     while (true)
13     {
14         //如果t小于u、v,往t的右子树中查找
15         if (t.value < left)
16         {
17             t = t.right;
18
19             //如果t大于u、v,往t的左子树中查找
20         }
21         else if (t.value > right)
22         {
23             t = t.left;
24         }
25         else
26         {
27             return t.value;
28         }
29     }
30 }

解法二:Tarjan(离线)

 1 const int mx = 10000; //最大顶点数
 2 int n, root;          //实际顶点个数,树根节点
 3 int indeg[mx];          //顶点入度,用来判断树根
 4 vector<int> tree[mx]; //树的邻接表(不一定是二叉树)
 5 void inputTree() //输入树
 6 {
 7     scanf("%d", &n); //树的顶点数
 8     for (int i = 0; i < n; i++) //初始化树,顶点编号从0开始
 9         tree[i].clear(), indeg[i] = 0;
10     for (int i = 1; i < n; i++) //输入n-1条树边
11     {
12         int x, y;
13         scanf("%d%d", &x, &y); //x->y有一条边
14         tree[x].push_back(y);
15         indeg[y]++;//加入邻接表,y入度加一
16     }
17     for (int i = 0; i < n; i++) //寻找树根,入度为0的顶点
18         if (indeg[i] == 0)
19         {
20             root = i;
21             break;
22         }
23 }
24 vector<int> query[mx]; //所有查询的内容
25 void inputQuires() //输入查询
26 {
27     for (int i = 0; i < n; i++) //清空上次查询
28         query[i].clear();
29
30     int m;
31     scanf("%d", &m); //查询个数
32     while (m--)
33     {
34         int u, v;
35         scanf("%d%d", &u, &v); //查询u和v的LCA
36         query[u].push_back(v);
37         query[v].push_back(u);
38     }
39 }
40 int father[mx], rnk[mx]; //节点的父亲、秩
41 void makeSet() //初始化并查集
42 {
43     for (int i = 0; i < n; i++) father[i] = i, rnk[i] = 0;
44 }
45 int findSet(int x) //查找
46 {
47     if (x != father[x]) father[x] = findSet(father[x]);
48     return father[x];
49 }
50 void unionSet(int x, int y) //合并
51 {
52     x = findSet(x), y = findSet(y);
53     if (x == y) return;
54     if (rnk[x] > rnk[y]) father[y] = x;
55     else father[x]  = y, rnk[y] += rnk[x] == rnk[y];
56 }
57 int ancestor[mx]; //已访问节点集合的祖先
58 bool vs[mx];      //访问标志
59 void Tarjan(int x) //Tarjan算法求解LCA
60 {
61     for (int i = 0; i < tree[x].size(); i++)
62     {
63         Tarjan(tree[x][i]);         //访问子树
64         unionSet(x, tree[x][i]); //将子树节点与根节点x的集合合并
65         ancestor[findSet(x)] = x;//合并后的集合的祖先为x
66     }
67     vs[x] = 1; //标记为已访问
68     for (int i = 0; i < query[x].size(); i++) //与根节点x有关的查询
69         if (vs[query[x][i]]) //如果查询的另一个节点已访问,则输出结果
70             printf("%d和%d的最近公共祖先为:%d\n", x,
71                   query[x][i], ancestor[findSet(query[x][i])]);
72 }
73 int main()
74 {
75     inputTree();  //输入树
76     inputQuires();//输入查询
77     makeSet();
78     for (int i = 0; i < n; i++)
79         ancestor[i] = i;
80     memset(vs, 0, sizeof(vs));
81     //初始化为未访问
82     Tarjan(root);
83 }

4.割点、割边(桥)

在一个无向连通图中,如果删除后某个顶点之后,图不再连通(即任意两点之间不能相互可达),这样的顶点叫做割点。

由此,删去某个顶点之后,然后进行一次dfs搜索一遍判断图是否还连通,便能求出该点是不是割点。

但是时间复杂度颇高。

所以,基于dfs搜索树设计了求割点的算法。

求割点可分为两种情况,

1. 求根节点的是否为割点,只要其有两棵或两棵以上子树,则为割点。

2. 叶节点都不是割点,所以只剩下要求非叶节点,若一非叶节点的子树的节点没有指向该点的祖先节点的回边,说明删除该点之后,该点的祖先节点与该点的子树不再连通,则说明该节点为割点。

使用dfs进行一次搜索,假设搜索结果是下图这样的,即1->3->2->4->5->6

圆圈内代表节点编号,旁边的数值代表该顶点在进行遍历时是第几个被访问到的,我们称做时间戳,用num数组来进行存储每个顶点的时间戳。

当遍历至某个顶点时,判断剩余未访问的点在不经过该点的情况下能否回到在访问该点之前的任意一个点,可以形象地说成该点的儿子在不经过它这个父亲的情况下是否还能回到它的祖先。

所以我们在用一个low数组来记录每个顶点在不经过其父节点时,能够直接回到的最小 ‘时间戳‘,如图

所以,对于某点u若至少存在一个儿子顶点v,使low[v] >= num[u],即不能回到祖先,则说明该点u为割点。

 1 #include <algorithm>
 2 #include <cstring>
 3 #include <cstdio>
 4 using namespace std;
 5 struct node{
 6     int v, next;
 7 }edge[4005];
 8 int head[1005], num[1005], low[1005], ans[1005];
 9 int n, m, no, index, root;
10 void add(int u, int v){
11     edge[no].v = v;
12     edge[no].next = head[u];
13     head[u] = no++;
14 }
15 void init(){
16     no = 1, index = 0, root = 1;
17     memset(head, -1, sizeof head);
18     memset(ans, 0, sizeof ans);
19     memset(num, 0, sizeof num);
20 }
21 void dfs(int cur, int father){
22     int child = 0;
23     ++index;
24     num[cur] = index;
25     low[cur] = index;
26     int k = head[cur];
27     while(k != -1){
28         if(num[edge[k].v] == 0){
29             ++child;
30             dfs(edge[k].v, cur);
31             low[cur] = min(low[cur], low[edge[k].v]);
32             if(cur != root && low[edge[k].v] >= num[cur]) ans[cur] = 1;
33             if(child == 2 && cur == root) ans[cur] = 1;
34         }
35         else if(edge[k].v != father){
36             low[cur] = min(low[cur], num[edge[k].v]);
37         }
38         k = edge[k].next;
39     }
40 }
41 int main(){
42     int i, u, v;
43     scanf("%d %d", &n, &m);
44     init();
45     for(i = 1; i <= m; ++i){
46         scanf("%d %d", &u, &v);
47         add(u, v);
48         add(v, u);
49     }
50     dfs(root, root);
51     for(i = 1; i <= n; ++i)
52         if(ans[i]) printf("%d ", i);
53     printf("\n");
54     return 0;
55 }

求割边其实就需要将上面的代码稍加改动,将low[v] >= num[u]改为low[v] > num[u],因为这句代码代表该点的子节点在不经过其父亲节点回不到该点及该点的祖先的,所以u->v这条边是一条割边。

 1 #include <algorithm>
 2 #include <cstring>
 3 #include <cstdio>
 4 using namespace std;
 5 struct node{
 6     int v, next;
 7 }edge[4005];
 8 int head[1005], num[1005], low[1005], ans[1005];
 9 int n, m, no, index, root;
10 void add(int u, int v){
11     edge[no].v = v;
12     edge[no].next = head[u];
13     head[u] = no++;
14 }
15 void init(){
16     no = 1, index = 0, root = 1;
17     memset(head, -1, sizeof head);
18     memset(ans, 0, sizeof ans);
19     memset(num, 0, sizeof num);
20 }
21 void dfs(int cur, int father){
22     ++index;
23     num[cur] = index;
24     low[cur] = index;
25     int k = head[cur];
26     while(k != -1){
27         if(num[edge[k].v] == 0){
28             dfs(edge[k].v, cur);
29             low[cur] = min(low[cur], low[edge[k].v]);
30             if(low[edge[k].v] > num[cur])
31                 printf("%d--%d\n", cur, edge[k].v);
32         }
33         else if(edge[k].v != father){
34             low[cur] = min(low[cur], num[edge[k].v]);
35         }
36         k = edge[k].next;
37     }
38 }
39 int main(){
40     int i, u, v;
41     scanf("%d %d", &n, &m);
42     init();
43     for(i = 1; i <= m; ++i){
44         scanf("%d %d", &u, &v);
45         add(u, v);
46         add(v, u);
47     }
48     dfs(root, root);
49     return 0;
50 }

时间复杂度为O(M+N)

参考来源:http://m.blog.csdn.net/yo_bc/article/details/60780840

暂更新到这里。

讲一讲这一周的体验吧。

首先有点郁闷每天都要考试wwww (无奈脸) 不过考着考着大概就习惯了吧唔。

然后发现周围的oier们真是越来越可爱了呢,他们可以在机房里搞很多事情hhhh(#斜眼)

NOIP很快也要到了呢,不管怎么样,一路往前走吧。

我似乎越说越乱了,感觉这几天思绪的确很乱,所以需要好好整理一下,结果也整的乱七八糟。

天气燥热,不过是晴好的天气。

我们不说话/这样就很好。

(真是越说越乱了我也不知道该说什么了就这样吧唔)

G.N. to world.——Hathaway

2017-07-07

时间: 2024-08-06 07:57:55

关于图论的若干巴拉巴拉的相关文章

图论及其应用——图

我们探索某个领域的知识,无不怀揣的核弹级的好奇心与求知欲,那么,今天,我们就将开始对图论的探索.   观察一副<机械迷城> 的一处谜题.    不得不承认,<机械迷城>这款解密游戏难度远胜于<纪念碑谷>, 其中一个困难点就在于——<纪念碑谷>的目标是很明确的,但是<机械迷城>往往需要自己凭感觉设立目标.而这里的关卡的目标,就是堵住第三个出水口. 为了解决这个谜题,如果不去考虑用暴力枚举的方法去试探(其实很多情况下都是用到这种情况)一开始,我们似乎

图论算法 有图有代码 万字总结 向前辈致敬

图的定义 背景知识 看到这篇博客相信一开始映入读者眼帘的就是下面这幅图了,这就是传说中的七桥问题(哥尼斯堡桥问题).在哥尼斯堡,普雷格尔河环绕着奈佛夫岛(图中的A岛).这条河将陆地分成了下面4个区域,该处还有着7座连接这些陆地的桥梁. 问题是如何从某地出发,依次沿着各个桥,必须经过每座桥且每座桥只能经过1次,最终回到原地. 不知道这个问题且好奇的童鞋现在肯定在忙活着找出来这道题的结果了. 是伟大的数学家欧拉(Leonhard Euler)在1736年首次使用图的方法解决了该问题. 欧拉将上面的模

【转】彻底弄懂最短路径问题(图论)

来源:彻底弄懂最短路径问题 http://www.cnblogs.com/hxsyl/p/3270401.html P.S.根据个人需要,我删改了不少 问题引入 问题:从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径——最短路径.解决最短路的问题有以下算法,Dijkstra算法,Bellman-Ford算法,Floyd算法和SPFA算法,另外还有著名的启发式搜索算法A*,不过A*准备单独出一篇,其中Floyd算法可以求解任意两点间的最短路径的长度.笔者认为任意一个最

离散数学图论

w 如何学习数据结构? - 知乎  https://www.zhihu.com/question/21318658/answer/63652147 作者:知乎用户链接:https://www.zhihu.com/question/21318658/answer/63652147来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 数据结构的本质就在于:如何将现实世界中各种各样的数据放入到内存中,并且如何在内存中操作这些数据,如何评价这些存储方案和操作方法.数据结构难学吗

图论算法

图论[Graph Theory]是数学的一个分支.它以图为研究对象.图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系. 分类 有向图,无向图:单图: 平面图,连通图,强连通图,有向无环图,AOV网,AOE网,完全图,二分图,完全二分图,正则图,二叉图: 树,外向树.内向树,章鱼图,人掌图(边仙人掌.点仙人掌),有向无环图,分图. 这里面,很多都是我闻所未闻的东西...图论真的博大精深呀

7.3图论模拟

FJSC图论测试 题目 1.无线通讯网(wireless.pas/cpp/c) [题目描述] 国防部计划用无线网络连接若干个边防哨所.2种不同的通讯技术用来搭建无线网络:每个边防哨所都要配备无线电收发器:有一些哨所还可以增配卫星电话. 任意两个配备了一条卫星电话线路的哨所(两边都拥有卫星电话)均可以通话,无论他们相距多远.而只通过无线电收发器通话的哨所之间的距离不能超过D,这是受收发器的功率限制.收发器的功率越高,通话距离D会更远,但同时价格也会更贵. 收发器需要统一购买和安装,所以全部哨所只能

各种图论模型及其解答(转载)

原文转自Jelline blog http://blog.chinaunix.net/uid-9112803-id-411340.html //============================== 在做研究的过程中,发现其实以前觉得没什么用的数学模型或者图论模型,突然间变得非常有用起来.感叹于自己相关知识的不足,于是搜索相关知识进行学习,并分享. 这篇转载的文章对于从事网络方向研究的初级人员有一定帮助,譬如我.对于已经熟悉图论各模型.各细节的人可以直接关闭此网页离开. //=======

各种图论模型及其解答(转)

原文转自Jelline blog http://blog.chinaunix.net/uid-9112803-id-411340.html 摘要: 本文用另一种思路重新组织<图论及其应用>相关知识.首先,用通俗化语言阐述了如何对事物间联系的问题进行图论建模:接着从现实例子出发,给出 各种典型图论模型,每种图论模型对应于图论一个重要内容:再者,介绍相关知识对上述提到的图论模型涉及的问题进行解答:最后,补充一些图论其他知识,包括 图论分支.易混概念. 符号约定: Q(Question)表示对问题描

从存图到最短路算法的图论总结

INTRODUCTION: 图论算法在计算机科学中扮演着很重要的角色,它提供了对很多问题都有效的一种简单而系统的建模方式.很多问题都可以转化为图论问题,然后用图论的基本算法加以解决.--百度百科 对于OI而言,图是指由若干给定的点及若干条连接两点的线(边)所构成的图形 借助图论知识,我们往往可以将一些复杂的问题转化到基础的图论算法上,进而使用已有算法解决全新问题 那么想如果想要运用图论,首先要从存图开始 前排感谢教我图论的周润喵老师,syc学长,就序老师 可是我还是没学会 一,存图 对于一个图而