二叉树的一些重要方法及实现

二叉树的遍历

给定树节点:

public static class TreeNode {
	public int value; 
	public TreeNode left;
	public TreeNode right;
	
	public TreeNode(int data) {
		this.value = data;
	}
	
	public String toString() {
		return this.val + "";
	}
}

以及如下的树结构:

								            6            
								         /     \         
								      3           8      
								    /   \       /   \    
								  1       5   null   10 
								/   \    /  \        /  \
							null  null null  null null  null

广度优先遍历

使用队列先进先出的规则实现

public static String breadthTravel(TreeNode root) {
	if (root == null) {
		return null;
	}
	LinkedList<String> res = new LinkedList<String>();
	Queue<TreeNode> queue = new LinkedList<TreeNode>();
	queue.add(root);
	while (!queue.isEmpty()) {
		TreeNode cur = queue.poll();
		res.add(cur.val + "");
		if (cur.left != null) {
			queue.add(cur.left);
		}
		if (cur.right != null) {
			queue.add(cur.right);
		}
	}
	return String.join(" ", res);
}

得到结果:6 3 8 1 5 10

先序遍历

二叉树的先中后序遍历实际上都是一种深度优先搜索,利用递归即可实现:

public static void order(TreeNode head) {
	if (head != null) {
	    System.out.print(head.value + " ");
		order(head.left);
		order(head.right);
	}
}

当打印在head.left前,就是先序遍历;当打印在head.lefthead.right中间,就是中序遍历;而当打印在head.right之后,就是后序遍历。递归函数的实现很简单,但是很多人不清楚为什么这样就能达到效果,那是因为不了解递归的整个过程。
下面画个图来描述整个递归过程:
二叉树的遍历
从上图的顺序可以发现:每个节点都会来到3次!!
不为空的节点的访问顺序依次是:6, 3, 1, 1, 1, 3, 5, 5, 5, 3, 6, 8, 8, 10, 10, 10, 8, 6
当提取每个节点出现的第一次时就是先序遍历顺序:即 6, 3, 1, 5, 8, 10
同理, 第二次就是中序遍历,即 1, 3, 5, 6, 8, 10
第三次就是后序遍历, 即 1, 5, 3, 10, 8, 6

对应在LeetCode上的题目链接:先序遍历
如果清楚明白递归的访问顺序之后,就可以实现一个先序遍历的非递归版本:

public static String preOrder(TreeNode head) {
	if (head != null) {
		List<String> res = new LinkedList<String>();
		Stack<TreeNode> stack = new Stack<TreeNode>();
		while (!stack.isEmpty() || head != null) {
			if (head != null) {  
				res.add(head.value + "");  // 记录当前节点的值
				stack.push(head);  // 模拟递归当前节点进栈
				head = head.left;  // 模拟递归preOrder(head.left)
			} else {
				head = stack.pop();  // 模拟递归preOrder(head.left)到达空值返回
				head = head.right;  // 模拟递归preOrder(head.right)
			}
		}
		return String.join(" ", res);
	}
	return null;
}

中序遍历

对应在LeetCode题目链接:中序遍历
按照先序遍历的非递归版,同样可以很容易的实现中序遍历的非递归版本:

public static String inOrder(TreeNode head) {
	if (head != null) {
		List<String> res = new LinkedList<String>();
		Stack<TreeNode> stack = new Stack<TreeNode>();
		while (!stack.isEmpty() || head != null) {
			if (head != null) {
				stack.push(head);
				head = head.left;
			} else {
				head = stack.pop();
				res.add(head.value + "");  // 与先序遍历不同的点是记录位置不一样
				head = head.right;
			}
		}
		return String.join(" ", res);
	}
	return null;
}

后序遍历

对应在LeetCode题目链接 :后序遍历
后序遍历的实现与先中序稍微不同,因为非递归无法能够向递归那样来到节点三次,所以只能转变思路,先实现中 -> 右 -> 左顺序,最后将结果逆序即可实现后序遍历!

public static String postOrder(TreeNode head) {
	if (head != null) {
		LinkedList<String> res = new LinkedList<String>();
		Stack<TreeNode> stack = new Stack<TreeNode>();
		// 模拟递归顺序中右左,通过链表来使结果逆序
		while (!stack.isEmpty() || head != null) {
			if (head != null) {
				res.addFirst(head.value + "");  // 每次向链表头部添加元素
				stack.push(head);
				head = head.right;  // 先向右
			} else {
				head = stack.pop();
				head = head.left;   // 再向左
			}
		}
		return String.join(" ", res);
	}
	return null;
}

二叉树的存储

