图是一种多对多的关系。
图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为: G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
无向图
图中任意两顶点之间的边都是无向边,则该图为无向图(Undirected graphs)。一般使用小括号表示“()”。
有向图
图中任意两顶点之间的边都是有向边,则该图为有向图(Directed graphs)。一般使用尖括号表示“<>”。
完全图
无向完全图
: 在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。(含有n个顶点的无向完全图有(n×(n-1))/2条边)有向完全图
: 在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称该图为有向完全图。(含有n个顶点的有向完全图有n×(n-1)条边)邻接
无向图中若两个顶点之间有边,则称之为邻接。
有向图中若存在边
度
顶点Vi的度(Degree)是指在图中与Vi相关联的边的数量。对于有向图来说,有入度(In-degree)和出度(Out-degree)之分,有向图顶点的度等于该顶点的入度和出度之和。
路径
从顶点Vi出发有一组边可到达顶点Vj,则称顶点Vi到顶点Vj的顶点序列为从顶点Vi到顶点Vj的路径(Path)。
出发点和结束点相同的称为环。序列中顶点不重复的路径称为简单路径。
连通
若从顶点Vi到顶点Vj有路径可达,则称两顶点连通。若图中任意俩顶点都连通,则称该图为连通图。
权
有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权(Weight)。
图的常用的存储结构有:邻接矩阵、邻接链表、十字链表、邻接多重表和边表,其中邻接矩阵和邻接链表是比较常用的表示方法。
邻接矩阵表示法(Adjacency Matrix),用两个数组来表示图,一个一维数组存储顶点信息,一个二数组存储图中边的信息。
具体表示如下图(图源百度百科)
邻接表是图的一种链式存储结构,邻接表由表头节点和表节点两部分组成,图中每个顶点均对应一个存储在数组中的表头节点。如果这个表头节点所对应的顶点存在邻接节点,则把邻接节点依次存放于表头节点所指向的单向链表中。
图源来自Google。
其他结构略。
本质上就是对每个顶点查找其邻接点的过程。时间复杂度由存储结构确定。邻接矩阵存储的遍历复杂度为O(n2),邻接表存储的遍历复杂度为O(n)。
基本思想:假定所有顶点均未被访问,随机访问一个顶点,然后访问该节点的任意一个邻接顶点,重复以上过程直到无未被访问过的邻接点,若仍有顶点未被访问,则从剩余顶点当中随机选择一个访问,重复上述操作,直至所有顶点均被访问。
深度优先搜索是一个递归的过程。
基本思想:假定所有顶点均未被访问,最忌访问一个顶点,然后依次访问该顶点的所有邻接顶点,然后依次访问这些邻接点的邻接点。并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。
广度优先搜索遍历图的过程是以v为起点,由近至远,依次访问和v有路径相通且路径长度为1,2…的顶点。
算法实现见后续篇章。
图篇参考文章:https://www.cnblogs.com/skywang12345/p/3711483.html, 在此基础上做了内容的增改。
连通图在遍历当中只用从一个顶点开始遍历就能走完全图,而非连通图则需要通过多个顶点才能够遍历全部。非连通图每一次遍历的新起点恰好是其各个连通分量的顶点集。
连通图
: 在无向图中,若任意两个顶点vi与vj都有路径相通,则称该无向图为连通图。
强连通图
: 在有向图中,若任意两个顶点vi与vj都有路径相通,则称该有向图为强连通图。
连通网
: 在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
生成树
: 一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
最小生成树
: 在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
参考文章 勿在浮沙筑高台http://blog.csdn.net/luoshixian099/article/details/51908175
Prim算法是以点为对象,挑选与点相连的最短边来构成最小生成树。而Kruskal算法是以边为对象,不断地加入新的不构成环路的最短边来构成最小生成树。
Kruskal涉及大量对边的操作,所以它适用于稀疏图;普通的prim算法适用于稠密图,但堆优化的prim算法更适用于稀疏图,因为其时间复杂度是由边的数量决定的
Prim算法
每一步骤选择相邻权重最小边的点加入集合,算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。复杂度O(n2)
1)以某一个点开始,寻找当前该点可以访问的所有的边;
2)在已经寻找的边中发现最小边,这个边必须有一个点还没有访问过,将还没有访问的点加入我们的集合,记录添加的边;
3)寻找当前集合可以访问的所有边,重复2的过程,直到没有新的点可以加入;
4)此时由所有边构成的树即为最小生成树
Kruskal算法
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。复杂度为O(n log n)。
把图中的所有边按代价从小到大排序;
把图中的n个顶点看成独立的n棵树组成的森林;
按权值从小到大选择边,所选的边连接的两个顶点**ui,vi**应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
重复(3),直到所有顶点都在一颗树内或者有n-1条边为止
算法实现见后续篇章。
路径问题一般是基于带权有向图,称路径上第一个顶点为源点,路径最后一个顶点为终点。
路径长度最短的最短路径的特点:
问题解法:
时间复杂度为O(n2),Dijkstra算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。
它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。
基本思想
通过Dijkstra计算图G中的最短路径时,需要指定起点s(即从顶点s开始计算)。
此外,引进两个集合S和U。S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而U则是记录还未求出最短路径的顶点(以及该顶点到起点s的距离)。
初始时,S中只有起点s;U中是除s之外的顶点,并且U中顶点的路径是”起点s到该顶点的路径”。然后,从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。 然后,再从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。 … 重复该操作,直到遍历完所有顶点。
参考 https://zhuanlan.zhihu.com/p/40338107
时间复杂度为O(n3),弗洛伊德算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或有向图或负权(但不可存在负权回路)的最短路径问题,同时也被用于计算有向图的传递闭包。
一般来说,从Vi到Vj的最短距离有两种,一是从Vi直接到Vj,二是两者之间经过若干其他顶点。我们可以这样来理解,当要求两个顶点之间的最短距离时,我们假设Dis(i,j)为Vi到Vj的最短距离,对于每一个Vk,我们检查Dis(i,k) + Dis(k,j) < Dis(i,j)是否成立,如果成立,证明从i到k再到j的路径比i直接到j的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点k,Dis(i,j)中记录的便是i到j的最短路径的距离。
从任意一条单边路径开始,边的长度为权值,若两顶点不邻接,权值设为无穷大;对每一对顶点Vi到Vj,看看是否存在一个Vk使得从Vi到Vk再到Vj比己知的路径更短,如果存在更新它。
参考 https://www.jianshu.com/p/b59db381561a
由集合上的一个偏序得到该集合上的一个全序,这个操作称为拓扑排序。有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。
拓扑排序(Topological Sorting)是一个有向无环图(DAG, Directed Acyclic Graph)的所有顶点的线性序列。且该序列必须满足下面两个条件:
基本思想
从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。
从图中删除该顶点和所有以它为起点的有向边。
重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。
上图为拓扑排序的简单示范。