节点类:
/**
* 节点类
*/
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);
}
实现难度的比较
从以上代码可以看出,递归实现的代码非常简洁,天然就是为生成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抛出异常,而使用循环的方式依然正常。