数据结构-基于邻接矩阵实现图的遍历可视化及使用Floyd、Dijkstra算法求解最短路径(JavaScript实现)

使用 JavaScript 基于邻接矩阵实现了图的深度、广度遍历,以及 Floyd、Dijkstra 算法求解最短路径。

另外使用 SVG 实现图的遍历可视化。

一、输入

首先,输入数据主要有两个,一个是存放节点名的数组,另一个是存放边对象的数组。

例如:

//存放图结点的数组
var graphVertex = ["v0","v1","v2","v3","v4","v5","v6"];
//存放图边的数组
var graphAdge = [
	{
		start:"v0",
		end: "v2",
		weight: 2
	},
	{
		start:"v0",
		end: "v5",
		weight: 8
	},
	{
		start:"v3",
		end: "v2",
		weight: 1
	},
	{
		start:"v3",
		end: "v5",
		weight: 3
	},
	{
		start:"v4",
		end: "v1",
		weight: 6
	},
	{
		start:"v5",
		end: "v4",
		weight: 8
	},
	{
		start:"v1",
		end: "v2",
		weight: 8
	},
	{
		start:"v1",
		end: "v6",
		weight: 8
	}
];

二、SVG画图

使用SVG画出圆形结点、节点名、边、边的权值数字以及表示有向图的箭头。

//显示图
var showGraph = function(){
	var svg = document.getElementById("svg");
	var circleStr = "",lineStr = "",textStr = "",arrowStr="";//圆、线和文本的HTML字符串
	var adge = JSON.parse(JSON.stringify(graphAdge));//层序遍历生成数组
	var vertex = JSON.parse(JSON.stringify(graphVertex));//层序遍历生成数组
	var vertexObj = {};//存放所有顶点的对象,键为顶点名,值为含有属性的对象
	var width = Number(svg.getAttribute("width"))-50;//画布宽度
	var r = width/2;//半径
	//画圆和顶点名
	for(let i=0;i<vertex.length;i++){
		let vertexName = vertex[i], len = vertexName.length;
		let angle = (i*2*Math.PI/vertex.length);
		let cx = 0, cy = 0;//当前结点的定位像素坐标
		cx = r*(1 + Math.sin(angle) )+25;
		cy = r*(1 - Math.cos(angle) )+25;
		let obj = {
			"cx":cx,
			"cy":cy
		};
		vertexObj[vertexName] = obj;
		circleStr += '<circle cx="'+cx+'" cy="'+cy+'" r="20" fill="#9F79EE"/></circle>';
		//调整文本缩进
		let textcx = len>1?(cx-10):(cx-5);
		let textcy = cy+6;
		textStr += '<text x="'+textcx+'" y="'+textcy+'" fill="black">'+vertexName+'</text>';
	}
	//画线和数字
	for(let i =0;i<adge.length;i++){
		//如果依然处于当前层,则累加占用宽度,否则将占用宽度置零,更新层数
		
		let startcx = 0, startcy = 0,endcx = 0,endcy = 0;//当前结点的定位像素坐标

		if(!vertexObj[adge[i].start]||!vertexObj[adge[i].end]){
			throw Error("边数组中有结点不在结点数组中");
			return;
		}
		startcx = vertexObj[adge[i].start].cx;
		startcy = vertexObj[adge[i].start].cy;
		endcx = vertexObj[adge[i].end].cx;
		endcy = vertexObj[adge[i].end].cy;
		lineStr += '<line x1="'+startcx+'" y1="'+startcy+'" x2="'+endcx+
			'" y2="'+endcy+'" style="stroke:#999;stroke-width:2" />';

		//计算三角形箭头的坐标和旋转角
		var getTriangle = function(obj){
			var x = endcx,y=endcy+20;
			startcx = obj.startcx;
			startcy= obj.startcy;
			endcx= obj.endcx;
			endcy= obj.endcy;
			var x = endcx, y = endcy+20;
			var points = [x-7,y+15,x+7,y+15,x,y].join(",");//一个三角形三点的坐标
			var angle = 0;
			//注意这里的坐标系和通常情况下的坐标系y轴是相反的,因此 endcy-startcy 要变为startcy-endcy
			angle =180*Math.acos((startcy-endcy)/Math.sqrt((endcx-startcx)*(endcx-startcx)+
				(startcy-endcy)*(startcy-endcy)))/Math.PI;
			if(endcx-startcx < 0){
				angle = 360 - angle;
			}
			var out = {
				"points":points,
				"angle":angle
			}
			return out;
		}
		//如果是无向图隐藏下面计算与画三角形箭头的代码即可
		var obj = {
			"startcx":startcx,
			"startcy":startcy,
			"endcx":endcx,
			"endcy":endcy
		}
		var data = getTriangle(obj);
		var angle = [data.angle,endcx,endcy].join(",");
		//画三角形箭头
		arrowStr += '<polygon points="'+data.points+'" fill:"#171717" transform="rotate('+angle+')"/>';
		
		obj = {
			"startcx":endcx,
			"startcy":endcy,
			"endcx":startcx,
			"endcy":startcy
		}
		data = getTriangle(obj);
		angle = [data.angle,endcx,endcy].join(",");
		//画三角形箭头
		arrowStr += '<polygon points="'+data.points+'" fill:"#171717" transform="rotate('+angle+')"/>';
		

		//调整文本缩进
		var textcx = (startcx + endcx)/2;
		var textcy = (startcy + endcy)/2;
		
		textStr += '<text x="'+textcx+'" y="'+textcy+'" fill="#171717">'+adge[i].weight+'</text>';
	}
	svg.innerHTML = lineStr+circleStr+textStr+arrowStr;
}

