提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
有一部分的内容是寒假博客的延申
一、关于图的各自术语、概念
图的定义:
一个图(一般记作 G {\displaystyle G} G)由两类元素构成,分别称为“顶点”(或节点、结点)和“边”。每条边有两个顶点作为其端点,我们称这条边“连接”了它的两个端点。因此,边可定义为由两个顶点构成的集合 ----维基百科
通俗地说:许多的点用线连起来 所构成的图形。
有向、无向图:连起节点的线段,可以是有方向的,也可以是无方向的。
上图就是无向图(默认两边都可以到达),加个特定的箭头就是有向的。
加权图:图的边可以赋予各自具体的长度,这就是各自的“权重”
二、存储图的数据结构
1.二维数组(最基本的方法)
map[a][b] 代表从a点到达b点是否有路
1为可以到达,0为不可达到。
先忽略图中的箭头,看成是无向图。
举例:2点与4点是联通的,那么map[2][4]就为1(因为无向,所以map[4][2]也是一样的)。
不难发现,无向图得到的二维数组是以对角线对称的。
毕竟map[a][b]和map[b][a]一样。
接下来看一个有向图
那么得到的就(基本上)不会是对称的了-----图出自《啊哈!算法》
一般像图里的都做如下规定:
map[a][b] 是从a到b
反过来map[b][a]从b到a
毕竟是有向图,所以就不会像之前那样对称了。
三、一些具体问题和算法
1.多源最短路径问题
问题先来个简单的情况:
(我自己画的草图)
A、B两城之间的道路如下,其中很明显可以看到
A可以直接到B,但是绕弯路很远。
A也可以经过C城到达B城,很明显虽然不是直达但是路程近了。
生活经验告诉我们肯定优先选择A-C-B
但是计算机不知道啊!我们如果存放数组肯定就是:
map【A】【B】,map【C】【B】,map【A】【C】
如此存放,哪怕我们知道实际上AC+CB<AB,但是计算机不知道啊!
所以这个算法的任务,就是学会—“看起来绕路、实际上路程更短”
绕路,这个简单的例子中绕的是C。
而真正的问题中可能会有很多的城市,像C这样被绕反而路程短了。
这么些个C,就是**“多源最短路径问题”的“源”**
可能还有D城,让A、B更短,这时候C城我们又不要了;有E、F…
一个个找,直到找到最短的。
当然,“改编不是乱编,戏说不是胡说” ,绕路也不是看见个城市就绕,至少你得保证C城同时和A、B都联通吧!比如:
这咋办???
想绕也饶不了。
没路可走肯定不行,有路就一定可以吗?
这种呢?AC有路啊!那map【A】【C】就可行嘛?
不对吧!C—>A可以,A—C 可没路!注意箭头!
所以我们在构建二维数组的时候,就得注意这类情况
map【A】【C】=9999999(即无限大)。
通俗例子讲完了,就来说说严谨的解释。
是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权(但不可存在负权回路)的最短路径问题 ----维基百科
此图是《啊哈!算法》的例子
这是根据图上的直接路径得到的路径(即未经过优化的)
现在只允许经过1号顶点,求任意两点的最短路径
**此时我们只需判断e[i][1] + e[1][j] 是否比e[i][j] 要小即可。
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if ( e[i][j] > e[i][1]+e[1][j] )
e[i][j] = e[i][1]+e[1][j];
代码很简单,遍历二维数组,然后一个个尝试,看i–>j这个路径可否被1号点优化。即:(1号点帮得上忙就来帮)
这时候我们发现,虽然不是全部路线都派上用场了,但是至少也有所收获。
1号点的完成了,那在此基础上,二号点也来帮忙,所以在此前基础上,把二号点也加进来。
//经过1号顶点
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if (e[i][j] > e[i][1]+e[1][j])
e[i][j]=e[i][1]+e[1][j];
//经过2号顶点
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if (e[i][j] > e[i][2]+e[2][j])
e[i][j]=e[i][2]+e[2][j];
嗯,又好了些。
那么接下来,让三号、四号节点也来帮忙的过程就自然不必多说了。
然后,把1、2、3、4节点 都来帮忙 的过程压缩到一个for循环里,就算是大功告成啦~~~
for(int k = 1 ; k <= n ; k ++)
for(int i = 1 ; i <= n ; i ++)
for(int j = 1 ; j <= n ; j ++)
if(e[i][j] > e[i][k] + e[k][j])
e[i][j] = e[i][k] + e[k][j];
k代表几个节点来帮忙
i、j用来遍历下标。
总结
优点:比较容易容易理解,可以算出任意两个节点之间的最短距离,代码编写简单。
缺点:时间复杂度比较高(n3),不适合计算大量数据,当数据稍微大点儿的时候就可以选择其他的算法来解决问题了,不然也会是超时。
2、单源最短路 Dijkstra
话接上文说到最短路径,上次是任意俩点都得给他优化。那这次我们指定一个顶点,以他为C位 ,就想知道他到剩下其他顶点的最短路径,用floyd好像duck不必 O(n³)的时间复杂度,这谁顶的住啊
所以可否空间换时间呢?可以!接下来请出来Dijkstra算法!
老规矩,先上图。这个图,咱们就以一号点为源点,找出剩下其他点,各自与1点的最短路径。
先存储一下图中的信息,老规矩e【A】【B】是A–>B的距离。
dis[2],就是目前已知的最短的从1到2的路径。
目前已知1–>2 和1–>3,选小的那个:1–>2.
然后找2可以到4点和3点。即2–>4,2–>3也已知了
这时候发现3点好像有两条路:1–>3 和1–>2–>3,依然选择小的那个,
刚才4点也确定了。
到此为止,2的所有出度都已经结束,那么就在已知到1号点距离的点中,找离一号点最近的那个点发起新一轮拓展攻势—4号点目前离一号点最近,就决定是你了!
4–>34、4–>513、4–>615都确定了,找最小的那个!。
4–>34最小!开始比对原来的1—>3,嗯短了些!更新数据!
4–>513次之!开始比对原来的1–>5,好像没变短,嗯不更新!
4–>615最后,之前6号点默认为∞(毕竟没到达过嘛,无历史记录 ),那就勉为其难的先更新一下吧!1—>6==19
四号点的历史使命也完成啦!再一次看谁(2、4都完成使命了,就不管了)离1最近。周而复始~
到最后所有的点都会更新好,这时候,1到所有点的最短距离都有啦~!