我们知道程序是运行在内存中的,一旦程序结束,那么在程序中所构建的结构都将不复存在。为了解决这个问题,就非常要必要将二叉树以某种特定的顺序保存起来,以便下次继续使用。
这就是二叉树的序列化

  public static String serialize(TreeNode head){
       if (head == null) {
		return "#,";
	}
	String res = head.value + ",";  // 保存序列化结果
	// 始终存放有数值的节点
	Queue<TreeNode> queue = new LinkedList<TreeNode>();
	queue.add(head);
	while (!queue.isEmpty()) {
		head = queue.poll();
		if (head.left != null) {
			res += head.left.value + ",";
			queue.add(head.left);
		} else {
			res += "#,";
		}
		if (head.right != null) {
			res += head.right.value + ",";
			queue.add(head.right);
		} else {
			res += "#,";
		}
	}
	return res;
   }

以上就是一个根据广度优先顺序存储的序列化方法。其中有两点需要注意下:

  1. 节点为null,依然要记录,上述方法用#代替。
  2. 在每个节点的数值后添加,,是为了分开各个节点的数值,不然全部合在一起无法区分。

有序列化,与之对应的当然就有反序列化,在平时做题的过程中,例如LeetCode就是通过将给定的数值列表反序列化为二叉树,而它的后台则是通过序列化来比较你提交的答案是否与之相同。
所以下面来实现二叉树的反序列化:

// 将给定的数值转化为树节点
private static TreeNode generateNodeByString(String val) {
	if (val.equals("#")) {
		return null;
	}
	return new TreeNode(Integer.valueOf(val));
}

public static TreeNode deserialize(String levelStr) {
	String[] values = levelStr.split(",");
	int index = 0;
	TreeNode head = generateNodeByString(values[index++]);
	Queue<TreeNode> queue = new LinkedList<TreeNode>();
	if (head != null) {
		queue.add(head);
	}
	TreeNode node = null;
	while (!queue.isEmpty()) {
		node = queue.poll();
		node.left = generateNodeByString(values[index++]);
		node.right = generateNodeByString(values[index++]);
		if (node.left != null) {
			node.left.parent = node;
			queue.add(node.left);
		}
		if (node.right != null) {
			node.right.parent = node;
			queue.add(node.right);
		}
	}
	return head;
}

上述的实现与LeetCode不同的是:最后一行的空节点依然要在序列化后的字符串表现出来。

打印二叉树

最后做题需要直观的打印我们所构造的二叉树,所以非常有必要实现。

// 用于获得树的层数
	public static int getTreeDepth(TreeNode head) {
		return head == null ? 0 : (1 + Math.max(getTreeDepth(head.left), getTreeDepth(head.right)));
	}

	private static void writeArray(TreeNode currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
		// 保证输入的树不为空
		if (currNode == null)
			return;
		// 先将当前节点保存到二维数组中
		res[rowIndex][columnIndex] = String.valueOf(currNode.value);

		// 计算当前位于树的第几层
		int currLevel = ((rowIndex + 1) / 2);
		// 若到了最后一层,则返回
		if (currLevel == treeDepth)
			return;
		// 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
		int gap = treeDepth - currLevel - 1;

		// 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
		if (currNode.left != null) {
			res[rowIndex + 1][columnIndex - gap] = "/";
			writeArray(currNode.left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
		}

		// 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
		if (currNode.right != null) {
			res[rowIndex + 1][columnIndex + gap] = "\\";
			writeArray(currNode.right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
		}
	}

	public static void show(TreeNode root) {
		if (root == null)
			System.out.println("EMPTY!");
		// 得到树的深度
		int treeDepth = getTreeDepth(root);

		// 最后一行的宽度为2的(n - 1)次方乘3,再加1
		// 作为整个二维数组的宽度
		int arrayHeight = treeDepth * 2 - 1;
		int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
		// 用一个字符串数组来存储每个位置应显示的元素
		String[][] res = new String[arrayHeight][arrayWidth];
		// 对数组进行初始化,默认为一个空格
		for (int i = 0; i < arrayHeight; i++) {
			for (int j = 0; j < arrayWidth; j++) {
				res[i][j] = " ";
			}
		}

		// 从根节点开始,递归处理整个树
		// res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0');
		writeArray(root, 0, arrayWidth / 2, res, treeDepth);

		// 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
		for (String[] line : res) {
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < line.length; i++) {
				sb.append(line[i]);
				if (line[i].length() > 1 && i <= line.length - 1) {
					i += line[i].length() > 4 ? 2 : line[i].length() - 1;
				}
			}
			System.out.println(sb.toString());
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值