三、基于邻接矩阵实现图的遍历、求最短路径

源码:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>图论</title>
	<style type="text/css">
   		.container{
   			width: 600px;
   			height: 800px;
   			margin: 0 auto;
   		}
		.node{
			width: 100px;
			min-height: 100px;
		}
		.circle{
			margin: 0 auto;
			width: 50px;
			height: 50px;
		}
   </style>
</head>
<body>
<div class="container">
	<div class="control">
	选择开始遍历的结点:
		<select id="node">
			<option>v0</option>
		</select>
	选择遍历方式:
		<select id="select">
			<option>深度优先遍历</option>
			<option>广度优先遍历</option>
		</select>
		<input type="button" name="click" value="开始遍历" id="button">
	</div>
	<svg id="svg" width="600" height="600"></svg>
</div>
<script type="text/javascript">
(function(){
//存放图结点的数组
var graphVertex = ["v0","v1","v2","v3","v4","v5","v6"];
//存放图边的数组
var graphAdge = [
	{
		start:"v0",
		end: "v2",
		weight: 2
	},
	{
		start:"v0",
		end: "v5",
		weight: 8
	},
	{
		start:"v3",
		end: "v2",
		weight: 1
	},
	{
		start:"v3",
		end: "v5",
		weight: 3
	},
	{
		start:"v4",
		end: "v1",
		weight: 6
	},
	{
		start:"v5",
		end: "v4",
		weight: 8
	},
	{
		start:"v1",
		end: "v2",
		weight: 8
	},
	{
		start:"v1",
		end: "v6",
		weight: 8
	}
];
//图的构造函数,n:结点数量 , e: 边的数量
var Graph = function(graphVertex,graphAdge){
	var vertex = [];//存放图中顶点的数组
	var arc = [];//存放图中边的邻接矩阵
	var n = graphVertex.length, e = graphAdge.length;
	//初始化邻接矩阵
	var initArr = function(){
		//深拷贝,不改变输入数组
		vertex = JSON.parse(JSON.stringify(graphVertex));
		//顶点和边的数量
		var n = graphVertex.length, e = graphAdge.length;
		//初始化arc数组
		for(let i=0;i<n;i++){
			let arrTemp = new Array(n);
			for(let j=0;j<n;j++){
				arrTemp[j] = Infinity;
			}
			arc[i] = arrTemp;
		}
		//给arc邻接矩阵填充边的权值
		for(let i=0;i<e;i++){
			var start = graphVertex.indexOf(graphAdge[i].start);
			var end = graphVertex.indexOf(graphAdge[i].end);
			if(start===-1||end===-1){
				throw Error("边数组中有结点不在结点数组中");
				return;
			}
			arc[start][end] = graphAdge[i].weight;
			arc[end][start] = graphAdge[i].weight;
		}
		//console.log(arc);
	};
	//立即执行函数,初始化
	(function(){
		initArr();
	})();

	//图的深度优先遍历
	var dfsTraverse = function(v){
		//输入检测
		if(vertex.indexOf(v)===-1){
			throw Error("广度优先遍历输入结点不在结点数组中");
			return;
		}
		var visited = [];
		var result = [];
		var stack = [];
		stack.push(v);
		result.push(v);
		visited[vertex.indexOf(v)] = true;
		while(stack.length!==0){
			let p = stack.pop();
			if(visited[vertex.indexOf(p)] !== true){
				result.push(p);
			}
			visited[vertex.indexOf(p)] = true;
			let row = vertex.indexOf(p);
			for(let i=n-1;i>=0;i--){
				if(arc[row][i]!==Infinity && visited[i] !== true){
					stack.push(vertex[i]);
				}
			}
			if(result.length>100){
				alert("死循环");
			}
		}
		//console.log(result);
		return result;
	}
	//图的广度优先遍历
	var bfsTraverse = function(v){
		//输入检测
		if(vertex.indexOf(v)===-1){
			throw Error("广度优先遍历输入结点不在结点数组中");
			return;
		}
		//广度优先遍历
		var queue = [];
		var visited = [];
		var pre = 0, tail = 1;
		queue.push(v);
		visited[vertex.indexOf(v)] = true;
		while(pre !== tail){
			let p = queue[pre++];
			let row = vertex.indexOf(p);
			if(row===-1){
				throw Error("广度优先遍历结点不在结点数组中");
			}
			for(let i=0;i<n;i++){
				if(arc[row][i]!==Infinity && visited[i] !== true){
					queue.push(vertex[i]);
					visited[i] = true;
				}
			}
			tail = queue.length;
			if(pre>100){
				alert("死循环");
			}
		}
		//console.log(queue);
		return queue;
	}
	//Floyd最短路径
	var floyd = function(){
		var dist = [], path = [];
		for(let i=0;i<n;i++){
			dist[i] = new Array();
			path[i] = new Array();
			for(let j=0;j<n;j++){
				dist[i][j] = arc[i][j];
				path[i][j] = [ vertex[i] ];
			}
		}
		for(let k=0;k<n;k++){
			for(let i=0;i<n;i++){
				for(let j=0;j<n;j++){
					if(dist[i][k] + dist[k][j] < dist[i][j]){
						dist[i][j] = dist[i][k] + dist[k][j];
						path[i][j] = path[i][k].concat(path[k][j]);
					}
					if(i===j){
						dist[i][j] = 0;
					}
				}
			}
		}
		for(let i=0;i<n;i++){
			for(let j=0;j<n;j++){
				path[i][j].push(vertex[j]);
			}
		}
		//console.log(dist,path);
		var out = {
			"dist":dist,
			"path":path
		}
		return out;
	}
	//Dijkstra
	var dijkstra = function(v){
		var dist = [], path = [];
		for(let i=0;i<n;i++){
			dist[i] = arc[vertex.indexOf(v)][i];
			if(dist[i]!==Infinity){
				path[i] = [v,vertex[i]];
			}else{
				path[i] = [];
			}
		}
		var vis = [];
		vis[vertex.indexOf(v)] = true;
		dist[vertex.indexOf(v)] = 0;
		var num = 1;
		while(num<n){
			let k = 1;
			for(let i=0;i<n;i++){
				if(vis[i]!==true && dist[k] > dist[i]){
					k = i;
				}
			}
			vis[k] = true;
			for(let i=0;i<n;i++){
				if(dist[i] > dist[k]+arc[k][i]){
					dist[i] =  dist[k]+arc[k][i];
					path[i] = path[k].concat([vertex[i]]);
				}
			}
			num++;
		}
		//console.log(dist,path);
		var out = {
			"dist":dist,
			"path":path
		}
		return out;
	}
	return {
		dfsTraverse: dfsTraverse,
		bfsTraverse: bfsTraverse,
		floyd: floyd,
		dijkstra: dijkstra
	}
}

//显示图
var showGraph = function(){
	var svg = document.getElementById("svg");
	var circleStr = "",lineStr = "",textStr = "",arrowStr="";//圆、线和文本的HTML字符串
	var adge = JSON.parse(JSON.stringify(graphAdge));//层序遍历生成数组
	var vertex = JSON.parse(JSON.stringify(graphVertex));//层序遍历生成数组
	var vertexObj = {};//存放所有顶点的对象,键为顶点名,值为含有属性的对象
	var width = Number(svg.getAttribute("width"))-50;//画布宽度
	var r = width/2;//半径
	//画圆和顶点名
	for(let i=0;i<vertex.length;i++){
		let vertexName = vertex[i], len = vertexName.length;
		let angle = (i*2*Math.PI/vertex.length);
		let cx = 0, cy = 0;//当前结点的定位像素坐标
		cx = r*(1 + Math.sin(angle) )+25;
		cy = r*(1 - Math.cos(angle) )+25;
		let obj = {
			"cx":cx,
			"cy":cy
		};
		vertexObj[vertexName] = obj;
		circleStr += '<circle cx="'+cx+'" cy="'+cy+'" r="20" fill="#9F79EE"/></circle>';
		//调整文本缩进
		let textcx = len>1?(cx-10):(cx-5);
		let textcy = cy+6;
		textStr += '<text x="'+textcx+'" y="'+textcy+'" fill="black">'+vertexName+'</text>';
	}
	//画线和数字
	for(let i =0;i<adge.length;i++){
		//如果依然处于当前层,则累加占用宽度,否则将占用宽度置零,更新层数
		
		let startcx = 0, startcy = 0,endcx = 0,endcy = 0;//当前结点的定位像素坐标

		if(!vertexObj[adge[i].start]||!vertexObj[adge[i].end]){
			throw Error("边数组中有结点不在结点数组中");
			return;
		}
		startcx = vertexObj[adge[i].start].cx;
		startcy = vertexObj[adge[i].start].cy;
		endcx = vertexObj[adge[i].end].cx;
		endcy = vertexObj[adge[i].end].cy;
		lineStr += '<line x1="'+startcx+'" y1="'+startcy+'" x2="'+endcx+
			'" y2="'+endcy+'" style="stroke:#999;stroke-width:2" />';

		//计算三角形箭头的坐标和旋转角
		var getTriangle = function(obj){
			var x = endcx,y=endcy+20;
			startcx = obj.startcx;
			startcy= obj.startcy;
			endcx= obj.endcx;
			endcy= obj.endcy;
			var x = endcx, y = endcy+20;
			var points = [x-7,y+15,x+7,y+15,x,y].join(",");//一个三角形三点的坐标
			var angle = 0;
			//注意这里的坐标系和通常情况下的坐标系y轴是相反的,因此 endcy-startcy 要变为startcy-endcy
			angle =180*Math.acos((startcy-endcy)/Math.sqrt((endcx-startcx)*(endcx-startcx)+
				(startcy-endcy)*(startcy-endcy)))/Math.PI;
			if(endcx-startcx < 0){
				angle = 360 - angle;
			}
			var out = {
				"points":points,
				"angle":angle
			}
			return out;
		}
		//如果是无向图隐藏下面计算与画三角形箭头的代码即可
		var obj = {
			"startcx":startcx,
			"startcy":startcy,
			"endcx":endcx,
			"endcy":endcy
		}
		var data = getTriangle(obj);
		var angle = [data.angle,endcx,endcy].join(",");
		//画三角形箭头
		arrowStr += '<polygon points="'+data.points+'" fill:"#171717" transform="rotate('+angle+')"/>';
		
		obj = {
			"startcx":endcx,
			"startcy":endcy,
			"endcx":startcx,
			"endcy":startcy
		}
		data = getTriangle(obj);
		angle = [data.angle,endcx,endcy].join(",");
		//画三角形箭头
		arrowStr += '<polygon points="'+data.points+'" fill:"#171717" transform="rotate('+angle+')"/>';
		

		//调整文本缩进
		var textcx = (startcx + endcx)/2;
		var textcy = (startcy + endcy)/2;
		
		textStr += '<text x="'+textcx+'" y="'+textcy+'" fill="#171717">'+adge[i].weight+'</text>';
	}
	svg.innerHTML = lineStr+circleStr+textStr+arrowStr;
}
showGraph();

var graph = new Graph(graphVertex,graphAdge);
console.log("基于邻接矩阵实现:");
console.log("深度优先遍历",graph.dfsTraverse("v0"));
console.log("广度优先遍历",graph.bfsTraverse("v0"));
console.log("Floyd算法求最短路径",graph.floyd());
console.log("Dijkstra算法求最短路径",graph.dijkstra("v0"));

var showGraphTraverse = function(id){
	var svg = document.getElementById("svg");
	svg.innerHTML = "";
	var circleStr = "",lineStr = "",textStr = "",arrowStr="";//圆、线和文本的HTML字符串
	var adge = JSON.parse(JSON.stringify(graphAdge));//层序遍历生成数组
	var vertex = JSON.parse(JSON.stringify(graphVertex));//层序遍历生成数组
	var vertexObj = {};//存放所有顶点的对象,键为顶点名,值为含有属性的对象
	var width = Number(svg.getAttribute("width"))-50;//画布宽度
	var r = width/2;//半径
	//画圆和顶点名
	for(let i=0;i<vertex.length;i++){
		let vertexName = vertex[i], len = vertexName.length;
		let angle = (i*2*Math.PI/vertex.length);
		let cx = 0, cy = 0;//当前结点的定位像素坐标
		cx = r*(1 + Math.sin(angle) )+25;
		cy = r*(1 - Math.cos(angle) )+25;
		let obj = {
			"cx":cx,
			"cy":cy
		};
		vertexObj[vertexName] = obj;
		var color = "#9F79EE";
		if(id===vertex[i]){
			color = "green";
		}


		circleStr += '<circle cx="'+cx+'" cy="'+cy+'" r="20" fill="'+color+'"/></circle>';
		//调整文本缩进
		let textcx = len>1?(cx-10):(cx-5);
		let textcy = cy+6;
		textStr += '<text x="'+textcx+'" y="'+textcy+'" fill="black">'+vertexName+'</text>';
	}
	//画线和数字
	for(let i =0;i<adge.length;i++){
		//如果依然处于当前层,则累加占用宽度,否则将占用宽度置零,更新层数
		
		let startcx = 0, startcy = 0,endcx = 0,endcy = 0;//当前结点的定位像素坐标

		if(!vertexObj[adge[i].start]||!vertexObj[adge[i].end]){
			throw Error("边数组中有结点不在结点数组中");
			return;
		}
		startcx = vertexObj[adge[i].start].cx;
		startcy = vertexObj[adge[i].start].cy;
		endcx = vertexObj[adge[i].end].cx;
		endcy = vertexObj[adge[i].end].cy;
		lineStr += '<line x1="'+startcx+'" y1="'+startcy+'" x2="'+endcx+
			'" y2="'+endcy+'" style="stroke:#999;stroke-width:2" />';

		//计算三角形箭头的坐标和旋转角
		var getTriangle = function(obj){
			var x = endcx,y=endcy+20;
			startcx = obj.startcx;
			startcy= obj.startcy;
			endcx= obj.endcx;
			endcy= obj.endcy;
			var x = endcx, y = endcy+20;
			var points = [x-7,y+15,x+7,y+15,x,y].join(",");//一个三角形三点的坐标
			var angle = 0;
			//注意这里的坐标系和通常情况下的坐标系y轴是相反的,因此 endcy-startcy 要变为startcy-endcy
			angle =180*Math.acos((startcy-endcy)/Math.sqrt((endcx-startcx)*(endcx-startcx)+
				(startcy-endcy)*(startcy-endcy)))/Math.PI;
			if(endcx-startcx < 0){
				angle = 360 - angle;
			}
			var out = {
				"points":points,
				"angle":angle
			}
			return out;
		}
		//如果是无向图隐藏下面计算与画三角形箭头的代码即可
		var obj = {
			"startcx":startcx,
			"startcy":startcy,
			"endcx":endcx,
			"endcy":endcy
		}
		var data = getTriangle(obj);
		var angle = [data.angle,endcx,endcy].join(",");
		//画三角形箭头
		arrowStr += '<polygon points="'+data.points+'" fill:"#171717" transform="rotate('+angle+')"/>';
		
		obj = {
			"startcx":endcx,
			"startcy":endcy,
			"endcx":startcx,
			"endcy":startcy
		}
		data = getTriangle(obj);
		angle = [data.angle,endcx,endcy].join(",");
		//画三角形箭头
		arrowStr += '<polygon points="'+data.points+'" fill:"#171717" transform="rotate('+angle+')"/>';
		


		//调整文本缩进
		var textcx = (startcx + endcx)/2;
		var textcy = (startcy + endcy)/2;
		
		textStr += '<text x="'+textcx+'" y="'+textcy+'" fill="#171717">'+adge[i].weight+'</text>';
	}
	svg.innerHTML = lineStr+circleStr+textStr+arrowStr;
}

var select = document.getElementById("select");
var button = document.getElementById("button");
var node = document.getElementById("node");

var optionStr = '';
for(let i =0;i<graphVertex.length;i++){
	optionStr += '<option>'+graphVertex[i]+'</option>';
}
node.innerHTML = optionStr;

button.addEventListener("click",function(){
	if(button.click===false){
		return;
	}else{
		button.click = false;
	}
	var nodeIndex = node.selectedIndex ;
	var selectValue = node.options[nodeIndex].value;
	console.log(selectValue);
	var index = select.selectedIndex ;
	console.log(index);
	switch(index){
		case 0: traversalArr = graph.dfsTraverse(selectValue);
				break;
		case 1: traversalArr = graph.bfsTraverse(selectValue);
				break;
		default: alert("选择遍历方式出错");
				break;
	}
	//注意,这里故意
	for(let i =0;i<=traversalArr.length;i++){
		setTimeout(function(i){
			showGraphTraverse(traversalArr[i]);
			if(i>=traversalArr.length){
				button.click = true;
				console.log("OK");
			}
		},1000*i,i);
	}
});

})();

</script>
</body>
</html>

截图:

《数据结构-基于邻接矩阵实现图的遍历可视化及使用Floyd、Dijkstra算法求解最短路径(JavaScript实现)》

《数据结构-基于邻接矩阵实现图的遍历可视化及使用Floyd、Dijkstra算法求解最短路径(JavaScript实现)》

    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/Alan_1550587588/article/details/80448800
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