数据结构-树的遍历可视化(JavaScript实现)

使用JavaScript实现树的深度优先遍历(前序和后序)、广度优先遍历(层序),分别使用递归和非递归的方式实现。

注:

a. 非递归实现效率高,代码可读性较差;

b. 递归代码写出来思路清晰,可读性强,但效率较低,且递归的次数受堆栈大小的限制,容易发生堆栈溢出。 


以下代码使用SVG 实现树的遍历可视化:

<!DOCTYPE html>
<html>
<head lang="en">
    <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="select">
			<option>深度优先-前序遍历</option>
			<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" src="singlyLinkedList.js"></script> -->
<script type="text/javascript">
(function(){
var treeData = {
	data: 1,
	childrens:[
		{
			data: 2,
			childrens:[
				{
					data: 4,
					childrens:[
						{
							data: 11,
							childrens:[]
						},
						{
							data: 12,
							childrens:[]
						}
					]
				},
				{
					data: 5,
					childrens:[]
				},
				{
					data: 6,
					childrens:[]
				}
			]
		},
		{
			data: 3,
			childrens:[
				{
					data: 7,
					childrens:[
						{
							data: "A",
							childrens:[]
						},
						{
							data: "B",
							childrens:[]
						}
					]
				},
				{
					data: 8,
					childrens:[]
				},
				{
					data: 9,
					childrens:[
						{
							data: 13,
							childrens:[
								{
									data: 14,
									childrens:[]
								},
								{
									data: 15,
									childrens:[]
								}
							]
						}
					]
				},
				{
					data: 10,
					childrens:[]
				}
			]
		}
	]
}
//二叉树
var BinaryTree = function(){
	//前序遍历递归实现
	var PreOrderTraversalRecursion = function(node){
		console.log(node.data);
		for(let i = 0;i<node.childrens.length;i++){
			this.PreOrderTraversalRecursion(node.childrens[i]);
		}
		return;
	}
	//后序遍历递归实现
	var PostOrderTraversalRecursion = function(node){
		for(let i = 0;i<node.childrens.length;i++){
			this.PostOrderTraversalRecursion(node.childrens[i]);
		}
		console.log(node.data);
		return;
	}
	//前序遍历非递归实现
	var PreOrderTraversal = function(rootNode){
		var stack = [];//存放结果的栈
		var temp = [];//暂存结点的栈
		temp.push(rootNode);
		while(temp.length>0){
			var node = temp.shift(); 
			stack.push(node);
			var childrens = node.childrens;
			if(childrens && childrens.length>0){
				for(let i =childrens.length-1;i >= 0;i--){
					temp.unshift(childrens[i]);
				}
			}		
		}
		var result = [];
		while(stack.length>0){
			result.push( stack.shift().data );
		}
		return result;
	}
	//后序遍历非递归实现
	var PostOrderTraversal = function(rootNode){
		var stack = [];//存放结果的栈
		var temp = [];//暂存结点的栈
		temp.push(rootNode);
		//暂存数组不为空,即未遍历所有结点时一直循环
		while(temp.length>0){
			var node = temp.pop(); 
			stack.push(node);
			var childrens = node.childrens;
			//如果栈顶结点存在子节点,则全部入栈
			if(childrens && childrens.length>0){
				for(let i = 0;i<childrens.length;i++){
					temp.push(childrens[i]);
				}
			}		
		}
		var result = [];//存放遍历结果的数组
		while(stack.length>0){
			result.push( stack.pop().data );
		}
		return result;
	}
	/*//前序遍历非递归实现1
	var PreOrderTraversal1 = function(rootNode){
		var stack = [];//暂存结点的栈
		var result = [],count = 0;//存放遍历结果的数组
		var p = rootNode;
		var  = null;
		while(p!==null && stack.length>0){
			while(ppre!==null){
				result[count++]=p.data;
				stack.push(p);
				if(p.childrens.length>0){
					p = p.childrens[0];
				}else{
					p = null;
				}
			}
			if(stack.length>0){
				p = stack[stack.length-1];
				var len = p.childrens.length;
				if(len===0 && pre===p.childrens[len-1] ){
					stack.pop();
					p = stack[stack.length-1].childrens[]
				}else{
					//当前结点有子节点,压栈
					for(let i = p.childrens.length-1;i>=1;i--){
						stack.push(p.childrens[i]);
					}
				}
				pre = p;
				p = p.childrens[1];
			}
		}
		stack.push(rootNode);
		
		return result;
	}*/
	//后序遍历非递归实现1
	var PostOrderTraversal1 = function(rootNode){
		var stack = [];//暂存结点的栈
		var result = [],count = 0;//存放遍历结果的数组
		var pre = null, cur = null;//前一个遍历的结点,当前遍历的结点
		stack.push(rootNode);
		while(stack.length>0){
			cur = stack[stack.length-1];//取当前结点
			var len = cur.childrens.length;
			if(len===0||pre === cur.childrens[len-1]){
				//当前结点没有子节点或子节点已经遍历完毕
				var pre = stack.pop();
				result[count++] = pre.data;
			}else{
				//当前结点有子节点,压栈
				for(let i = cur.childrens.length-1;i>=0;i--){
					stack.push(cur.childrens[i]);
				}
			}
		}
		return result;
	}
		//层序遍历
	var LeverOrderTraversal1 = function(tree){
		var rootNode = JSON.parse( JSON.stringify(tree) );
		var result = [],floor=0;
		var queue = [],index = 0;//队列、遍历索引和队列中结点的数量
		rootNode.floor = 1;
		rootNode.widthRatio = 1;//占画布宽度的比例
		queue.push(rootNode);
		rootNode.father = rootNode;
		rootNode.which = 0;
		while(index!=queue.length){
			var node = queue[index++];
			var len = node.childrens.length;
			if(len>0){
				for(let i=0;i<len;i++){
					node.childrens[i].floor = node.floor+1;
					node.childrens[i].which = i;
					node.childrens[i].widthRatio = node.widthRatio/len;
					node.childrens[i].father = node;
					queue.push(node.childrens[i]);
				}
			}
			if(queue.length>100) break;
		}
		for(let i=0;i<queue.length;i++){
			result[i] = queue[i];	
		}
			
		return result;
	}
	return {
		PreOrderTraversalRecursion: PreOrderTraversalRecursion,
		PostOrderTraversalRecursion: PostOrderTraversalRecursion,
		PreOrderTraversal: PreOrderTraversal,
		PostOrderTraversal: PostOrderTraversal,
		PostOrderTraversal1: PostOrderTraversal1,
		LeverOrderTraversal1: LeverOrderTraversal1
	}
}

/*var tree = new BinaryTree();
tree.PreOrderTraversalRecursion(treeData);
console.log("\n");
tree.PostOrderTraversalRecursion(treeData);
console.log("前序优先前序遍历:",tree.PreOrderTraversal(treeData));
console.log("深度优先后序遍历:",tree.PostOrderTraversal(treeData));
console.log("深度优先后序遍历1:",tree.PostOrderTraversal1(treeData));
console.log("层序遍历/广度优先遍历:",tree.LeverOrderTraversal1(treeData));*/

var showTree = function(){
	var svg = document.getElementById("svg");
	var circleStr = "",lineStr = "",textStr = "";//圆、线和文本的HTML字符串
	var tree = new BinaryTree();
	var nodes = tree.LeverOrderTraversal1(treeData);//层序遍历生成数组
	console.log(nodes);
	//计算画树需要的画布尺寸
	var size = (function(nodes){
		var numArr = [];
		for(let i =0;i<nodes.length;i++){
			if(numArr[nodes[i].floor-1]===undefined){
				numArr[nodes[i].floor-1]=0;
			}
			numArr[nodes[i].floor-1]++; 
		}
		//console.log(numArr);
		var max = 0;
		for(let i =0;i<numArr.length;i++){
			if(numArr[i] > max){
				max = numArr[i];
			}
		}
		//console.log(max);
		var size = {
			width: max*100,
			height: numArr.length*80+80
		}
		return size;
	})(nodes);
	//设置SVG画布尺寸
	svg.setAttribute("width",size.width);
	svg.setAttribute("height",size.height);
	svg.parentNode.style.width = size.width+"px";
	var width = size.width;

	var occupyWidth = 0,preFloor = 0;//当前层之前结点占用的宽度,之前的层数
	//遍历所有树的结点,生成圆、线和文本的HTML字符串
	for(let i =0;i<nodes.length;i++){
		//如果依然处于当前层,则累加占用宽度,否则将占用宽度置零,更新层数
		if(preFloor === nodes[i].floor){
			occupyWidth += nodes[i-1].width*width;
		}else{
			occupyWidth = 0;
			preFloor = nodes[i].floor;
		}

		let cx = 0, cy = 0;//当前结点的定位像素坐标
		cx = occupyWidth + width*nodes[i].width/2;
		cy = nodes[i].floor * 80;
		nodes[i].cx = cx;
		nodes[i].cy = cy;
		lineStr += '<line x1="'+nodes[i].cx+'" y1="'+nodes[i].cy+'" x2="'+nodes[i].father.cx+
			'" y2="'+nodes[i].father.cy+'" style="stroke:black;stroke-width:2" />';
		circleStr += '<circle cx="'+cx+'" cy="'+cy+'" r="20" fill="#9F79EE"/></circle>';
		//调整文本缩进
		var textcx = nodes[i].data>9?(nodes[i].cx-10):(nodes[i].cx-5),
			textcy = (nodes[i].cy+6);
		textStr += '<text x="'+textcx+'" y="'+textcy+'" fill="white">'+nodes[i].data+'</text>';
	}
	svg.innerHTML = lineStr+circleStr+textStr;
}
//showTree();


var showTreeTraverse = function(id){
	var svg = document.getElementById("svg");
	svg.innerHTML = "";
	var circleStr = "",lineStr = "",textStr = "";//圆、线和文本的HTML字符串
	var tree = new BinaryTree();
	var nodes = tree.LeverOrderTraversal1(treeData);//层序遍历生成数组
	//console.log(nodes);
	console.log(id);
	//计算画树需要的画布尺寸
	var size = (function(nodes){
		var numArr = [];
		for(let i =0;i<nodes.length;i++){
			if(numArr[nodes[i].floor-1]===undefined){
				numArr[nodes[i].floor-1]=0;
			}
			numArr[nodes[i].floor-1]++; 
		}
		//console.log(numArr);
		var max = 0;
		for(let i =0;i<numArr.length;i++){
			if(numArr[i] > max){
				max = numArr[i];
			}
		}
		//console.log(max);
		var size = {
			width: max*100,
			height: numArr.length*80+80
		}
		return size;
	})(nodes);
	//设置SVG画布尺寸
	svg.setAttribute("width",size.width);
	svg.setAttribute("height",size.height);
	svg.parentNode.style.width = size.width+"px";
	var width = size.width;

	//遍历所有树的结点,生成圆、线和文本的HTML字符串
	for(let i =0;i<nodes.length;i++){
		let nodeColor = nodes[i].data === id?"#080808":"#9F79EE";

		let cx = 0, cy = 0;//当前结点的定位像素坐标
		let father = nodes[i].father;
		if(i === 0){
			father.cx = width/2;
		}
		//父节点宽度的最左边
		let start = father.cx - width*father.widthRatio/2;
		//当前结点的横坐标
		cx = start + width*nodes[i].widthRatio*(nodes[i].which + 0.5);
		console.log(start,cx);
		cy = nodes[i].floor * 80;
		nodes[i].cx = cx;
		nodes[i].cy = cy;
		lineStr += '<line x1="'+cx+'" y1="'+cy+'" x2="'+nodes[i].father.cx+
			'" y2="'+nodes[i].father.cy+'" style="stroke:black;stroke-width:2" />';
		circleStr += '<circle cx="'+cx+'" cy="'+cy+'" r="20" fill="'+nodeColor+'"/></circle>';
		//调整文本缩进
		var textcx = nodes[i].data>9?(nodes[i].cx-10):(nodes[i].cx-5),
			textcy = (nodes[i].cy+6);
		textStr += '<text x="'+textcx+'" y="'+textcy+'" fill="white">'+nodes[i].data+'</text>';
	}
	svg.innerHTML = lineStr+circleStr+textStr;
}


var tree = new BinaryTree();
var nodes = tree.LeverOrderTraversal1(treeData);//层序遍历生成数组
console.log(nodes);
var traversalArr = tree.PostOrderTraversal(treeData);
showTreeTraverse(null,nodes);
//var traversalArr = tree.PreOrderTraversal(treeData);
var select = document.getElementById("select");
var button = document.getElementById("button");

button.addEventListener("click",function(){
	if(button.click===false){
		return;
	}else{
		button.click = false;
	}
	var nodes = tree.LeverOrderTraversal1(treeData);//层序遍历生成数组
	var traversalArr = tree.PostOrderTraversal(treeData);
	var index = select.selectedIndex ;
	console.log(index);
	switch(index){
		case 0: traversalArr = tree.PreOrderTraversal(treeData)
				break;
		case 1: traversalArr = tree.PostOrderTraversal(treeData)
				break;
		case 2: traversalArr = (function(){
						var arr = tree.LeverOrderTraversal1(treeData);
						for(let i =0;i<arr.length;i++){
							arr[i] = arr[i].data;
						}
						return arr;
					})();
				break;
		
	}
	//注意,这里故意
	for(let i =0;i<=traversalArr.length;i++){
		setTimeout(function(i){
			showTreeTraverse(traversalArr[i],nodes);
			if(i>=traversalArr.length){
				button.click = true;
				console.log("OK");
			}
		},400*i,i);
	}
});

})();

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


截图:



猜你喜欢

转载自blog.csdn.net/alan_1550587588/article/details/80384945