图论_图结构的基础理论知识
图结构
1. 图也是一种常见的数据结构
2. 图是一种和树结构由些相似的数据结构
3. 图结构也图论,在数学的概念上,树是图的一种
4. 图论以图为研究对象,研究顶点和边组成的图形的数学理论和方法
图结构的使用场景
1. 人与人之间的关系网(六度空间理论)
2. 地铁路线图
3. 村庄间的路线图
图结构的特点
1. 图结构中有两个重要的概念:顶点和边
2. 一组顶点:通常用V(Vertex)表示顶点的集合
3. 一组边:通常边用E(Edge)表示边的集合
边表示顶点与顶点之间的连线
边可以是有向的也可以是无向的
如:A -- B表示无向, A --> B表示有向
图结构的常见术语
1. 顶点
2. 边
3. 相邻顶点:通过一条边可以连接起来的两个顶点互为相邻顶点
4. 顶点的度:一个顶点所连的相邻顶点的个数叫做顶点的度
5. 路径:由顶点V1, V2, ...组成的一个连续的序列叫做一条路径
简单路径:不包含重复的顶点
贿赂:第一个顶点和最后一个顶点相同的路径成为回路
6. 有向图:边为有向的图
7. 无向图:边全为无向的图
8. 带权图:边携带一个权重的图
9. 无权图:边没有权重的图
图的信息:
一个图结构需要包含以下信息:
1. 顶点的表示:
抽象成1 2 3... 或者A B C D...
2. 边的表示:
邻接矩阵,即一个二维数组表示相邻顶点直接按的距离
邻接矩阵可以方便的表示有向图和无向图,带权图和无权图。
邻接矩阵的问题:
邻接矩阵中,如果用0表示无边,1表示有边,当顶点较多,边较少时,邻接矩阵就是一个稀疏矩阵,造成存储空间的浪费
邻接表:邻接表也是一种表示边的存储方式,作用和邻接矩阵一样
1. 邻接表由每个顶顶以及和顶点相邻的顶点列表组成
邻接表存在的问题:
1. 出度容易计算,入度计算比较麻烦
出度:一个顶点连接的相邻顶点
入度:连接顶点的相邻顶点
图结构的封装:
1. 数据存储:
1. 顶点用数组存储
2. 边用邻接表存储
2. 属性
1. this.vertexs 顶点(数组)
2. this.edges 字典
3. 方法
1. addVertex() 添加顶点
2. addEdge() 添加边
3. toString() 返回邻接表
4. 顶点遍历
1. 广度优先搜索(Breadth-First Search, BFS)
基于队列,入队列的顶点先被访问,顺序和层序遍历一样
2. 深度优先搜索(Depth-First Search, DFS)
基于栈或递归,
这两种遍历算法都需要指定第一个被访问的顶点
图结构的代码实现:
function Graph(){ // 图的属性 // 1. 图的顶点(数组) // 2. 图的边(字典) this.vertexs = []; this.edges = {}; // 图的方法 // 1. 添加顶点 addVertex() Graph.prototype.addVertex = function(v){ this.vertexs.push(v); this.edges[v] = []; } // 2. 添加边 addEdge() Graph.prototype.addEdge = function(v1, v2){ if(this.edges[v1].indexOf(v2) == -1){ this.edges[v1].push(v2); } if(this.edges[v2].indexOf(v1) == -1){ this.edges[v2].push(v1); } } // 3. 打印邻接表 toString() Graph.prototype.toString = function(){ var res = ""; for(var i = 0; i < this.vertexs.length; i++){ res = res + this.vertexs[i] + " |-->| "; // 这里的item为取出的邻接表中的一项(即一个数组) var item = this.edges[this.vertexs[i]]; for(var j = 0; j < item.length; j++){ res = res + item[j] + " "; } res = res + "\n"; } return res; } // 4. 颜色初始化 Graph.prototype.initColor = function(){ var colors = {}; for(var k = 0; k < this.vertexs.length; k++){ colors[this.vertexs[k]] = 'white'; } return colors; } // 5. 图的遍历 /* 广度优先搜索(Breadth-First Search, BFS) 基于队列,入队列的顶点先被 深度优先搜索(Depth-First Search, DFS) 基于栈或递归, 这两种遍历算法都需要指定第一个被访问的顶点 */ // 5.1 广度优先搜索 Graph.prototype.bfs = function(firstV){ // 1. 初始化颜色 var colors = this.initColor(); // 2. 创建队列 var q = new Queue(); // 3. 将初始顶点压入队列 q.enqueue(firstV); colors[firstV] = 'gray'; var resultString = ""; // 4. 循环遍历,将所有的顶点都压入队列 while(!q.isEmpty()){ // 4.1 取出队列头部的顶点 var v = q.dequeue(); // console.log(v); resultString += v + " "; // 4.2 获取这个顶点的相邻顶点 var vList = this.edges[v]; // 4.3 将这些顶点压入队列中 for(var j = 0; j < vList.length; j++){ if(colors[vList[j]] == 'white'){ // 颜色为white表示未被访问过 q.enqueue(vList[j]); // 将颜色设置为gray表示已经压入队列了 colors[vList[j]] = 'gray'; } } } // 5. 返回字符串 return resultString; } // 5.2 深度优先搜索 Graph.prototype.dfs = function(firstV, colors){ var colors = this.initColor(); var res = ""; this.dfsV(firstV, colors, function(vertex){ res += vertex + " "; }); return res; } Graph.prototype.dfsV = function(v, colors, handler){ // 1. 处理这个节点 handler(v); colors[v] = 'red'; // 2. 取出邻接表一个元素数组 var vlist = this.edges[v]; // 3. 依次访问取出的数组中的元素 for(var k = 0; k < vlist.length; k ++){ if(colors[vlist[k]] == 'white'){ this.dfsV(vlist[k], colors, handler); } } } }