图的定义
- 图由边的集合和定点的集合组成,
- 如果一个图的顶点是有序的,则可以称为有向图,在对图中的顶点排序后,便可以绘制一个流向
- 如果图是无序的,则称为无序图
- 图中的一系列顶点构成路径,路径中所有的顶点都由边连接。路径的长度用路径的第一个顶点到最后一个顶点之间的数量表示。由指向自身的顶点组成的路径称为环,环的长度为0.
- 圈是至少有一条边的路径,且路径的第一个顶点和最后一个顶点相同。无论是有向图还是无向图。只要没有重复边的圈就是简单圈,除了第一个顶点和最后一个顶点以外没有其他顶点有重复的圈 称为平凡圈。
- 如果顶点之间有路径,那么这个顶点就是强联通的
用图来对现实问题进行建模
- 交通流量
- 运算系统
- 计算机网络系统
图类
如果用树和二叉树的方式来构建图,那么随着图的增大,这种构建方式效率会很低
表示顶点
一个顶点的类,label同于标识顶点,wasVisited表示这个顶点是否被访问过
function Vertex(label, wasVisited) {
this.label = label
this.wasVisited = wasVisited
}
表示边
与二叉树不同,一个顶点既可以有一条边也可以由多条边。 如果顶点2与0134都相连,那么在数组索引为2的位置存储0134表示。
adj[2,2,[0,1,3,4],2,2]
构建图
function Vertex(label, wasVisited) {
this.label = label
this.wasVisited = wasVisited
}
function Graph(v) {
this.vertices = v;
this.edgs = 0
this.adj = []
for(var i = 0; i< this.vertices; i++) {
this.adj[i] = []
this.adj[i].push('')
}
}
Graph.prototype = {
addEdge: function(v, w) {
this.adj[w].push(v)
this.adj[v].push(w)
this.edgs ++
},
show: function() {
var arr = []
for(var i = 0; i< this.vertices; i++) {
arr.push(`${i} ->`)
for(var j = 0; j< this.vertices; j++) {
if(this.adj[i][j]!=undefined) {
arr.push(this.adj[i][j]+ ' ')
}
}
arr.push(',')
}
return arr
}
}
var g = new Graph(5)
g.addEdge(0,1)
g.addEdge(0,2)
g.addEdge(1,3)
g.addEdge(2,4)
g.show()
搜索图
确定一个指定的顶点可以到达哪些顶点,这是对图的基本操作。 在图上可以执行深度优先和广度优先
深度优先搜索
从一条路径的顶点开始追溯,知道到达最后一个顶点,然后回溯,继续追溯吓一跳路径。直到找到最后的顶点。
访问一个没有访问过的顶点,将它标记已访问,再递归访问在初始顶点的临表中其他没有被访问的顶点 在类中添加一个marked数组
this.marked = []
for(var i = 0; i< this.vertices; i++) {
this.marked[i] = false
}
// 深度优先搜索
dfs: function(v) {
this.marked[v] = true
if(this.adj[v] != undefined) {
console.log(`Visited vertex: ${v}`)
this.adj[v].forEach(w => {
if(!this.marked[w]) {
this.dfs(w)
}
})
}
}
function(v) {
this.marked[v] = true
if(this.adj[v] != undefined) {
this.adj[v].forEach(w => {
if(!this.marked[w]) {
this.dfs(w)
}
})
}
}
深度优先思路:
- 初始化marked 数组
- 判断当前顶点临接数组是否有其他顶点,有则遍历
- 遍历所有这个顶点的临接顶点是否被标记,没有被标记则进行递归dfs(这个顶点)
广度优先
从第一个顶点开始搜索,尝试访问尽量靠近他的顶点。
- 查找与当前顶点相邻的未访问顶点,将其添加到已访问顶点的队列中
- 从图中取出下一个顶点V,添加到已访问的顶点队列
- 将所有与V相邻的未被访问的顶点添加到队列
bfs: function(s) {
var queue = []
this.marked[s] = true
queue.push(s)
while(queue.length > 0) {
var v = queue.shift()
if(this.adj[v]) {
console.log(v)
this.adj[v].forEach(w => {
if(!this.marked[w]) {
this.marked[w] = true
queue.push(w)
}
})
}
}
}
查找最短路径
广度优先算法,会自动查找一个顶点到另一个相邻顶点的最段路径。 只要对广度优先算法加以修改就能得到最短路径
确定路径
需要一个数据来保存一个顶点到下一个顶点的所有边,我们称为edgeTo。因为是使用了广度优先,我们每次都会遇到一个没有被标记的顶点。除了对它进行标记外,还会对临界列表中我们正在探索的那个顶点添加一条边到这个顶点
bfs: function(s) {
var queue = []
this.marked[s] = true
queue.push(s)
while(queue.length > 0) {
var v = queue.shift() // 取出队首
if(this.adj[v]) {
console.log(`Visited vertex: ${v}`)
}
this.adj[v].forEach(w => {
if(!this.marked[w]) {
this.edgeTo[w] = v
this.marked[w] = true
queue.push(w)
}
})
}
},
辅助方法 pathTo 用于存储于指定顶点有公共边的所以顶点
pathTo: function(v) {
var source = 0
if(!this.hasPathTo(v)) {
return undefined
}
var path = []
for(var i = v; i != source; i = this.edgeTo[i]) {
path.push(i)
}
path.push(source)
return path
},
hasPathTo: function(v) {
return this.marked[v]
}
拓扑排序
拓扑排序会对有向图的所有顶点排序,使有向边从前面的顶点指向后边的顶点
拓扑排序算法
与深度优先搜索类似,拓扑排序访问当前顶点的临接数组的所有顶点,知道这个列表穷尽,才会将当前顶点压入栈中。
算法的实现
完整的Graph
function Vertex(label, wasVisited) {
this.label = label
this.wasVisited = wasVisited
}
function Graph(v) {
this.vertices = v;
this.vertexList = []
this.edgs = 0
this.adj = []
this.marked = []
this.edgeTo = []
for(var i = 0; i< this.vertices; i++) {
this.adj[i] = []
// this.adj[i].push('')
this.marked[i] = false
}
}
Graph.prototype = {
addEdge: function(v, w) {
this.adj[w].push(v)
this.adj[v].push(w)
this.edgs ++
},
show: function() {
var arr = []
for(var i = 0; i< this.vertices; i++) {
arr.push(`${i} ->`)
for(var j = 0; j< this.vertices; j++) {
if(this.adj[i][j]!=undefined) {
arr.push(this.adj[i][j]+ ' ')
}
}
arr.push(',')
}
return arr.join('')
},
dfs: function(v) {
this.marked[v] = true
if(this.adj[v] != undefined) {
console.log(`Visited vertex: ${v}`)
this.adj[v].forEach(w => {
if(!this.marked[w]) {
this.dfs(w)
}
})
}
},
bfs: function(s) {
var queue = []
this.marked[s] = true
queue.push(s)
while(queue.length > 0) {
var v = queue.shift() // 取出队首
if(this.adj[v]) {
console.log(`Visited vertex: ${v}`)
}
this.adj[v].forEach(w => {
if(!this.marked[w]) {
this.edgeTo[w] = v
this.marked[w] = true
queue.push(w)
}
})
}
},
pathTo: function(v) {
var source = 0
if(!this.hasPathTo(v)) {
return undefined
}
var path = []
for(var i = v; i != source; i = this.edgeTo[i]) {
path.push(i)
}
path.push(source)
return path
},
hasPathTo: function(v) {
return this.marked[v]
},
topSort: function() {
var stack = []
var visited = []
for(var i = 0; i< this.vertices; i++) {
visited[i] = false
}
for(var i = 0; i< this.vertices; i++) {
if(!visited[i]) {
this.topSortHelper(i, visited, stack)
}
}
for(var i = 0; i< stack.length; i++) {
if(stack[i] != undefined && stack[i] != false) {
console.log(this.vertexList[stack[i]])
}
}
},
topSortHelper: function(v, visited, stack) {
visited[v] = true
for (const w in this.adj[v]) {
if(!visited[w]) {
this.topSortHelper(visited[w], visited, stack)
}
}
stack.push(v)
}
}
var g = new Graph(5)
g.addEdge(0,1)
g.addEdge(0,2)
g.addEdge(1,3)
g.addEdge(2,4)
g.bfs(0)
var vertex = 4
var paths = g.pathTo(vertex)
while(paths.length > 0) {
console.log(paths.pop())
}
g.vertexList = ['cs1', 'cs2', 'data Structures', 'assembly language', 'operate system']
g.show()
g.topSort()