图和散列表、二叉树同样,是一种非线性数据结构。如图1所示,图由一系列顶点以及链接顶点的边构成。由一条边链接在一块儿的顶点成为相邻顶点,好比A和B、A和D是相邻的,而A和E不是相邻的。一个顶点相邻顶点的数量叫做度,好比A的度为三、D的度为4。路径指相邻顶点的一个连续序列,如ABEI、ACDG;简单路径指不包含重复顶点的路径(除环外),如ADG;环指首尾顶点相同的路径,如ADCA,环也属于简单路径。若是图中每两个顶点之间都有路径相连,则称该图是连通的。javascript
如图2,若是图中的边具备方向,称该图为有向图。若是图中的边是双向的,则该图是强连通的,例如图3中的C和D是强连通的。图也能够是加权的,例如图3中的每条边都有权值。java
图能够用来解决计算机中的不少问题,好比搜索图中的一个特定顶点或搜索一条特定边,寻找图中的一条路径(从一个顶点到另外一个顶点) ,寻找两个顶点之间的最短路径,以及环检测。算法
图的表示方式有多种,没有绝对正确的表示方式,采用哪一种方式取决于图的类型和待解决的问题。这里介绍三种方式:邻接矩阵、邻接表、关联矩阵。segmentfault
邻接矩阵用一个二维数组来表示图中顶点的链接状况;若是索引为i的节点和索引为j的节点链接,则array[i][j] === 1,不然array[i][j] === 0,如图4。邻接矩阵的缺点是,若是图不是强连通的,矩阵中就会出现不少0,从而计算机须要浪费存储空间来表示根本不存在的边。例如,即便某一顶点只有一个相邻顶点,也须要一整行来表示该顶点的链接状况,数组
邻接表由图中每一个顶点的相邻顶点的列表所组成,如图5。只要能表示一对多的数据结构,均可以用来描述邻接表,好比多维列表(数组)、链表、散列表、字典等。
在大多数状况下,邻接表是更好的选择,但邻接矩阵也有其优势,好比要判断顶点A和B是否相邻,邻接矩阵比邻接表要快。数据结构
图5测试
在关联矩阵表示的图中,矩阵的行表示顶点,列表示边。如图6所示,咱们使用二维数组来表示二者之间的连通性,若是顶点A是边E的入射点,则array[A][E] === 1;不然,array[A][E] === 0。
关联矩阵一般用于边的数量比顶点少的状况下,以节省空间和内存。如图6,顶点数是5,边的数量是6,用邻接矩阵表示图须要的空间是5*5=25,而使用关联矩阵表示图须要的空间是5*6=30。this
图6spa
首先首先声明类的骨架:3d
function Graph() { var vertices = []; var adjList = new Dictionary(); }
其中Dictionary类的实现参考以前的文章字典。
咱们使用数组 vertices 来存储图中全部顶点的名字,以及字典 adjList 来存储邻接表。字典将会使用顶点的名字做为键,邻接顶点列表做为值。
接下来实现向图中添加顶点的方法:
this.addVertex = function(v){ vertices.push(v); adjList.set(v, []); };
该方法接受顶点 v 做为参数。咱们将该顶点添加到顶点列表 vertices 中,而且在邻接表中,设置顶点 v 做为键对应的字典值为一个空数组。
接着实现一个点到另外一个点的链接:
this.addEdge = function(v, w){ adjList.get(v).push(w); adjList.get(w).push(v); };
这个方法接受两个顶点做为参数。首先,咱们经过将 w 加入到 v 的邻接表中,添加了一条自顶
点 v 到顶点 w 的边。若是是有向图,则只须要该方法的第一行代码就好了。咱们这里要实现无向图,咱们须要添加一条自 w 向 v 的边,即该方法的第二行代码。
使用该图类进行简单的测试:
var graph = new Graph(); var myVertices = ['A','B','C','D']; for (var i=0; i<myVertices.length; i++){ graph.addVertex(myVertices[i]); // 添加图的顶点 } // 添加图的边 graph.addEdge('A', 'B'); graph.addEdge('A', 'C'); graph.addEdge('B', 'D'); graph.addEdge('C', 'D');
上述代码生成图对应的邻接表为:
A -> B C
B -> A D
C -> A D
D -> B C
有两种算法能够对图进行遍历:广度优先搜索(Breadth-First Search,BFS)和深度优先搜索(Depth-First Search,DFS)。图遍历能够用来寻找特定的顶点或寻找两个顶点之间的路径,检查图是否连通,检查图是否含有环等。
图遍历算法的思路是追踪每一个第一次访问的节点,而且追踪有哪些节点尚未被彻底探索。对于两种图遍历算法,都须要明确指出第一个被访问的顶点。
彻底探索一个顶点须要查看该顶点的每一条边。对于每一条边所链接的没有被访问过的顶点,将其标注为被发现的,并将其加进待访问顶点列表中。
咱们用三种状态来反映顶点的状态:
由于只有这三种状态,初始状态是白色,所以每一个顶点至多访问两次,这样作可以保证算法的效率。
广度优先搜索算法和深度优先搜索算法基本上是相同的,只是待访问顶点列表的数据结构不一样。
广度优先搜索算法:数据结构是队列。经过将顶点存入队列中,最早入队列的顶点先被探索。
深度优先搜索算法:数据结构是栈。经过将顶点存入栈中,沿着路径探索顶点,存在新的相邻顶点就去访问。