1.图的邻接矩阵表示法
- 邻接矩阵让每一个节点和一个证书相关联,该整数作为数组的下标值。
- 我们用一个二维数组来标识顶点之间的连接。
- 在二维数组中0表示没有连线,1表示有连线。
- 通过二维数组,我们很快的找到一个顶点和那些顶点有连线(比如A顶点,只需要遍历第一行就可)
- 另外A-A,B-B也就是顶点到自己的连线,通常使用0表示。
2.图的邻接表表示
- 邻接表由图中每个顶点以及和顶点相邻的顶点列表组成.
- 这个列表有很多中方式来存储: 数组/链表/字典(哈希表)都可以.
- 比如我们要表示和A顶点有关联的顶点(边), A和B/C/D有边, 那么我们可以通过A找到对应的数组/链表/字典, 再取出其中的内容就可以啦。
- 邻接表计算"出度"是比较简单的(出度: 指向别人的数量, 入度: 指向自己的数量)邻接表如果需要计算有向图的"入度", 那么是一件非常麻烦的事情。
3.我们封装一个字典结构
/***
* 封装一个字典结构
*/
function Dictionay(){
//字典属性
this.items = {};
//字典所有操作方法
//1.在字典中添加键值对
Dictionay.prototype.set = function(key, value){
this.items[key] = value;
}
//2.判断字典中是否存在某个key
Dictionay.prototype.has = function(key){
return this.items.hasOwnProperty(key);
}
//3.从字典中移除元素
Dictionay.prototype.remove = function(key){
//1.判断字典中是否有这个元素
if(!this.has(key)) return false;
//2.从字典中删除这个元素
delete this.items[key];
return true;
}
//4.根据key获取value
Dictionay.prototype.get = function(key){
return this.has(key) ? this.items[key]: undefined;
}
//5.获取所有的keys
Dictionay.prototype.keys = function(){
return Object.keys(this.items);
}
//6.size方法
Dictionay.prototype.size = function(){
return this.keys().length;
}
//7.clear方法
Dictionay.prototype.clear = function(){
this.items = {};
}
}
//使用字典
var dl = new Dictionay();
-
- 使用我们封装的字典
- 使用我们封装的字典
-
图结构的封装
4.图结构的封装(顶点:数组/边:字典)
/****
* 图结构的封装
*/
function Graph(){
//属性 : 顶点(数组)/边(字典)
this.vertexes = []; //顶点
this.edge = new Dictionay(); //边
}
- 添加顶点的方法
//添加顶点的方法
Graph.prototype.addVertex = function (v1) {
this.vertexes.push(v1);
//刚开始只有顶点,没有边,所以存储的是空数组
this.edges.set(v1, []);
}
//添加边的方法
//插入两个顶点
Graph.prototype.addEdge = function (v1, v2) {
this.edges.get(v1).push(v2);
this.edges.get(v2).push(v1);
}
- 添加toString方法
//toString()方法,以邻接表的方法打印出来
Graph.prototype.toString = function(){
//1.定义字符串,保存最终的结果
var resultString = '';
//2.遍历所有的顶点,以及对应的边
for(var i = 0 ; i < this.vertexes.length; i ++){
resultString += this.vertexes[i] + '->';
//拼接和这个节点相连的节点,通过对应的key获取value(另外一个数组)
var vEdges = this.edges.get(this.vertexes[i]);
for(var j = 0; j < vEdges.length; j ++){
resultString += vEdges[j] + ' ';
}
resultString += '\n';
}
//3.返回string
return resultString;
}
- 测试上述方法
//测试添加方法
//1.创建图结构
var graph = new Graph();
//2.添加顶点
var myVertexes = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
for (var i = 0; i < myVertexes.length; i++) {
graph.addVertex(myVertexes[i]);
}
//3.添加边
// 添加边
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');
//4.测试结果
alert(graph.toString());
结果如下:
5.图的遍历(重点)
图的遍历思想
图的遍历思想和树的遍历思想是一样的。
图的遍历意味着需要将途中每个顶点访问以边,并且不能有重复。
图的两种遍历方式
广度优先搜索(BFS)
深度优先搜索(DFS)
两种遍历方式都需要第一个被访问的节点
5.1 BFS和DFS的思想
BFS:基于队列,入队列的顶点先被探索
DFS: 基于栈或者使用递归,通过将顶点存入栈中,顶点是沿着路径被探索的,存在新的相邻顶点就去访问。
为了记录顶点是否被访问过,我们使用三种颜色来反映他们的状态。
- 白色:表示该顶点还没有被访问
- 灰色:表示该顶点被访问过,但未被探索过
- 黑色:表示该顶点被访问过且被完全探索过
初始化状态颜色代码:
//初始化状态颜色(使用颜色来记录状态,并且全部置为白色)
Graph.prototype.initColor = function(){
var colors = [];
for(var i = 0; i < this.vertexes.length; i ++){
colors[this.vertexes[i]] = 'white';
}
return colors;
}
5.2 BFS广度优先搜索
广度优先搜索的实现
创建一个队列Q
将v标注为被发现的(灰色),并将v放入队列Q
如果Q非空,执行下面的步骤
将v从q中取出队列
将v标注为被发现的灰色
将v所有的未被访问的零界点(白色),加入队列中
将v标注为黑色
- 广度优先搜索的代码实现
//封装队列类
function Queue() {
//属性
this.items = [];
//方法
//1.将元素加入到队列中
Queue.prototype.enqueue = function (element) {
this.items.push(element);
}
//2.从队列中删除前端元素
Queue.prototype.dequeue = function () {
return this.items.shift()
}
//3.查看前端的元素
Queue.prototype.front = function () {
return this.items[0];
}
//4.查看队列是否为空
Queue.prototype.isEmpty = function () {
return this.items.length == 0;
}
//5.查看队列中元素的个数
Queue.prototype.size = function () {
return this.items.length;
}
//6.toString方法
Queue.prototype.toString = function () {
let resultString = '';
for (let i = 0; i < this.items.length; i++) {
resultString += this.items[i] + ',';
}
return resultString;
}
}
//实现广度优先搜索(BFS)
Graph.prototype.bfs = function(initV,handler){
// 1.初始化颜色
var colors = this.initColor();
//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 将v的颜色设置为灰色
colors[v] = 'gray';
//4.4 遍历所有的领点,并且加入到队列中
for(var i = 0 ; i < vList.length; i++){
var e = vList[i];
if(colors[e] == 'white'){
//表示从来没有探测过
colors[e] = 'gray';
queue.enqueue(e);
}
}
//4.5 v已经被探测,访问v顶点
handler(v);
//4.6 将顶点设置为黑色
colors[v] = 'black';
}
}
//5.测试bfs
var result = '';
graph.bfs(graph.vertexes[0],function(v){
result += v + ' ';
})
alert(result);
5.3 DFS深度优先搜索
深度优先搜索的实现
深度优先搜索算法会从第一个指定的顶点开始遍历图,沿着路径直到这条路径最后被访问了
接着按照园路回退并且探索吓一条路径
广度优先搜索可以使用队列,也可以使用栈,这里我们使用递归完成。
//实现深度优先搜索(DFS)
Graph.prototype.dfs = function(initV,handler){
//1.初始化颜色
var colors = this.initColor();
//2.从某个顶点开始依次递归访问
this.dfsVisit(initV,colors,handler);
}
//实现递归访问
Graph.prototype.dfsVisit = function(v,colors,handler){
//1.先将颜色设置为灰色
colors[v] = 'gray';
//2.处理v顶点
handler(v);
//3.访问v相连的其他顶点
var vList = this.edges.get(v);
for(var i = 0; i < vList.length; i++){
var e = vList[i];
if(colors[e] == 'white'){
//如果是白色就递归访问
this.dfsVisit(e, colors, handler);
}
}
//4.将v设置为黑色
colors[v] = 'black';
}
//6.测试dfs
var result2 = '';
graph.dfs(graph.vertexes[0],function(v){
result2 += v + ' ';
return result2;
})
alert(result2);