数据结构与算法学习——图论

什么是图?

在计算机程序设计中,图结构也是一种非常常见的数据结构
但是图论其实是一个非常大的话题
图结构是一种与树结构有些相似的数据结构
图论是数学的一个分支,并且在数学概念上,树是图的一种
它以图为研究对象,研究顶点和边组成的图形的数学理论和方法
主要研究的目的是事务之间的关系,定点代表事务,边代表两个事物间的关系

图的现实案例

人与人之间的关系网
甚至科学家们在观察人与人之间的关系网时,还发现了六度空间理论

六度空间理论

理论上认为世界任何不认识的两个人只需要很少的中间人就可以建立起联系。
并非一定经过六步,只是需要很少的步骤

数据结构与算法学习——图论_第1张图片

图通常有什么特点?

一组顶点:通常用V(Vertex)表示顶点的集合
一组边:通常用E(Edge)表示边的集合
边是顶点和顶点之间的连线
边可以是有向的,也可以是无向的
比如A----B,通常表示无向
A---->B,通常表示有向

七桥问题

图形一笔画完的条件:有0个奇点或有2个奇点
我们来看一下口这个字有4个偶点0个奇点所以一笔能够画完(由双数的边连接的都是偶点,由单数的边连接的都是奇点)
数据结构与算法学习——图论_第2张图片

田字不能一笔画成,因为它的奇点有四个(红色)违反了规则。

数据结构与算法学习——图论_第3张图片

图的术语

顶点

顶点刚才我们已经介绍过了,表示图中的一个节点
比如下图中的数字

边刚才我们也介绍过了,表示顶点和顶点之间的连线
比如地铁站中两个站点之间的直接连线,就是一个边
注意:这里的边不要叫做路径,路径有其他的概念,待会儿我们会介绍
下图中 0 - 1有一条边,1 - 2有一条边,0 - 2没有边

相邻顶点

由一条边连接在一起的顶点称为相邻顶点

一个顶点的度是相邻顶点的数量
比如0顶点和其他两个顶点相连,0顶点的度是2
比如1顶点和其他四个顶点相连,1顶点的度是4

路径

路径是顶点v1,v2...,vn的一个连续序列,比如下图中0 1 5 9 就是一条路径
简单路径:简单路径要求不包含重复的顶点,比如0 1 5 9是一条简单路径
回路:第一个顶点和最后一个顶点相同的路径称为回路 比如0 1 5 6 3 0

无向图:

下图图就是一张无向图,因为所有的边都没有方向
比如 0 - 1之间有边,那么说明这条边可以保证0 ->1,也可以保证1 ->0

无权图:

下图就是一张无权图(边没有携带权重),图中的边是没有任何意义的。
不能说4-9的边比0-1的边更远或者更长。

数据结构与算法学习——图论_第4张图片

有向图

有向图表示的图中的边是有方向的
比如0->1,不能保证一定可以1->0,要根据方向来定,比如下图就是一张有向图

带权图

带权图表示边有一定的权重
这里的权重可以是任意你希望表示的数据
比如距离或者花费的时间或者票价

数据结构与算法学习——图论_第5张图片

图的表示

怎么在程序中表示图呢?
我们知道一个图包含很多顶点,另外包含顶点和顶点之间的连线(边)
这两个都是非常重要的图信息,因此都需要在程序中提现出来
顶点的表示相对简单,我们先讨论顶点的表示
上面的顶点,我们抽象成1 2 3 4,也可以抽象成A B C D
在后面的案例中,我们使用A B C D
那么这些A B C D我们可以使用一个数组来存储起来(存储所有的顶点)
当然A B C D有可能还表示其他含义的数据(比如村庄的名字)
那么边如何表示呢?
因为边是两个顶点之间的关系,所以表示起来会稍微麻烦一些

邻接表

邻接表由图中每个顶点以及和顶点相邻的顶点列表组成
这个列表有很多种方式来存储:数组/链表/哈希表都可以
数据结构与算法学习——图论_第6张图片

图片解析
比如我们要表示和A顶点有关联的顶点(边),A和B/C/D有边
那么我们可以通过A找到对应的数组/链表/哈希表,再取出其中的内容即可

邻接表的问题:

邻接表计算出度是比较简单的(出度:指向别人的数量,入度:指向自己的数量)
邻接表如果需要计算有向图的入度,那么是非常麻烦的事情
它必须构造一个逆邻接表,才能有效的计算入度,但是在开发中出度相对用的比较少

图的遍历思想

图的遍历思想和树的遍历思想是一样的
图的遍历意味着需要将图中每个顶点都访问一遍,并且不能有重复的访问
有两种算法可以对图进行遍历
广度优先搜索(Breadth-first Search,简称BFS)
深度优先搜索(Depth-First Search,简称DFS)
两种遍历算法都需要明确指定第一个被访问的顶点

遍历的思想

两种算法的思想:
BFS:基于队列,入队列的顶点先被探索
DFS:基于栈或使用递归,通过将顶点存入栈中,顶点是沿着路径被探索的,存在新的相邻顶点就去访问

为了记录顶点是否被访问过,我们同时使用三种颜色来反映他们的状态:
白色:表示该顶点还没有被访问过
灰色:表示该顶点被访问过,但未被探索过
黑色:表示该顶点被访问过且被完全探索过

广度优先搜索

广度优先搜索算法思路:
广度优先算法会从指定的第一个顶点开始遍历图,先访问其所有的相邻节点,就像一次访问图的一层,换句话说,就是先宽后深的访问顶点
数据结构与算法学习——图论_第7张图片

深度优先搜索思路:

深度优先搜索算法将会从第一个指定的顶点开始遍历图,沿着路径知道这条路经最后被访问了。
接着原路回退并探索下一条路径
数据结构与算法学习——图论_第8张图片

代码实现

class Graph {
  constructor() {
    this.vertexes = [];
    this.edges = [];
  }
  // 添加顶点
 addVertex(v) {
    this.vertexes.push(v);
    this.edges[v] = [];
  }
  // 添加边
 addEdge(v1, v2) {
    this.edges[v1].push(v2);
    this.edges[v2].push(v1);
  }
  //  toString
 toString() {
    let resString = '';
    for (let i = 0; i < this.vertexes.length; i++) {
      let v = this.vertexes[i];
      resString += v + '->';
      for (let j = 0; j < this.edges[v].length; j++) {
        resString += this.edges[v][j]
      }
      resString += 'n'
 }
    return resString;
  }
  // 初始化颜色
 initColors() {
    let colors = [];
    for (let i = 0; i < this.vertexes.length; i++) {
      let v = this.vertexes[i];
      for (let j = 0; j < this.edges[v].length; j++) {
        colors[this.edges[v][j]] = 'white'
 }
    }
    return colors;
  }
  // 广度遍历 bfs bfs(v, handler) {
    let colors = this.initColors();
    let items = [];
    items.push(v);
    colors[v] = 'gray';
    while (items.length > 0) {
      let headData = items.shift();
      for (let i = 0; i < this.edges[headData].length; i++) {
        let e = this.edges[headData][i];
        if (colors[e] == 'white') {
          colors[e] = 'gray';
          items.push(e)
        }
      }
      handler(headData)
      colors[v] = 'black';
    }
  }
  //  dfs 深度遍历
 dfs(v, handler) {
    let colors = this.initColors();
    this.dfsSearch(v, handler, colors)
  }
  dfsSearch(v, handler, colors) {
    colors[v] = 'gray';
    handler(v);
    colors[v] = 'black';
    for (let i = 0; i < this.edges[v].length; i++) {
      let e = this.edges[v][i];
      if (colors[e] == 'white') {
        this.dfsSearch(e, handler, colors)
      }
    }
  }
}
代码测试
let graph = new Graph();
//添加顶点操作
let myVertexes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
myVertexes.forEach(v => graph.addVertex(v));
//添加边操作
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('A', 'D');
graph.addEdge('B', 'E');
graph.addEdge('B', 'F');
graph.addEdge('C', 'G');
graph.addEdge('D', 'G');
graph.addEdge('D', 'H');
graph.addEdge('E', 'I');
//toString方法测试
alert(graph.toString());
 //深度遍历测试
let resString = '';
graph.dfs(myVertexes[0], function (key) {
  resString += key + ' '
})
alert(resString)
//广度遍历测试
let resString1 = '';
graph.bfs(myVertexes[0], function (key) {
  resString1 += key + ' '
})
alert(resString1)

你可能感兴趣的:(数据结构和算法,图论)