11.图和图算法

460 阅读3分钟

图的定义

  • 图由边的集合和定点的集合组成,
  • 如果一个图的顶点是有序的,则可以称为有向图,在对图中的顶点排序后,便可以绘制一个流向
  • 如果图是无序的,则称为无序图
  • 图中的一系列顶点构成路径,路径中所有的顶点都由边连接。路径的长度用路径的第一个顶点到最后一个顶点之间的数量表示。由指向自身的顶点组成的路径称为环,环的长度为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()