图是非线性数据结构,是一种较线性结构和树结构更为复杂的数据结构,在图结构中数据元素之间的关系能够是任意的,图中任意两个数据元素之间均可能相关。java
图(graph)是一种网状数据结构,图是由非空的顶点集合和一个描述顶点之间关系的集合组成。算法
二元组定义:G=(V,E)数组
若是图的边限定为从一个顶点指向另外一个顶点,即每条边都是顶点的有序偶对,称之为有向图(directed graph)。网络
方向起始的顶点称为起点或尾(弧尾)。方向指向的顶点称为终点或头(弧头)。数据结构
若是图中的边没有方向性,即每条边都是顶点的无序偶对,称之为无向图(undirected graph)。ui
设图G=(V,E)和图G'=(V',E')。若是V’⊆V且E’⊆E,则称G'是G的一个子图(subgraph)。 若是V’=V且E’⊆E,则称G’是G的一个生成子图(spanning subgraph)。spa
有n(n-1)/2条边的无向图称为无向彻底图。.net
有n(n-1)条边的有向图称为有向彻底图。3d
有不多边的图称为稀疏图。指针
边较多的图称为稠密图。
对于无向图G=(V,E),若是边(u,v)∈E,则称顶点u与顶点v互为邻接点。
两个邻接点连成的边叫作两个节点的相关边。
与每一个顶点相连的边的数叫该点的度(degree)。
对有向图中某结点的弧头数称为该结点的入度(in degree)。
对有向图中某结点的弧尾数称为该结点的出度(out degree)。
有度=入度+出度
在一个图中,某个顶点Vp出发,沿着一些边通过一些顶点,到达Vg则称通过的这些顶点序列为Vp到Vg的路径。Vp是路径的始点,Vg是路径的终点。
对于有向图路径也是有向的,路径的方向只能是从起点到终点,且与它通过的每一条边的方向一致。
路径上的边或弧的数目称之为该路径的长度。
除始点和终点外,其余各顶点均不一样的路径称之为简单路径。
从一个顶点出发又回到该顶点,则所通过的路径称为回路。
始点和终点相同的简单路径称之为简单回路。
在无向图中,从一个顶点到另外一个顶点之间有路径,则称这两个顶点是连通的。
若是图中任意一对顶点之间都是连通的,则称此图为连通图。
非连通图中的每个连通部分叫连通份量。
对于有向图,若两点之间有互相到达的路径,则称这两点是强连通。
若是有向图中任何一对顶点都是强连通的,则此图叫强连通图。
有向图中最大连通子图称为有向图的强连通份量。
有些图对应每条边有一相应的数值,这个数值称为该边的权。
带权的图称为网(network)。网可分为有向网和无向网。
以下是图的抽象数据类型定义:
ADT Graph{ 数据对象D:D是具备相同性质的数据元素的集合。 数据关系R:R={<u,v>|P(u,v)∧(u,v∈D)} 基本操做: getType(); //返回图的类型 getVexNum(); //返回图中顶点数 getEdgeNum(); //返回图中边数 getVertex(); //返回图中全部顶点的迭代器 getEdge(); //返回图中全部边的迭代器 remove(v); //在图中删除特定的顶点 remove(e); //在图中删除特定的边 insert(v); //在图的顶点集中添加一个新顶点 insert(e); //在图的边集中添加一条新边 areAdjacent(u,v); //判断v是不是u的邻接点 edgeFromTo(u,v); //返回u到v的边,若是不存在返回空 adjVertexs(u); //返回顶点u的全部邻接点 DFSTraverse(v); //从顶点v开始深度优先搜索遍历图 BFSTraverse(v); //从顶点v开始广度优先搜索遍历图 shortestPath(v); //求顶点v到图中全部顶点的最短路径 generateMST(); //求无向图的最小生成树,有向图不支持此操做 toplogicalSort(); //求有向图的拓扑序列。无向图不支持此操做 criticalPath(); //求有向无环图的关键路径。无向图不支持此操做 }ADT Graph
从图的逻辑结构定义来看,没法将图中的顶点排列成一个惟一的线性序列。在图中任意一个顶点均可以当作是图的第一个顶点,对任何一个顶点而言,它的邻接点之间也不存在顺序关系。为了方便存储和操做,须要将图中的顶点按必定的序列排列起来。
顶点在图中的位置就是指该顶点在人为肯定的序列中的位置。
因为图的结构比较复杂,任意两个顶点之间均可能存在联系,所以没法以数据元素在存储区的位置来表示元素之间的关系,即图没有顺序映像的存储结构,但能够借助数组来表示数据元素之间的关系。
借助数组存储的方法有邻接矩阵表示法和邻接表表示法。
图的邻接矩阵(adjacent matrix)表示法是使用数组来存储图结构的方法,也被称为数组表示法。 它采用两个数组来表示图:一个是用于存储全部顶点信息的一维数组,另外一个是用于存储图中顶点之间关联关系的二维数组,这个关联关系数组也被称为邻接矩阵。
邻接矩阵有以下特性:
优势:
邻接矩阵表示法对于以图的顶点为主的运算比较适合。
缺点:
除彻底图外,其余图的邻接矩阵有许多零元素,特别是当n值较大,而边数相对彻底图的边n-1又少的多时,则此矩阵称为稀疏矩阵,很是浪费存储空间。
邻接表(adjacency list)是图的一种链式存储方法,邻接表表示法相似于树的孩子链表表示法。
在邻接表中对于图G中的每一个顶点vi创建一个单链表,将全部邻接于vi的顶点vj链成一个单链表,并在表头附设一个表头结点,这个单链表就称为顶点vi的邻接表。
邻接表中共有两种结点结构,分别是边表结点和表头结点。
邻接表中的每个结点均包含有两个域:邻接点域和指针域。
边表结点由3个域组成:
头结点由2个域组成:
以下图为邻接表的存储示例:
在无向图的邻接表中,顶点的每个边表结点对应于与顶点相关联的一条边。
在有向图的邻接表中,顶点的每个边表结点对应于以顶点为始点的一条弧,所以也称有向图的邻接表的边表为出边表。
在有向图的邻接表中,将顶点的每一个边表结点对应于以顶点为重点的一条弧,即用便捷点的邻接点域存储邻接到顶点的序号,由此构成的邻接表称为有向图的逆邻接表,逆邻接表有边表称为入边表。
邻接表与邻接矩阵的关系以下:
邻接表表示法示例以下:
邻接表的性质以下:
须要说明的是:
图的另外一种矩阵表示法为以顶点和边的关联关系为基础创建矩阵,这个矩阵称之为关联矩阵。定义以下:
图G=(V,E)的关联矩阵是一个|V|×|E|矩阵,使得:
在一个多图的关联矩阵中,一些列是相同的,一个列只有一个1则表明一个环。 以下是关联矩阵的表示:
从图中某个顶点出发访问图中全部顶点,且使得每一顶点仅被访问一次,这一过程称之为图的遍历。
图的遍历是图的运算中最重要的运算,图的许多运算均以遍历为基础。
图的遍历按搜索路径不一样分为深度优先搜索遍历(Depth First Search)和广度优先搜索遍历(Breadth First Search)。
深度优先搜索的基本方法是:
从图中某个顶点发v出发,访问此顶点,而后依次从v的未被访问的邻接点出发深度优先遍历图,直至图中全部和v有路径相通的顶点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个不曾被访问的顶点做起始点,重复上述过程,直至图中全部顶点都被访问到为止。
以下图:
图的深度优先搜索遍历是一个递归过程,其特色是尽量先对纵深方向的顶点进行访问。
对图进行深度优先搜索遍历时,按访问顶点的前后次序所获得的顶点序列称为该图的深度优先搜索遍历序列,简称为DFS序列。
一个图的DFS序列不必定唯一,这与算法、图的存储结构以及初始出发点有关。
图的深度优先搜索算法也可使用堆栈以非递归的形式实现,使用堆栈实现深度优先搜索的思想以下:
广度优先搜索遍历的基本方法是:
假设从图中某顶点v出发,在访问了v以后依次访问v的各个不曾访问过的邻接点,而后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”先被访问,直至图中全部已被访问的顶点的邻接点都被访问到。若此时图中尚有顶点未被访问,则另选图中一个不曾被访问的顶点做起始点,重复上述过程,直至图中全部顶点都被访问到为止。
过程以下图:
图的广度优先搜索遍历是一个递归过程,其特色是尽量先对横向的顶点进行访问。
对图进行广度优先搜索遍历时,按访问顶点的前后次序所获得的顶点序列,称为该图的广度优先搜索遍历序列,简称为BFS序列。
一个图的BFS序列不必定唯一,这与算法、图的存储结构以及初始出发点有关。
广度优先搜索遍历的实现与树的按层遍历实现同样都须要使用队列,使用队列实现广度优先搜索的思想以下:
图论中,一般将树定义为一个无回路连通图。对于无回路连通图,只要选定某个顶点做为根,以此顶点为树根对每条边定向,竟能获得一般的树。
一个连通图G的子图若是是一棵包含G的全部顶点的树,则该子图称为G的生成树。
生成树有一下特色:
由深度优先搜索获得的生成树称为深度优先生成树,简称为DFS生成树。
由广度优先搜索获得的生成树称为广度优先生成树,简称为BFS生成树。
由图的遍历可得以下概念:
若从图的某个顶点出发,能够系统地遍历图中的全部顶点,则遍历时,通过的边和图的全部顶点所构成的子图,称为图的生成树。
对于连通网络G=(V,E),边是带权的,于是G的生成树的各边也是带权的。生成树的各边的权值总和称为生成树的权,并把权最小的生成树称为G的最小生成树。
构成最小生成树的方法有多种。这些算法能够分为下面几类:
不管上述那种类型的算法,均用到了最小生成树的以下性质。
设G=(V,E)是一个连通网络,U是顶点集V的一个真子集。若是(u,v)是G中全部的一个端点在U(即u∈U)里,另外一个端点不在U(即v∈V-U)里的边中,具备最小权值的一条边,则必定存在G的一棵最小生成树包括此边(u,v)。这个性质称为MST性质。
设G(V,E)为一个连通网,顶点集V=(v1,v2,……,vn)。设T(U,TE)是所要求的G的一棵最小生成树,其中U是T的顶点集,TE是T的边集,而且将G中边上的权看做是长度。
普里姆算法的基本思想:
首先任选V中一个顶点v1,构成入选顶点集U={v1},此时入选边集TE为空集,V中剩余顶点构成待选顶点集V-U;在全部关联于入选顶点集和待选顶点集的边中选取权值最小的一条边(vi,vj)加入入选边集(这里vi为入选顶点,vj为待选顶点),同时将vj加入入选定点集。重复以上过程,直至入选顶点集U包含全部顶点(U=V),入选边集包含n-1条边,MST性质保证上述过程求得的T(U,TE)是G的一棵最小生成树。
过程以下图:
设G=(V,E)是连通网络,令最小生成树的初始状态为只有n个顶点而无边的非连通图T=(V,Φ),T中的每一个顶点自成一格连通份量。按照长度递增的顺序依次选择E中的边(u,v),若是该边断点u、v分别是当前T的两个连通份量T一、T2中的顶点,则将该边加入到T中,T一、T2也由此边链接成一格连通份量;若是u、v是当前同一个连通份量中的顶点,则舍去此边,这是由于每一个连通份量都是一棵树,此边添加到树中将造成回路。依次类推,知道T中全部顶点都在同一连通份量上为止。从而获得G的一棵最小生成树T。
过程以下:
克鲁斯卡尔算法和普里姆算法产生的生成树是相同的。
不一样之处:
在许多应用领域,带权图都被用来描述某个网络,好比通讯网络、交通网络等。这种状况下,各边的权重就对应于两点之间通讯的成本或交通费用。此时,一类典型的问题就是:在任意指定的两点之间若是存在通路,那么最小的消耗是多少。这类问题实际上就是带权图中两点之间最短路径的问题。
在图中两点之间的最短路径问题包括两个方面:一是求图中一个顶点到其余顶点的最短路径,二是求图中每对顶点之间的最短路径。
这里的路径不是指路径上边数的总和,而是指路径上各边的权值总和。
单源最短路径是指,在带权图G=(V,E)中,已知源点为s∈V,求s到其他各顶点的最短路径。
若π=(u0=s,u1,u2,… ,uk=v)是从顶点s到顶点v的最短路径,则对于任何0 ≤ i < j ≤ k,τ=(ui, ui+1, … , uj)是从顶点ui到uj的最短路径。
此定理也能够简单的描述为:最短路径的子路径也是最短路径。
算法基本思想:
设置两个顶点集S和T,S中存放已肯定最短路径的顶点,T中窜访待肯定最短路径的顶点。初始时,S中仅有一个源点,T中包含除源点外其他顶点,此时各顶点的当前最短路径长度为源点到该顶点的弧上的权值。接着选取T中当前最短路径长度最小的一个顶点v加入S,而后修改T中生于顶点的当前最短路径长度。
修改原则是:当v的最短路径长度是v到T中的顶点之间的权值之和小于该顶点的当前最短路径长度时,用前者替换后者。重复上述过程,直至S中包含全部的顶点。
Dijkstra算法只能求出源点到其他顶点的最短路径,若是须要求出带权图中任意一对顶
点之间的最短路径,能够用每个顶点做为源点,重复调用Dijkstra算法|V|次,时间复杂度为O(|V|^3)。
假设求出的每对顶点之间的最短距离使用一个|V|×|V|矩阵D保存和输出。下面定义符号D(k),0 ≤k ≤|V|。在定义中假设带权图中全部的顶点排成一个序列。
定义:D(k)(0 ≤k ≤|V|)是一个|V|阶方阵,其中D(k)[i][j]是在考虑带权图中前k个顶点,将它们做为中间顶点时从顶点vi到顶点vj的当前最短距离(1 ≤i ≤|V|,1 ≤j ≤|V|)。
D(0)表示当顶点vi,vj之间不考虑任何顶点做为中间顶点时的最短距离,显然D(0)[i][j]就是顶点vi到vj的边的权值,若是使用邻接矩阵A做为存储结构,D(0)[i][j]=A[i][j].weight。而且若是将全部的顶点均考虑在内,vi到vj的当前最短距离就是在图中vi到vj的最短距离,即δ(vi,vj) = D(|V|)[i][j] (1 ≤i ≤|V|,1 ≤j ≤|V|)。
Floyd算法的基本思想是:
(1)用邻接矩阵初始化D(0),对角线元素为0;
(2)在顶点vi、vj之间考虑顶点v1,比较在引入v1以后vi到vj的当前最短距离是否能够经过v1变得更小。把v1放在vi到vj的路径上,vi到vj之间可能会产生新的路径,其距离为D(0)[i][1] + D(0)[1][j],固然v1的引入可能反而会加大vi到vj的距离,所以须要比较D(0)[i][1] + D(0)[1][j]与D(0)[i][j]的大小。最终,在考虑v1以后vi到vj的当前最短距离为二者中小的。即:
D(1)[i][j] = min{ D(0)[i][1] + D(0)[1][j] , D(0)[i][j]}
(3)通常状况下,若是在考虑了前k-1个顶点{v1, v2, … , vk-1}以后,从顶点vi到vj的当前最短距离是D(k-1)[i][j]。那么在顶点vi、vj之间考虑前k个顶点时,顶点vi到vj的当前最短距离为如下两个距离中小的:在考虑前k-1个顶点基础上将vk放在vi到vj的路径上,此时产生新的路径长度为D(k-1)[i][k] + D(k-1)[k][j];以及不将vk放在vi到vj的路径上的距离D(k-1)[i][j]。最终,在考虑前k个顶点{v1, v2, … , vk}以后,vi到vj的当前最短距离 D(k)[i][j] = min{ D(k-1)[i][k] + D(k-1)[k][j] , D(k-1)[i][j]} 1 ≤k ≤|V|
(4)依次进行下去,直到k = |V|。此时D(|V|)[i][j]即为带权图中任意两个顶点vi到vj的最短距离。
有向无环图(directed acyclic graph)是指一个无环的有向图,简称DAG。
用顶点表示活动(Activity),用弧表示活动之间的前后次序关系的有向图,称为顶点活动图(Activity On Vertex Network),简称为AOV网。
AOV网的特色是在网中必定不能有有向回路。检测网中是否存在环,则采用拓扑排序的方法。
在一个AOV网络中,若vi为vj的先行活动,vj为vk的先行活动,则vi必为vk的先行活动,即活动的先行关系具备传递性。
若是从离散数学的观点看AOV网络中的活动关系能够当作是一个偏序关系,工程完成活动的线性序列能够当作是一个全序关系。
由某个集合上的一个偏序获得该集合上的一个全序,此操做称之为拓扑排序(topological sort)。即将AOV网络各个顶点(表明各个活动)排列成一个线性有序的序列,使得AOV网络中全部应存在的前驱和后继关系都能获得知足。拓扑排序就是构造AOV网络顶点的拓扑有序序列的运算。
拓扑排序算法的基本步骤是:
与AOV网络对应的是边表示活动的AOE网络。若是在有向无环的带权图中:
因为一个工程只有一个开始点和一个完成点,因此在正常状况下,AOE网络中只有一个入度为0的顶点,也只有一个出度为0的顶点,它们分别称之为源点和汇点。
在AOE网络中,有些活动顺序进行,有些活动并行进行。从源点到各个顶点,以致从源点到汇点的有向路径可能不止一条。这些路径的长度也可能不一样。完成不一样路径的活动所需的时间虽然不一样,但只有各条路径上全部活动都完成了,整个工程才算完成。所以,完成整个工程所需的时间取决于从源点到汇点的最长路径长度,即在这条路径上全部活动的持续时间之和。这条路径长度最长的路径就叫作关键路径(critical path)
求关键路径的算法:
上一篇:树(tree)
下一篇:排序(Sort)