遍历树的两种方式的测试及比较

节点类:

/**
 * 节点类
 */
public class TreeNode {
	/**
	 * 节点编号
	 */
	public String id;
	/**
	 * 节点内容
	 */
	public String text;
	/**
	 * 父节点编号
	 */
	public String parentId;
	/**
	 * 孩子节点列表
	 */
	//private Children children = new Children();
	private List<TreeNode> children = new ArrayList<TreeNode>();

	// 添加孩子节点
	public void addChild(TreeNode node) {
		//this.children.addChild(node);
		this.children.add(node);
	}

	public List<TreeNode> getChildren() {
		return children;
	}
}

使用递归的方式遍历树并生成json字符串

//使用递归的方式来遍历树
	private void travelByRecursion(TreeNode node, StringBuilder builder) {
		//dealnode
		//System.out.print("{" + "\"id\" : \"" + node.id + "\"" + ", \"text\" : \"" + node.text + "\"");
		builder.append("{" + "\"id\" : \"" + node.id + "\"" + ", \"text\" : \"" + node.text + "\"");
		List<TreeNode> children = node.getChildren();
		if (children != null && !children.isEmpty()) {
			//System.out.print(", \"children\" : [");
			builder.append(", \"children\" : [");
			for (Iterator<TreeNode> it = children.iterator(); it.hasNext();) {
				TreeNode n = it.next();
				travelByRecursion(n, builder);
				if (it.hasNext()) {
					//System.out.print(",");
					builder.append(‘,‘);
				}

			}
			//System.out.print("]");
			builder.append(‘]‘);
		} else {
			//System.out.print(", \"leaf\" : true");
			builder.append(", \"leaf\" : true");
		}
		//System.out.print("}");
		builder.append(‘}‘);
	}

使用循环的方式遍历树并生成json字符串

//使用循环的方式来遍历树
	private void travelByCycle(TreeNode node, StringBuilder builder) {
		//定义一个先入后出的栈存放节点信息
		Stack<TreeNode> stack = new Stack<TreeNode>();
		//定义一个先入后出的栈存放节点的闭合符号
		Stack<String> closeSymbolStack = new Stack<String>();
		//定义一个散列表存放同一层节点的最后一个节点的信息(用于判断是否输出逗号),key为node的id,value为上一节点的id
		Map<String, String> latestNode = new HashMap<String, String>();
		stack.push(node);
		TreeNode lpNode;
		//根节点肯定是同一层节点的最后一个节点,所以需要加入散列表
		latestNode.put(node.id, null);
		//int i=0;
		//closeSymbolStack.push("]}");
		//System.out.println(node.id);
		//i++;
		while (!stack.isEmpty()) {
			lpNode = stack.pop();
			//dealnode
			builder.append("{" + "\"id\" : \"" + lpNode.id + "\"" + ", \"text\" : \"" + lpNode.text + "\"");
			List<TreeNode> children = lpNode.getChildren();
			if (children != null && !children.isEmpty()) {
				builder.append(", \"children\" : [");
				boolean firstNode = true;
				TreeNode currentNode = null;
				for (Iterator<TreeNode> it = children.iterator(); it.hasNext();) {
					currentNode = it.next();
					if (firstNode) {
						//栈是先进后出的,所以在同一层级中第一个入栈的节点实际上是最后一个被读取的节点也就是最后一个节点
						//System.out.println(currentNode.id + "," + lpNode.id);
						latestNode.put(currentNode.id, lpNode.id);
						firstNode = false;
					}
					stack.push(currentNode);
				}
				//往闭合符号栈中压入该节点对应的闭合符号
				//判断该节点是否为同一层节点的最后一个节点
				//如果不是同一层节点的最后一个节点则需要多输出一个逗号
				if (latestNode.containsKey(lpNode.id)) {
					//System.out.println(lpNode.id);
					closeSymbolStack.push("]}");
					//i++;
				} else {
					closeSymbolStack.push("]},");
				}
			} else {
				builder.append(", \"leaf\" : true}");
				//判断是滞是否是同一层节点的最后一个叶子节点

				if (!latestNode.containsKey(lpNode.id)) {
					//如果不是是同一层节点的最后一个叶子节点则输出逗号
					builder.append(‘,‘);
				} else {
					//如果是是同一层节点的最后一个叶子节点则输出闭合符号
					if (!closeSymbolStack.isEmpty()) {
						builder.append(closeSymbolStack.pop());
					}
					//如果是同一层节点的最后一个叶子节点则判断上一节点是否是最后一个节点,如果是则输出闭合符号,循环处理直到上一节点不是同层次的最后一个节点或上一节点为空
					String tempId = lpNode.id;
					String parendNodeId = latestNode.get(tempId);
					while (parendNodeId != null && latestNode.containsKey(parendNodeId)) {
						//System.out.println(tempId + "," + parendNodeId);
						if (!closeSymbolStack.isEmpty()) {
							builder.append(closeSymbolStack.pop());
						}
						tempId = parendNodeId;
						parendNodeId = latestNode.get(tempId);
					}
				}
			}

		}
		if (!closeSymbolStack.isEmpty()) {
			for (Iterator<String> it = closeSymbolStack.iterator(); it.hasNext();) {
				builder.append(it.next());
			}
		}
		//System.out.println("i is " + i);
	}
  1. 实现难度的比较

