一、图引入
相信很多同学都听说过六度空间理论(SixDegress of Separation):只要通过6个人的关系网,你就能够认识全世界所有的人。这个理论和我们接下来要讲的图非常相似。
![0817ac5ea637a8ccd4843c756959c683.png](https://i-blog.csdnimg.cn/blog_migrate/71df99196be5b8a5d7a47c8bb31db190.jpeg)
从上图中,我们可以看出,如果你认识6个人,是很有可能认识其他所有人的。
但是,对于全球的30亿的互联网人来说,你真的可以全部认识吗?大多数同学此刻一定是定神一想,这还用问?你是傻子吗?这一定不可能呀。但是对于这个问题,我们可以用我们未来学习的图的知识解决!
![a2f2bb760a0dac5d91379c9066ea7629.png](https://i-blog.csdnimg.cn/blog_migrate/b1aa2e0316c3fa4599dd05a2511b8708.jpeg)
除了上述问题,对于下图,我再来提出两个问题:
- 从陈家庄到张家村,怎么走最快呢?
- 怎么修公路使得村村通的花费最少呢?
![8eff7967a5d85923ed00729eedd0117d.png](https://i-blog.csdnimg.cn/blog_migrate/2ab785babd77bafc0d33f1d134b3a269.jpeg)
有些同学说了,这还不简单,让我用我的火眼金睛数一数。但是对于下述这张图呢?
![1a9ed55a89af7f5baa960baf34cb3961.png](https://i-blog.csdnimg.cn/blog_migrate/7269a8409ed1fc76bf8865a29350af86.jpeg)
算了,我还是街边喊666(溜)吧!!!
二、什么是图(Graph)
对于上述提出的几个问题,只要你耐下性子和我修习“图法”,相信不久之后,你就不是路边那个只会喊“666”的同志,而是挥手说“同志们好”。那么到底什么是图呢?记住nick心法:图就是下面我讲的这个小东西。废话~,还不如看下图——我(灵魂画师)手绘图:
![e885803c1aba5c46105d541090719545.png](https://i-blog.csdnimg.cn/blog_migrate/583882f5f8a1e2ebec75e1748d3169db.jpeg)
以前我们学习的链表表示的是一对一的关系;学的树表示一对多的关系;而我们此次的主角,冠勇三军的图却是大有来头,从上图可以看出,图表示的是多对多的关系,通常,它包含如下“小弟”:
- 一组顶点:通常用V(Vertex)表示顶点集合
- 一组边:通常用E(Edge)表示边的集合
- 边是顶点对:即 (v,w)∈E
- (v,w)∈E,其中v,w∈V
- v,w∈V
- 无向边(v,w)
- (v,w)表示从v指向w的边,无箭头,如下图所示:
![aae216887e66f5df4233eac5773d28a8.png](https://i-blog.csdnimg.cn/blog_migrate/39d2ff91826ae6d84b097cd971ea9440.jpeg)
- 有向边
- 也表示从v指向w的边(单行线),但是它有个该死的箭头,如下图所示:
![010a54b0728ca1dca184edcbdbfac4a5.png](https://i-blog.csdnimg.cn/blog_migrate/c6085b2b419963231c410a058180b8ae.jpeg)
- 牢记:图的边不考虑重边和自回路,记不住现场扇自己两巴掌,没人看的见,如下图所示是错误的:
![4a93ebf7d3905f28e5d502ef224fc07d.png](https://i-blog.csdnimg.cn/blog_migrate/b4e43561bc6df3a2b82086a02d2e4386.jpeg)
三、抽象数据类型定义
我也知道枯燥,但是你逼着自己读一遍都做不到,大江大河等着你逛?
- 类型名称:图(Graph)
- 数据对象集:G(V, E),由一个非空的有限顶点集合V和一个有限边集合E组成。
- 操作集:对于任意图G∈Graph
- G∈Graph,以及v∈V,e∈E
- v∈V,e∈E
- Graph Create():建立并返回空图;
- Graph InsertVertex(Graph G, Vertex v):将v插入G
- Graph InsertEdge(Graph G, Edge e):将e插入G;
- Void DFS(Graph G, Vertex v):从顶点v出发深度优先(别好奇,继续往下看)遍历图G;
- Void BFS(Graph G, Vertex v):从顶点v出发宽度优先遍历图G;
- Void ShortestPath(Graph G, Vertex v, int Dist[]):计算图G中顶点v到任意其他顶点的最短距离;
- Void MST(Graph G):计算图G的最小生成树;
- ……,以后都会讲到的,别急,现在不想弄死你,否则你醉生梦死(秃头)走不动了怎么办???
![4a5f8eec2e92d4e129cb6d2275f37eab.png](https://i-blog.csdnimg.cn/blog_migrate/ab540e39473246d4e85dbf4abaedd9a6.jpeg)
四、常见术语
你随便找一本图论的书,图的常见术语随随便便几十页,为了再次不让你醉生梦死,我就例举出几个,你看着记下就好了:
![f304c1658c6b52da4ccd01df01a549aa.png](https://i-blog.csdnimg.cn/blog_migrate/95bca6d0fe8585be413a98ef2a3d1aa4.jpeg)
对,你没有看错,就这几个,还有很多!
五、怎么在程序中表示一个图
理论这东西怎么能符合我大nick的智商,那就来一点实践的吧!
逼逼叨叨一大堆,你倒是讲讲我们怎么在程序中表示一个图呀?
既然你选择了死亡,那我就告诉你吧。在程序中,我们一般有以下两种方式表示图(这并不意味着只有两个,多着呢!),分别为邻接矩阵和邻接表,下面重点戏来了,你个戏精,不是你想要来点实践的吗?
六、邻接矩阵
别听到矩阵就慌了阵脚,有我这个冠勇三军的大nick在,怕啥怕,来吧!
你可以把邻接矩阵看成一个正方形,也可以看成一个二维平面直角坐标轴,也可以混在一起看。我们先来看看它长啥挫样:
![459c195f7331953bd6730d6527f46220.png](https://i-blog.csdnimg.cn/blog_migrate/c656b2f271de8396c723d14682ccbc4c.jpeg)
不可否认的是,这张图是有点难理解的,但是你要重视接下来我讲的这两句话:
- 邻接矩阵G[N][N]——N个顶点从0到N-1编号
![42c33e9a33ce5803350e07d51015d58e.png](https://i-blog.csdnimg.cn/blog_migrate/c219cf431accf55b3c157d5337752f1c.jpeg)
对于上图的邻接矩阵,其实存在一个很大的bug,上图的邻接矩阵是沿红线对称的,也就是说,我们是否可以做到如下图所示,只要红色区域的部分呢?这样就可以节省一半空间了。
![6596e3c29b53cdf9e46d23457580f0fb.png](https://i-blog.csdnimg.cn/blog_migrate/71520da8c4ec218e4909c54250820047.jpeg)
![7e293b293001d168e9e60576dfdb953d.png](https://i-blog.csdnimg.cn/blog_migrate/de0a5dfd335d0177d097635ad9b2e955.jpeg)
6.1 邻接矩阵的优点
上面逼逼了一大堆邻接矩阵的理论,实在让人痛苦,那么使用邻接矩阵有啥好处呢?好处大大的有,有以下四点好处:
- 直观、简单、好理解,这难道不是优点吗?
- 方便检查任意一对顶间是否存在边
- 方便找任一顶点的所有邻接点(有边直接相连的顶点)
- 方便计算任一顶点的度(从该点触发的边数为出度,指向该点的边数为入度)
- 无向图:对应行(或列)非0元素的个数(出度就是入度呀!!!)
- 有向图:对应行非0元素的个数是出度;对应列非0元素的个数是入度
6.2 邻接矩阵的缺点
作为一个一个冠勇三军的大nick,不能总鼓励,也得给点打压!
那么邻接矩阵有什么缺点呢?缺点其实不多,就以下两点:
- 浪费空间——存稀疏图(点很多而便很少,其实就是0很多的意思,有大量无效元素)
- 对稠密图(特别是完全图 )
6.3 邻接矩阵的代码表示
直接上代码吧!c和python版本我都给你准备好了,但是我推荐你先看完理论再去研究代码。
6.3.1 c表示
/* c语言实现 *//* 图的邻接矩阵表示法 */ #define MaxVertexNum 100 /* 最大顶点数设为100 */#define INFINITY 65535 /* ∞设为双字节无符号整数的最大值65535*/typedef int Vertex; /* 用顶点下标表示顶点,为整型 */typedef int WeightType; /* 边的权值设为整型 */typedef char DataType; /* 顶点存储的数据类型设为字符型 */ /* 边的定义 */typedef struct ENode *PtrToENode;struct ENode{ Vertex V1, V2; /* 有向边 */ WeightType Weight; /* 权重 */};typedef PtrToENode Edge; /* 图结点的定义 */typedef struct GNode *PtrToGNode;struct GNode{ int Nv; /* 顶点数 */ int Ne; /* 边数 */ WeightType G[MaxVertexNum][MaxVertexNum]; /* 邻接矩阵 */ DataType Data[MaxVertexNum]; /* 存顶点的数据 */ /* 注意:很多情况下,顶点无数据,此时Data[]可以不用出现 */};typedef PtrToGNode MGraph; /* 以邻接矩阵存储的图类型 */ MGraph CreateGraph( int VertexNum ){ /* 初始化一个有VertexNum个顶点但没有边的图 */ Vertex V, W; MGraph Graph; Graph = (MGraph)malloc(sizeof(struct GNode)); /* 建立图 */ Graph->Nv = VertexNum; Graph->Ne = 0; /* 初始化邻接矩阵 */ /* 注意:这里默认顶点编号从0开始,到(Graph->Nv - 1) */ for (V=0; VNv; V++) for (W=0; WNv; W++) Graph->G[V][W] = INFINITY; return Graph; } void InsertEdge( MGraph Graph, Edge E ){ /* 插入边 */ Graph->G[E->V1][E->V2] = E->Weight; /* 若是无向图,还要插入边 */ Graph->G[E->V2][E->V1] = E->Weight;} MGraph BuildGraph(){ MGraph Graph; Edge E; Vertex V; int Nv, i; scanf("%d