目录
1. 根据二叉树创建字符串
题目:
给你二叉树的根节点
root
,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。空节点使用一对空括号对
"()"
表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。
我们先看看实例一:
根据题目可知, 最终的输出是采用前序遍历的方式,前序遍历我们都懂,也就是“根左右”嘛,那么是如何得到这个输出的呢?
1)思路
其实很简单:我们观察示例给出的输出,首先我们从根节点1出发,根节点为空吗?不为空时,会把这个值输出,前序遍历的话,根节点走完是不是就往左边走了,根节点的左边(也就是2)是不是空?不是则打印左括号“(”,此时我们已经遍历到了2的位置,此时对于它来说可以看成是一颗根为2的新树,重复上述加粗的过程。我们是不是就得到输出的“1(2(4”了?
当节点走到4的时候,根节点为4,其左边为空,右边也为空,说明4这颗树已经完了,这个时候就直接加个“)”,注意:什么时候加 “)” ?树走完了的时候才加。当树走完了就回去,此时4就回到了2,如下图所示:
从示例的解释中我们可以知道,2的右侧为空时应该加一个“()”,但是实际输出中省略了,也就是说当左边不为空,右边为空时,直接返回(也就是这棵树已经走完) ,前面说了,树走完要干嘛?加 ")" !所以就回到了1,此时对应输出就是:1(2(4)) 这两个右括号分别是4和2这两棵树完时添加的,回到1时进入到右边的3,如下图所示:
1的右边不为空,所以添加“(”, 并把值添加进去,3的左右都为空,直接返回(树已经走完),加")"。就形成了示例中的输出 。
我们看实例二,这里有点区别的就是2的左树为空,右树不为空时不会省略"()"。
2)代码实现
public String tree2str(TreeNode root) {
StringBuilder sbu = new StringBuilder();
tree2strChild(root,sbu);
return sbu.toString();
}
public void tree2strChild(TreeNode root,StringBuilder sbu) {
if(root == null) {
return;
}
sbu.append(root.val);
//1、先递归左树
if(root.left != null) {
sbu.append("(");
tree2strChild(root.left,sbu);
sbu.append(")");
}else {
//root == 4
if(root.right == null) {
return;
}else {
sbu.append("()");
}
}
//2、递归右树
if(root.right != null) {
sbu.append("(");
tree2strChild(root.right,sbu);
sbu.append(")");
}else {
return;
}
}
2. 二叉树前序非递归遍历实现
题目:给你二叉树的根节点 root
,返回它节点值的 前序 遍历
1)思路:
前序遍历,“根左右”。如果是递归做法的话,这题对大家都没有难度,我们沿着递归的路径走去完成这题。根节点A打印很容易,但是打印了我们能不能丢了这个信息?不能,因为A的右边这棵树我们还要记录下来,所以打印A归打印A,A本身要记录下来,那么我们要怎么做呢?
我们先申请一个栈,从根节点A出发,root这个根节点不动,定义一个cur来代替根节点走,每次当节点不为空,我们就打印下来,同时把节点存进栈中 ,然后让cur沿着左边走。当我们把左边走完,cur就到了空的位置,说明“根左”走完了,那么我们怎么走“右”?如下图所示:
我们看栈顶元素,弹出栈顶元素 D,让cur等于栈顶元素的右边,为空时就继续弹出栈顶元素(B),然后继续让cur等于栈顶元素的右边(E),重复刚开始操作(节点不为空就打印并存入栈中),这样便可以完成这道题了。如下图所示:
我们可以定义一个top来存栈顶元素,如上述操作中,栈顶元素先是D,然后变成了E。
2)代码实现
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null) {
return list;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
list.add(cur.val);
cur = cur.left;
}
TreeNode top = stack.pop();
cur = top.right;
}
return list;
}
3. 二叉树中序非递归遍历实现
1)思路:
这题是中序遍历,“左根右”,所以我们要先打印左树再打印根,其实上面的前序遍历你弄懂了,这题中序遍历也不是什么大问题了,只是在前序遍历的基础上把打印的地方修改一下即可,在上题前序遍历中,我们只要一入栈就把该节点的值打印出来了,而这题我们只需要把打印的位置改到出栈的时候即可,为什么是出栈时候呢?看下图:
当cur走到D的左树时候为空,那么这个时候就要出栈,上文说的,出栈就要打印,这个时候就会把D打印出来(定义top来记录这个栈顶元素),同时cur 变成 栈顶元素的右边,如下图:
此时cur还是为空,所以继续出栈,栈顶元素就变为B,这个时候就会打印B,同时cur更新为B的右侧,如下图:
这里博主就不再赘述了,相信各位小伙伴已经清楚了,我们看代码:
2)代码实现:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null) {
return list;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()) {
while(cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.pop();
list.add(top.val);
cur = top.right;
}
return list;
}
}
4. 二叉树后序非递归遍历实现
1)思路:
后续遍历,“左右根”,跟上面一样,只要根不为空就放入栈中,但是不能打印! 如下图:
当cur走到空的时候能否出栈弹出D?不能,因为有一种可能就是D的右侧还有数据,所以这里有两种可能,一种D的右树为空,一种D的右树不为空,如下图所示D的右侧不为空:
这种情况我们只能将D拿来指示一下,更新cur到D的右侧,但是不出栈D,所以我们要把pop改成peek,后序遍历的代码跟我们上面的不太一样,如上图。所以我们根据中序遍历的代码修改如下:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null) {
return list;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()) {
while(cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.peek();
}
return list;
}
}
所以我们接下来的代码就要用if-else语言,写D的右树为空和D的右树不为空时的情况,if D的右树为空,那么就可以直接弹出栈顶元素同时打印,如下图:
else D的右树不为空,老规矩压栈,然后cur往H的左边走为空,如下图所示:
接着cur往H的右边走也为空,我们再次进入循环,这是个关键点,代码会再次peek一次,如下图,这一部分的代码如下:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null) {
return list;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur != null || !stack.isEmpty()) {
while(cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.peek();
if(top.right == null) {
stack.pop();
list.add(top.val);
}else {
cur = top.right;
}
}
return list;
}
}
peek完成后,D的右边为空吗?不为空则走else,cur = cur.right; 这个时候cur = H,又把H放进去了栈,这个时候代码就会出现死循环,为什么会死循环?因为只考虑了右边为空,而不为空会先被打印,也就说什么时候打印有两种情况,右边为空和右边已经被打印了,所以要记录下来上一个被打印的节点,所以我们接下来的问题就是如何证明被打印过的呢?我们回到D的左边,如下图:
依据代码,top的右边为空吗?D不为空cur = cur.right. 如下:
再次进入循环,把H压栈,同时cur再往左边走:
接下来代码再peek一下,top更新为H,H的右边为空,所以出栈并打印H,
这个时候定义一个引用prev来指向当前的top来证明被打印, 此时if走完了,此时cur为空,如下图:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null) {
return list;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode prev = null;
while(cur != null || !stack.isEmpty()) {
while(cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.peek();
if(top.right == null || 被打印过) {
stack.pop();
list.add(top.val);
prev = top;
}else {
cur = top.right;
}
}
return list;
}
}
然后代码继续向后走,D的右侧为空吗?不为空,但是被打印过吗?也就是说top.right == prev吗?满足条件,此时就会弹出D,同时打印,prev更新为D,如下图:
依此类推,通过prev便可以记录下来最近一次被打印过的节点。
2)代码实现:
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null) {
return list;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode prev = null;
while(cur != null || !stack.isEmpty()) {
while(cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.peek();
if(top.right == null || top.right == prev) {
stack.pop();
list.add(top.val);
prev = top;
}else {
cur = top.right;
}
}
return list;
}
}
感谢阅读,希望对大家有所帮助!!