从以上代码可以看出,递归实现的代码非常简洁,天然就是为生成json或xml这种格式的字符串设计的,而通过循环的方式遍历的代码显得比较烦琐,特别是为了实现生成json字符串的功能,使用了两个辅助变量closeSymbolStack和latestNode后显得更加烦琐,说实话为了使用循环的方式来生成json我花了大概一天的时间,而递归只有了几分钟而已。

2. 性能比较

我分别对深度只有4的树形结构进行了几次循环测试,测试结果如下

循环次数 递归实现耗时(毫秒) 循环实时耗时(毫秒)
5000 40 70
10000 70 101
100000 306 394
1000000 1287 1638
10000000 10963 13621

然后又分别不同的树的深度进行了测试,测试结果如下

树的深度 递归实现耗时(毫秒) 循环实时耗时(毫秒)
1000 2 5
2000 13 14
3000 7 14
4000 12 19
5000 java.lang.StackOverflowError 24
10000 java.lang.StackOverflowError 36
100000 java.lang.StackOverflowError 475

可以看出,如果树的深度不深的话,使用递归的性能会比使用循环的好一些,但如果树的深度比较深,树的深度超过5000后,使用递归的方式由于调用栈太深导致jvm抛出异常,而使用循环的方式依然正常。

时间: 2024-11-06 09:51:40

遍历树的两种方式的测试及比较的相关文章

JavaScript 算法应用: 遍历DOM树的两种方式

1 常见的DOM树结构: 2  DOM数遍历有两种方式: 3 广度优先代码: 4 深度优先遍历代码 原文地址:https://www.cnblogs.com/autoXingJY/p/9193600.html

遍历hashMap的两种方式

第一种: Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry) iter.next(); Object key = entry.getKey(); Object val = entry.getValue(); } 效率高,以后一定要使用此种方式!第二种: Map map = new HashMap(); I

遍历序列的两种方式

number = [0,1,2,3,5,7,8] for i in number: #第一种 print(i) for i in range(len(number)): #第二种 print(number[i]) str1 = 'abcdefg' for j in str1: print(j) for j in range(len(str1)): print(str1[j]) 输出: 01235780123578abcdefgabcdefg 原文地址:https://www.cnblogs.co

JavaScript 深度遍历对象的两种方式,递归与非递归

递归遍历: 基本问题: 当前属性值不为对象时,打印键和值 递归过程:当前属性值为对象时,打印键,继续递归 var o = { a: { b: { c: { d: { e: { f: 1, g:{ h:2 } } } } } } }; function printObjRec(obj) { for (var prop in obj) { if (typeof (obj[prop]) === "object") { console.log(prop); getProp(obj[prop])

awk中遍历数组的2种方式

awk中数组比较随意,同一个数组中的元素不一定要相同类型,而且数组下表可以是数字也可以是字符. 遍历数组有两种方式: 1. 类似于C++的方式 #-----------------------------/chapter11/ex11-30.sh------------------ #! /bin/awk -f BEGIN { #定义数组 stu[1]="200200110" stu[2]="200200164" stu[3]="200200167&quo

遍历获得磁盘文件的两种方式

在winform中可能有这种情况,遍历某一个文件夹得到当前文件夹中的所有文件以及子文件夹中的所有文件,以此类推,然后添加到一个TreeView控件中,或者通过控制台输出文件以及文件夹的名称.方法多种多样,下面说的是通过递归和队列的方式来进行.递归其实就是在函数调用的时候进行压栈进行的,所以可以概述为通过栈和队列来实现. 递归方式实现 private void GetAllFile (string strPath,TreeNode parentNode) { //得到当前路径下的所有文件和文件夹

HashMap遍历的两种方式,推荐使用entrySet()

转自:HashMap遍历的两种方式,推荐使用entrySet() 第一种: Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) {     Map.Entry entry = (Map.Entry) iter.next();     Object key = entry.getKey();     Object val = entry.getValue(); } 效率

C++ 数组遍历的两种方式

C++ 数组遍历的两种方式: #include <iostream> using namespace std; int main() { // 一维数组 int fibonacci[5] = {1, 1, 2, 3, 5}; // 使用索引遍历 // 求数组长度:sizeof(array)/sizeof(array[0]) cout << "Traverse By Index: "; for (int i = 0; i < sizeof(fibonacci

[Contract] 测试 Solidity 合约代码的两种方式 与 优缺点

第一种,使用 Truffle 这类继承了测试工具的框架,只要编写 js 脚本就可以测试 web3 与合约的逻辑. 优点是完全可控,粒度够细,便于集成测试:缺点是需要花费一些时间编写测试脚本,不过值得. 第二种,使用 Remix 提供的图形化方法调用,可以快速验证方法返回值的正确性,方便了调试. 只需要 Remix 载入文件,部署合约,调试.优点是图形化.上手快:缺点是无法做到测试情形全部覆盖.粒度粗. 小结:以上两种方式使用上可以做一个结合,开发阶段持续编写测试用例,验证调试阶段可以适当利用图形