【数据结构十一】图

图论:是数学的一个分支,以图为研究对象,研究顶点和边组成的图形的数学理论和方法。研究的主要目的是事物之间的关系,顶点代表事物,边代表两个事物间的关系。

图的术语:

请添加图片描述

顶点:

顶点:图中的一个结点。例如:顶点 0。

相邻顶点:由一条边连接在一起的顶点。例如:顶点 0 和顶点 1 是相连的,顶点 0 和顶点 1 就是相邻顶点。

顶点的度:一个顶点的度是相邻顶点的数量。例如:顶点 0 和顶点 1、顶点 3 相连,因此顶点 0 的度是 2。

出度:指向别的顶点的边的数量。

入度:指向自己的边的数量。

边:

边:顶点与顶点之间的连线。例如:顶点 0 和顶点 1 之间就有一条边。

路径:

路径:是顶点 v1、v2…vn的一个连续序列。例如:0 1 5 9 就是一条路径。

简单路径:路径中不包含重复的顶点。例如:0 1 5 9 就是一条简单路径,0 1 5 2 4 5 9 就不是一条简单路径。

回路:第一个顶点和最后一个顶点相同的路径。例如:0 1 5 6 3 0。

图:

图:是由顶点的集合和边的集合组成的,通常用 V(Vertex)表示顶点的集合,用 E(Edge)表示边的集合。

有向图:边是有方向的图。

无向图:所有的边都没有方向的图。例如:上图就是无向图,顶点 0 和顶点 1之间的边是无向的,既能从顶点 0 到顶点 1,也能从顶点 1 到顶点 0。

带权图:边带有一定权重的图,权重可以是任何希望表示的数据,比如距离或者花费的时间等等。

无权图:边没有携带权重的图。例如:上图就是无权图,因为不能说顶点 0 和顶点 3 之间的边比顶点 0 和顶点 1 之间的边更长。

生活中的图:

  1. 人与人之间的关系网:可以将一个个人看作顶点,人与人之间的关系看作边。
    请添加图片描述
  2. 地铁图:可以将一个个站点看作顶点,站点与站点之间的线路看作边。

    高德地图、百度地图导航时,就用到了图去规划时间最短、线路最短等的线路。
    请添加图片描述

  3. 村庄间的关系网:可以将一个个的村庄看作顶点,村庄与村庄之间的道路看作边。
    请添加图片描述

图的表示方法:

常见的表示图的方式有:邻接矩阵和邻接表。

邻接矩阵:

它是一个二维数组,让每个顶点和一个整数关联,该整数作为数组的下标值,数组中的元素表示两个顶点之间是否有一条边,0 表示没有边,1 表示有边。例如:[0][2] 就表示顶点 A 到顶点 C 有一条边。

A - AB - B 等是自回路,也就是顶点到自己的连线,通常用 0 表示。

邻接矩阵的缺点:如果图是一个稀疏图,那么邻接矩阵中将存在大量的 0,这意味着浪费了计算机存储空间来表示根本不存在的边。
在这里插入图片描述

邻接表:

邻接表由图中的每个顶点以及和顶点相邻的顶点列表组成,以顶点作为索引,以和顶点相邻的顶点列表作为值。

邻接表的缺点:邻接表计算出度比较简单,但是计算入度就非常麻烦了,此时必须构造一个逆邻接表,才能有效地计算入度。不过,在实际开发中,使用入度相对较少。
在这里插入图片描述

图的遍历:

图的遍历意味着需要将图中的每个顶点访问一遍,并且不能有重复的访问。

为了记录顶点是否被访问过,设置三种状态来标识:

  1. 未发现:表示该顶点还没有被访问。
  2. 已发现:表示该顶点被访问过,但并未被探索过。也就是说该顶点已经被访问过,但是该顶点的相邻顶点还没被访问。
  3. 已探索:表示该顶点被访问过且被完全探索过。

有两种算法可以对图进行遍历:广度优先搜索、深度优先搜索。两种遍历算法都需要明确指定第一个被访问的节点。

广度优先搜索(Breadth-First Search、BFS):

广度优先搜索的思想:基于队列。

类似于树结构的层序遍历。

请添加图片描述
上图广度优先搜索的过程:

  1. 首先指定第一个顶点 A 并访问它。
  2. 接着访问顶点 A 的未发现的相邻顶点 B、C、D。
  3. 接着访问顶点 B 的未发现的相邻顶点 E、F。
  4. 接着访问顶点 C 的未发现的相邻顶点 G。
  5. 接着访问顶点 D 的未发现的相邻顶点 H。
  6. 接着访问顶点 E 的未发现的相邻顶点 I。

深度优先搜索(Depth-First Search、DFS):

深度优先搜索:从一条路径的起始顶点开始追溯,直到到达最后一个顶点;然后回溯,继续追溯下一条路径,直到到达最后的顶点;如此往复,直到没有路径为止。

类似于树结构中的先序遍历。

