图的术语:
度:一个顶点的度是相邻顶点的数量
简单路径:简单路径要求不包含重复的顶点
回路:第一个顶点和最后一个顶点相同的路径称为回路
带权图:带权图表示边有一定的权重
无权图:边没有携带权重
邻接矩阵:
邻接矩阵让每个节点和一个整数相关联,整数作为数组的下标值。可以用二维数组来表示顶点之间的连接
邻接矩阵有一个比较严重的问题,如果图是稀疏图,那么矩阵中将存在大量的0,这就意味着我们浪费了计算机存储空间来表示不存在的边。
邻接表:
邻接表由每个顶点和顶点相邻的顶点列表组成。
邻接表计算出度
比较简单,计算入度
比较麻烦(出度:指向别人的数量。入度:指向自己的数量)
图的遍历
图的遍历意味着需要将图中每个顶点访问一遍,并且不能有重复的访问。
广度优先搜索(Breadth-First Search, 简称BFS)
深度优先搜索(Depth-First Search, 简称DFS)
两种遍历算法,都需要明确指定第一个被访问的顶点
用white表示节点没有被访问过
用gray表示节点被访问过,但与之相邻的节点没有被访问完
用black表示节点被访问过且与之相邻的节点都被访问完了
<body>
<script src="./dict.js"></script>
<script src="./queue.js"></script>
<script>
function Graph() {
// 属性
this.vertexes = [] // 存储顶点
this.adjList = new Dictionay() // 存储边
// 添加方法
Graph.prototype.addVertex = function (v) {
this.vertexes.push(v)
this.adjList.set(v, [])
}
Graph.prototype.addEdge = function (v1, v2) {
this.adjList.get(v1).push(v2)
this.adjList.get(v2).push(v1)
}
Graph.prototype.toString = function () {
var resultStr = ""
for (var i = 0; i < this.vertexes.length; i++) {
resultStr += this.vertexes[i] + "->"
var adj = this.adjList.get(this.vertexes[i])
for (var j = 0; j < adj.length; j++) {
resultStr += adj[j] + " "
}
resultStr += "\n"
}
return resultStr
}
// 初始化颜色
Graph.prototype.initializeColor = function () {
var colors = []
for (var i = 0; i < this.vertexes.length; i++) {
colors[this.vertexes[i]] = "white"
}
return colors
}
// 广度优先算法
Graph.prototype.bfs = function (v, handler) {
// 1.初始化颜色
var color = this.initializeColor()
// 2.创建队列
var queue = new Queue()
// 3.将传入的顶点放入队列中
queue.enqueue(v)
// 4.从队列中依次取出和放入数据
while (!queue.isEmpty()) {
// 4.1.从队列中取出数据
var qv = queue.dequeue()
// 4.2.获取qv相邻的所有顶点
var qAdj = this.adjList.get(qv)
// 4.3.将qv的颜色设置成灰色
color[qv] = "gray"
// 4.4.将qAdj的所有顶点依次压入队列中
for (var i = 0; i < qAdj.length; i++) {
var a = qAdj[i]
if (color[a] === "white") {
color[a] = "gray"
queue.enqueue(a)
}
}
// 4.5.因为qv已经探测完毕, 将qv设置成黑色
color[qv] = "black"
// 4.6.处理qv
if (handler) {
handler(qv)
}
}
}
// 深度优先搜索
Graph.prototype.dfs = function (handler) {
// 1.初始化颜色
var color = this.initializeColor()
// 2.遍历所有的顶点, 开始访问
for (var i = 0; i < this.vertexes.length; i++) {
if (color[this.vertexes[i]] === "white") {
this.dfsVisit(this.vertexes[i], color, handler)
}
}
}
// dfs的递归调用方法
Graph.prototype.dfsVisit = function (v, color, handler) {
// 1.将v的颜色设置为灰色
color[v] = "gray"
// 2.处理v顶点
if (handler) {
handler(v)
}
// 3.v的所有邻接顶点的访问
var vAdj = this.adjList.get(v)
for (var i = 0; i < vAdj.length; i++) {
var w = vAdj[i]
if (color[w] === "white") {
this.dfsVisit(w, color, handler)
}
}
// 4.将v设置为黑色
color[v] = "black"
}
}
// 测试代码
var graph = new Graph()
// 添加顶点
var myVertexes = ["A", "B", "C", "D", "E", "F", "G", "H", "I"]
for (var i = 0; i < myVertexes.length; i++) {
graph.addVertex(myVertexes[i])
}
// 添加边
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('A', 'D');
graph.addEdge('C', 'D');
graph.addEdge('C', 'G');
graph.addEdge('D', 'G');
graph.addEdge('D', 'H');
graph.addEdge('B', 'E');
graph.addEdge('B', 'F');
graph.addEdge('E', 'I');
// 打印结果
alert(graph)
// 调用广度优先算法
var result = ""
graph.bfs(graph.vertexes[0], function (v) {
result += v + " "
})
alert(result) // A B C D E F G H I
// 调用深度优先算法
result = ""
graph.dfs(function (v) {
result += v + " "
})
alert(result) // A B E I F C D G H
</script>
dict.js
// 创建字典的构造函数
function Dictionay() {
// 字典属性
this.items = {}
// 字典操作方法
// 在字典中添加键值对
Dictionay.prototype.set = function (key, value) {
this.items[key] = value
}
// 判断字典中是否有某个key
Dictionay.prototype.has = function (key) {
return this.items.hasOwnProperty(key)
}
// 从字典中移除元素
Dictionay.prototype.remove = function (key) {
// 1.判断字典中是否有这个key
if (!this.has(key)) return false
// 2.从字典中删除key
delete this.items[key]
return true
}
// 根据key去获取value
Dictionay.prototype.get = function (key) {
return this.has(key) ? this.items[key] : undefined
}
// 获取所有的keys
Dictionay.prototype.keys = function () {
return Object.keys(this.items)
}
// 获取所有的value
Dictionay.prototype.values = function () {
return Object.values(this.items)
}
// size方法
Dictionay.prototype.size = function () {
return this.keys().length
}
// clear方法
Dictionay.prototype.clear = function () {
this.items = {}
}
}
queue.js
// 自定义队列
function Queue() {
var items = []
// 队列操作的方法
// enter queue方法
this.enqueue = function (element) {
items.push(element)
}
// delete queue方法
this.dequeue = function () {
return items.shift()
}
// 查看前端的元素
this.front = function () {
return items[0]
}
// 查看队列是否为空
this.isEmpty = function () {
return items.length == 0
}
// 查看队列中元素的个数
this.size = function () {
return items.length
}
}