JS 数据结构,图

  • 什么是图 ?

图是网络结构的抽象模型,图是 一组由边连接的节点(或顶点)。一个图 G=(V,E)。由 V:一组顶点;E:一组边,连接中的顶点。

  • 图的相关术语 ?
  1. 相邻顶点:由一条边连接在一起的。
  2. 顶点的度:相邻顶点的数量。
  3. 路径:路径是顶点 v1,v2,...vk 的一个连续序列,其中 vi 和 vi+1 是相邻的。
  4. 简单路径:不包含重复的顶点。
  5. 有向 / 无向图:图可以是无向的(边没有方向)或是有向的(边有方向)。
  6. 连通图:如果图中每两个顶点间在双向上都存在路径,则该图是强连通的。
  7. 加权图:图还可以是加权的(每条边赋予了权值)。
  8. 如果图中不存在环,则称该图是无环的。如果图中每两个顶点间都存在路径,则该图是连通的。
  • 图的表示 ?
  1. 图最常见的实现是 邻接矩阵。每个节点都和一个整数相关联,该整数将作为数组的索引。我们用一个二维数组来表示顶点之间的连接。如果索引为 i 的节点和索引为j的节点相邻,则 array[i][j] === 1,否则 array[i][j] === 0。
  2. 邻接表:我们也可以使用一种叫作邻接表的动态数据结构来表示图。邻接表由图中每个顶点的相邻顶点列表所组成。存在好几种方式来表示这种数据结构。我们可以用列表(数组)、链表,甚至是散列表或是字典来表示相邻顶点列表。
  3. 我们还可以用 关联矩阵 来表示图。在关联矩阵中,矩阵的行表示顶点,列表示边。如下图所示,我们使用二维数组来表示两者之间的连通性,如果顶点 v 是边 e 的入射点,则 array[v][e] === 1;否则,array[v][e] === 0。

【注意】:要找出顶点 v 和 w 是否相邻,使用邻接矩阵会比较快。关联矩阵通常用于边的数量比顶点多的情况下,以节省空间和内存。

  • 图类的封装,使用邻接表实现图 ?

邻接表:我们也可以使用一种叫作邻接表的动态数据结构来表示图。邻接表由图中每个顶点的相邻顶点列表所组成。存在好几种方式来表示这种数据结构。我们可以用列表(数组)、链表,甚至是散列表或是 字典 来表示相邻顶点列表。下面是 字典类的封装

//封装字典类
function Dictionary(){
	this.items = {};
	var that = this;
	Dictionary.prototype.has = function(key){
		return key in this.items;
	}
	Dictionary.prototype.set = function(key, value){
		this.items[key] = value;
	}
	Dictionary.prototype.remove = function(key){
		if(that.has(key)){
			delete this.items[key];
			return true;
		}else{
			return false;
		}
	}
	Dictionary.prototype.get = function(key){
		return that.has(key) ? this.items[key] : undefined;
	}
     Dictionary.prototype.values = function(){
		var values = {};
		 for(var k in this.items){
			 if(that.has(k)){
					values.push(this.items[k]);
			 }
		 }
		 return values;
	 }
}

邻接表,实现图。内部使用到字典。下面是 图类的具体封装

// 创建图类
function Graph(){
	// 数组,存储图中所有顶点的名字
	this.vertices = [];
	// 字典,存储邻接表;将顶点的名字作为键,邻接顶点列表作为值。
	this.adjList = new Dictionary();
	// 向图中添加一个新的顶点
	Graph.prototype.addVertex = function(v){
		this.vertices.push(v);
		this.adjList.set(v, []);
	}
	// 用来添加顶点之间的边的方法
	Graph.prototype.addEdge = function(v, w){
		this.adjList.get(v).push(w);
		this.adjList.get(w).push(v);
	}
	Graph.prototype.toString = function(){
		var s = '';
		for(var i=0; i<this.vertices.length; i++){
			s += this.vertices[i] + '->';
			var neighbors = this.adjList.get(this.vertices[i]);
			for(var j=0; j<neighbors.length; j++){
				s += neighbors[j] + ' ';
			}
			s += '\n';
		}
		return s;
	}	
}

图类的测试 :

var graph = new Graph(); 
var myVertices = ['A','B','C','D','E','F','G','H','I']; //{7} 
for (var i=0; i<myVertices.length; i++){ //{8} 
	graph.addVertex(myVertices[i]); 
} 
graph.addEdge('A', 'B'); //{9} 
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');
console.log(graph.toString());
			
  • 图的遍历操作 ?

图的遍历 : 通常有长度优先遍历和深度优先遍历。图遍历可以用来 寻找特定的顶点 或 寻找两个顶点之间的路径,检查 图是否连通,检查 图是否含有环 等。