请添加图片描述
上图深度优先搜索的过程:

  1. 首先指定第一个顶点 A 并访问它。
  2. 接着访问顶点 A 的相邻顶点 B。
  3. 接着访问顶点 B 的相邻顶点 E。
  4. 接着访问顶点 E 的相邻顶点 I。
  5. 由于顶点 I 和顶点 E 都没有未发现的相邻顶点了,因此沿着原路回退到顶点 B,访问顶点 B 的另一个相邻顶点 F。
  6. 由于顶点 F 和顶点 B 都没有未发现的相邻顶点了,因此沿着原路回退到顶点 A,访问顶点 A 的另一个相邻顶点 C。
  7. 接着访问顶点 C 的相邻顶点 D。
  8. 接着访问顶点 D 的相邻顶点 G。
  9. 由于顶点 G 没有未发现的相邻顶点了,因此沿着原路回退到顶点 D,访问顶点 D 的另一个相邻顶点 H。

图的实现:

使用邻接表实现无向图。
请添加图片描述

// 封装图
function Graph() {
	// 图中的元素:顶点的集合、边的集合
	this.vertexes = []
	this.edges = new Dictionay() // 此处用到了第六章封装的字典数据结构
}
// 图中的操作
// 添加顶点
Graph.prototype.addVertex = function(v) {
	this.vertexes.push(v)
	this.edges.set(v, [])
}
// 添加边
Graph.prototype.addEdge = function(v1, v2) {
	this.edges.get(v1).push(v2)
	this.edges.get(v2).push(v1)
}
// 将图结构的内容以字符串形式返回
Graph.prototype.toString = function() {
	var resultStr = ''
	for (var i = 0; i < this.vertexes.length; i++) {
		var vertexe = this.vertexes[i]
		resultStr += vertexe + ' -> ' + this.edges.get(vertexe).join(',') + '\n'
	}
	return resultStr
}
// 调用图
var graph = new Graph()
graph.addVertex('A')
graph.addVertex('B')
graph.addVertex('C')
graph.addVertex('D')
graph.addVertex('E')
graph.addVertex('F')
graph.addVertex('G')
graph.addVertex('H')
graph.addVertex('I')
graph.addEdge('A', 'B')
graph.addEdge('A', 'C')
graph.addEdge('A', 'D')
graph.addEdge('B', 'E')
graph.addEdge('B', 'F')
graph.addEdge('C', 'D')
graph.addEdge('C', 'G')
graph.addEdge('D', 'G')
graph.addEdge('D', 'H')
graph.addEdge('E', 'I')
console.log(graph.toString())

请添加图片描述

广度优先搜索的实现:

  1. 创建一个队列,并将指定的第一个被访问的顶点添加到队列中。
  2. 只要队列不为空,就循环执行以下步骤:
  1. 取出队首的顶点。
  2. 获取此顶点的所有未发现(白色)的相邻顶点,将其标记为已发现(灰色)并添加到队列中。
  3. 访问此顶点,并将其标记为已探索(黑色)。
// 初始化顶点的状态为未发现
Graph.prototype.initializeColor = function(v1, v2) {
	var colors = []
    this.vertexes.forEach(v => {
        colors[v] = 'white'
    })
    return colors
}
// 广度优先搜索(BFS)
Graph.prototype.BFS = function(initV) {
    var resultStr = ''

	// 1. 初始化颜色
    var colors = this.initializeColor()

    // 2. 创建队列
    var queue = new Queue()
    // 3. 将初始顶点添加到队列中
    queue.enqueue(initV)
    // 4. 循环从队列中取出顶点
    while(!queue.isEmpty()) {
        // 4.1 取出队首的顶点
        var v = queue.dequeue()

        // 4.2 获取此顶点的相邻顶点
        var vList = this.edges.get(v)
        // 4.3 遍历所有的相邻顶点并将其添加到队列中
        vList.forEach(v => {
            // 如果此顶点未被发现,则将其状态改为已发现并添加到队列中
            if (colors[v] === 'white') {
                colors[v] = 'gray'
                queue.enqueue(v)
            }
        })

        // 4.4 访问该顶点,并将其状态改为已探索
        resultStr += v + ' '
        colors[v] = 'black'
    }

    return resultStr
}
// 测试广度优先搜索
console.log(graph.BFS(graph.vertexes[0])) // A B C D E F G H I 

深度优先搜索的实现:

  1. 访问此顶点,并将其标记为已发现(灰色)。
  2. 获取此顶点的相邻顶点列表,只要其中的相邻顶点状态为未发现(白色),就再次执行步骤1、步骤2。
  3. 将此顶点标记为已探索(黑色)。
// 深度优先搜索的递归函数
Graph.prototype.DFSRecursiveFunc = function (v, colors) {
    // 1. 将此顶点的状态改为已发现
    colors[v] = 'gray'

    // 2. 访问此顶点
    console.log(v)

    // 3. 访问此顶点的相邻顶点
    var vList = this.edges.get(v)
    for(var i = 0; i < vList.length; i++) {
        var adjacentV = vList[i]
        // 如果此相邻顶点的状态为未发现,则递归访问此相邻顶点
        if (colors[adjacentV] === 'white') {
            this.DFSRecursiveFunc(adjacentV, colors)
        }
    }

    // 4. 将此顶点的状态改为已探索
    colors[v] = 'black'
}
// 深度优先搜索(DFS)
Graph.prototype.DFS = function(initV) {
    // 1. 初始化颜色
    var colors = this.initializeColor()

    // 2. 从初始顶点开始递归访问
    this.DFSRecursiveFunc(initV, colors)
}
// 测试深度优先搜索
graph.DFS(graph.vertexes[0]) // A B E I F C D G H 
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值