递归序列的认识(太重要了!!!)
上图就是左侧时一颗树,右上是递归序,下方是三种遍历方式,先序、中序和后序,那么如何从递归序变成下面三种遍历方式呢?
- 先序遍历就是递归序中只打印第一次到出现的数字
- 中序遍历就是递归序中只打印第二次到出现的数字
- 后序遍历就是递归序中只打印第三次到出现的数字
对应我们经常看到的代码
public static void treeLoop(Node head){
if(head == null){
return;
}
//一进来,当前位置就是自己的位置
//第一次打印自己
System.out.println(head.val);
//访问左子树
treeLoop(head.left);
//左子树访问完毕,回到自己
//第二次打印自己
System.out.println(head.val);
//访问右子树
treeLoop(head.right);
//右子树访问完毕,回到自己
//第三次打印自己
System.out.println(head.val);
}
所以说为什么下面先序遍历、中序遍历、后序遍历只是改变打印位置,然后就是对应的遍历方式,就是这个原因
树的遍历
递归方式
1、先序遍历(头->左->右)
public static void treeLoop(Node head){
if(head == null){
return;
}
System.out.println(head.val);
treeLoop(head.left);
treeLoop(head.right);
}
2、中序遍历(左->头->右)
public static void treeLoop(Node head){
if(head == null){
return;
}
treeLoop(head.left);
System.out.println(head.val);
treeLoop(head.right);
}
3、后序遍历(左->右->头)
public static void treeLoop(Node head){
if(head == null){
return;
}
treeLoop(head.left);
treeLoop(head.right);
System.out.println(head.val);
}
递归方式就是打印位置不一样,是根据递归序而来的,需要用哪种遍历方式,就在对应位置做想做的事事情
非递归方式
1、先序遍历
思路:申请一个栈,每次先把一个元素放入栈中,如果栈不为空,从栈弹出一个元素,做事情,然后再把右孩子压栈,再把左孩子压栈
public static void treeLoop(Node head){
if(head != null){
Stack<Node> stack = new Stack<>();
stack.add(head);
while(!stack.isEmpty()){
Node pop = stack.pop();
System.out.println(pop);
if(head.right != null){
stack.push(head.right);
}
if(head.left != null){
stack.push(head.left);
}
}
}
}
2、中序遍历
每课子树左边界进栈,每次弹出一个元素,打印,并把当前元素的右子树的左边界进栈,周而复始
public static void treeLoop(Node head){
if(head != null){
Stack<Node> stack = new Stack<>();
//一开始栈为空,Node不为空,所以head不为空,可以进入while循环
while(!stack.isEmpty() || head != null){
//所有左孩子进栈
if(head != null){
stack.push(head);
head = head.left;
} else {
//弹出最后一个左孩子,打印(处理),再把最后一个左孩子的右孩子
head = stack.pop();
System.out.println(head.val + " ");
head = head.right;
}
}
}
}
3、后序遍历
思路:申请两个栈,一个栈和一个收集栈,先把第一个元素放入栈中,判断左栈是否为空,不为空,弹出一个元素,放入收集栈,再把左孩子和右孩子压入栈中,周而复始,最后输出辅助栈
public static void treeLoop(Node head){
if(head != null){
Stack<Node> s1 = new Stack<>(); //栈
Stack<Node> s2 = new Stack<>(); //辅助栈
s1.add(head);
while(!s1.isEmpty()){
Node pop = s1.pop();
s2.push(pop);
if(head.left != null){
s1.push(head.left);
}
if(head.right != null){
s1.push(head.right);
}
}
while(!s2.isEmpty()){
System.out.print(s2.pop().val + " ");
}
}
System.out.println();
}
宽度优先遍历(层次遍历)
思路:使用队列完成
public static void treeLoop(Node head){
if(head ==null){
return;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while(!queue.isEmpty()){
Node cur = queue.poll();
System.out.println(cur.val);
if(cur.left != null){
queue.add(cur.left);
}
if(cur.right != null){
queue.add(cur.right);
}
}
}
题目:
根据宽度优先遍历,求一课树最长的最大的宽度,null不算在内
public static void treeLoop(Node head){
if(head ==null){
return;
}
Queue<Node> queue = new LinkedList<>();
//存放节点和层级的map
Map<Node,Integer> levelMap = new HashMap<>();
levelMap.put(head,1);
//当前在第几层,这个是全局增长的
int curLevel = 1;
//当前层有多少节点,这个没到一层就会重置
int curLevelNodes = 0;
//最大宽度
int maxNodes = Integer.MIN_VALUE;
queue.add(head);
while(!queue.isEmpty()){
Node cur = queue.poll();
//判断当前节点是不是和我们层级一样
if(curLevel == levelMap.get(cur)){
curLevelNodes++;
}else{
maxNodes = Math.max(maxNodes,curLevelNodes);
curLevel++;
curLevelNodes = 1;
}
if(cur.left != null){
//维护map
levelMap.put(cur.left,curLevel + 1);
queue.add(cur.left);
}
if(cur.right != null){
//维护map
levelMap.put(cur.right,curLevel + 1);
queue.add(cur.right);
}
}
//最后比较最后一层节点数量和我们最大节点比较,最后一层在上面未比较
maxNodes = Math.max(maxNodes,curLevelNodes);
}
以上所有内容是皆来自于算法课程
B站-左程云左神的教学视频