图遍历算法的思想 : 是必须追踪每个第一次访问的节点,并且追踪哪些节点还没有被完全探索。完全探索 一个顶点要求我们查看该顶点的每一条边。对于每一条边所连接的没有被访问过的顶点,将其标注为被发现,并将其加入待访问顶点列表中。为了保证算法的效率,务必访问每个顶点至多两次。

当要标注已经访问过的顶点时,我们用三种颜色来反映它们的状态。白色:表示该顶点还没有被访问。灰色:表示该顶点被访问过,但并未被探索过。黑色:表示该顶点被访问过且被完全探索过。这就是之前提到的务必 访问每个顶点最多两次 的原因。

  • 广度优先搜索算法 (BFS)?

通过将顶点存入队列中,最先入队列的顶点先被探索。广度优先搜索算法会从指定的第一个顶点开始遍历图,先访问其所有的相邻点,就像一次访问图的一层

从顶点 v 开始的广度优先搜索算法所遵循的步骤。(1) 创建一个队列 Q。(2) 将 v 标注为被发现的(灰色),并将 v 入队列Q。(3) 如果 Q 非空,则运行以下步骤:(a) 将 u 从 Q 中出队列;(b) 将标注 u 为被发现的(灰色);(c) 将 u 所有未被访问过的邻点(白色)入队列;(d) 将 u 标注为已被探索的(黑色)。

【注意】:可以使用 BFS 寻找最短路径。

// 实现广度优先搜索算法
Dictionary.prototype.initalizeColor = function() {
	var color = [];
	for (var i = 0; i < this.vertices.length; i++) {
		color[this.vertices[i]] = 'white';
	}
	return color;
}
Dictionary.prototype.bfs = function(v, callback) {
	var color = that.initalizeColor();
	var queue = new Queue();
	queue.enqueue(v);
	while (!queue.isEmpty()) {
		var u = queue.dequeue();
		var neighbors = adjList.get(u);
		color[u] = 'grey';
		for (var i = 0; i < neighbors.length; i++) {
			var w = neighbors[i];
			if (color[w] === 'white') {
				color[w] = 'grey';
				queue.enqueue(w);
			}
		}
		color[u] = 'black';
		if (callback) {
			callback(u);
		}
	}
}

 

  • 使用 BFS 寻找最短路径 ?
this.BFS = function(v) {
	var color = initializeColor(),
		queue = new Queue(),
		d = [],  
		pred = []; 
	queue.enqueue(v);
	for (var i = 0; i < vertices.length; i++) { 
		d[vertices[i]] = 0;  
		pred[vertices[i]] = null;
	}
	while (!queue.isEmpty()) {
		var u = queue.dequeue(),
		neighbors = adjList.get(u);
		color[u] = 'grey';
		for (i = 0; i < neighbors.length; i++) {
			var w = neighbors[i];
			if (color[w] === 'white') {
				color[w] = 'grey';
				d[w] = d[u] + 1; 
				pred[w] = u;  
				queue.enqueue(w);
			}
		}
		color[u] = 'black';
	}
	return {
		distances: d,
		predecessors: pred
	};
};
  • 深度优先算法 (DFS)? 

通过将顶点存放在栈中,顶点是沿着路径被探索的,存在新的相邻节点就去访问。深度优先搜索算法将会 从第一个指定的顶点开始遍历图,沿着路径直到这条路径最后一个顶点被访问了,接着原路回退并探索下一条路径。深度优先搜索算法不需要一个源顶点。在深度优先搜索算法中,若图中顶点 v 未访问,则访问该顶点 v。

访问顶点 v:(1)  标注 v 为被发现的(灰色)(2) 对于 v 的所有未访问的邻点 w:(a)  访问顶点 w。(3)标注 v 为已被探索的(黑色)。深度优先搜索的步骤是递归的,这意味着深度优先搜索算法使用栈来存储函数调用(由递归调用所创建的栈)。

【注意】:拓扑排序——使用深度优先搜索。

// 实现广度优先算法
Dictionary.prototype.initalizeColor = function() {
	var color = [];
	for (var i = 0; i < this.vertices.length; i++) {
		color[this.vertices[i]] = 'white';
	}
	return color;
}
Dictionary.prototype.dfs = function(callback) {
	var color = initializeColor();
	for (var i = 0; i < this.vertices.length; i++) { 
		if (color[vertices[i]] === 'white') {
			dfsVisit(this.vertices[i], color, callback); 
		}
	}
}
Dictionary.prototype.dfsVisit = function(u, color, callback) {
	color[u] = 'grey';  
	if (callback) { 
		callback(u);
	}
	var neighbors = adjList.get(u); 
		for (var i = 0; i < neighbors.length; i++) { 
		var w = neighbors[i];  
		if (color[w] === 'white') {  
			dfsVisit(w, color, callback);  
		}
	}
	color[u] = 'black'; 
};

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值