左神算法讲堂笔记 05 树
1、非递归遍历树
非递归遍历有点难,目前只能读懂代码,里面的精髓还未真正参透。
先序遍历:不断地把根入栈,再取出来输出
根左右, 那么先入栈的肯定是根,要保证输出左子树,再输出右子树,那么就要先入栈右子树。
void preOrderUnRecur(Node head) {
System.out.println("先序 根 左 右: ");
if (head != null) {
Stack<Node> stack = new Stack<>();
stack.push(head);
while (!stack.empty()) {
Node node = stack.pop();
System.out.println(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
}
}
中序遍历:必须左子树走到底,才能取出来输出,然后整体移动到右子树进行
void inOrderUnRecur(Node head) {
System.out.println("中序 左 根 右: ");
if (head != null) {
Stack<Node> stack = new Stack<>();
while (!stack.empty() || head != null) {
// 1)得到一颗树, 原则就是左子树走到底
// 2)不能再走时, 就取出根进行输出,
// 3) 接着换到右子树上重复1)
if (head != null) {
// 保证左子树压栈到底
stack.push(head);
head = head.left;
}else{
// 左子树走完之后, 就到了根 , 然后进行右子树操作
head = stack.pop();
System.out.println(head.val);
// 右子树必须执行完才能返回上一层,
// 因此整体移植到右子树进行操作(有种递归的感觉,想象右子树分离,传入head)
head = head.right;
}
}
}
}
后序遍历:不断地取栈,然后左右节点入栈。用第二个栈把头节点存起来方便输出
后序遍历是 左 右 根 , 和先序遍历的 根 左 右 其实只要根输出位置变化了而已。利用栈把输出序列保存。
void posOrderUnRecur1(Node head) {
System.out.println("后序 左 右 根: ");
if (head != null) {
Stack<Node> stack = new Stack<>();
Stack<Node> stack2 = new Stack<>();
stack.push(head);
while (!stack.empty()) {
head = stack.pop();
//根最后输出,因此根最先加
stack2.push(head);
// 因为先执行的后输出,因此先执行右子树
if (head.left != null) {
stack.push(head.left);
}
if (head.right != null) {
stack.push(head.right);
}
}
while (!stack2.empty()) {
System.out.println(stack2.pop().val);
}
}
}
2、前驱后继问题
后继
分两步,第一步向下看, 第二步向上看。具体解释在代码里
前驱
分两步,第一步向下看, 第二步向上看。
package zcy;
import org.junit.Test;
import zcy.Utils.PrintBinaryTree;
public class PreAftTree {
public static class Node {
public int value;
public Node left;
public Node right;
public Node parent;
public Node(int data) {
this.value = data;
}
}
// 获取某结点的后继
public Node getSuccessorNode(Node node) {
if (node == null) {
return node;
}
if (node.right != null) {
// 第一步 向下看
// 如果右子树不为空,那么后继肯定在右子树的最左边
return getLeftMost(node.right);
}else{
// 第二步 向上看
// 两种情况,1)当前结点位于 父节点的 左子树 --> 后继是父结点
// 2)当前结点位于父节点的右子树 --> 不断向上找推,直到一个结点属于 父节点的左子树
Node parent = node.parent;
if (parent != null && parent.left != node) {
node = parent;
parent = node.parent;
}
return parent;
}
}
public static Node getLeftMost(Node node) {
if (node == null) {
return node;
}
while (node.left != null) {
node = node.left;
}
return node;
}
// 获取某结点的前驱
public Node getQian(Node node) {
if (node == null) {
return node;
}
if (node.left != null) {
// 第一步 向下看,如果左子树存在,那么找到左子树的最右端
return getRightMost(node.left);
}else{
// 第二步 向上看,找到当前结点属于父节点的右孩子。
Node parent = node.parent;
while (parent != null && parent.right != node) {
node = parent;
parent = node.parent;
}
return parent;
}
}
public static Node getRightMost(Node node) {
if (node == null) {
return node;
}
while (node.right != null) {
node = node.right;
}
return node;
}
@Test
public void testForTree() {
Node head = new Node(6);
head.parent = null;
head.left = new Node(3);
head.left.parent = head;
head.left.left = new Node(1);
head.left.left.parent = head.left;
head.left.left.right = new Node(2);
head.left.left.right.parent = head.left.left;
head.left.right = new Node(4);
head.left.right.parent = head.left;
head.left.right.right = new Node(5);
head.left.right.right.parent = head.left.right;
head.right = new Node(9);
head.right.parent = head;
head.right.left = new Node(8);
head.right.left.parent = head.right;
head.right.left.left = new Node(7);
head.right.left.left.parent = head.right.left;
head.right.right = new Node(10);
head.right.right.parent = head.right;
PrintBinaryTree.printTree(head);
Node test = head.left.left;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.left.left.right;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.left;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.left.right;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.left.right.right;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.right.left.left;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.right.left;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.right;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.right.right; // 10's next is null
System.out.println(test.value + " next: " + getSuccessorNode(test));
}
}
3、平衡二叉树
左子树和右子树高度差不超过1
一个办法是求左右子树的高度,然后进行判断。 这样有个问题,每个结点都会进入子树进行求高度,log(n) ^2 。 我们可以这样做,求高度的时候如果发现左右子树高度差>1,那么就返回-1,标明这棵树不是平衡二叉树。
public boolean IsBalanced_Solution(TreeNode root) {
if(root == null)
return true;
return TreeDepth(root)!=-1;
}
public int TreeDepth(TreeNode root) {
if(root == null)return 0;
int left = TreeDepth(root.left);
if(left == -1) return -1;
int right = TreeDepth(root.right);
if(right == -1) return -1;
return Math.max(left, right) - Math.min(left, right) > 1 ? -1 : Math.max(left, right) + 1;
}
4、搜素二叉树
对于一个根结点,左边小于根,右边大于根。中序遍历,得到的序列是升序的。
5、完全二叉树
一层一层遍历下来(用队列保存,左右入队列,出队列的顺序就是按层遍历)
解法:
遇到这种情况1),直接false,
遇到情况2),后面遇到的结点都必须是叶子结点
举个列子
boolean isBST(Node head) {
if (head == null) {
return true;
}
Queue<Node> Q = new LinkedList<>();
boolean isLeaf = false;
Q.offer(head);
while (!Q.isEmpty()) {
Node node = Q.poll();
Node l = node.left;
Node r = node.right;
// 违反 2) 违反 1)
if ((isLeaf && (l != null || r != null)) || (l == null && r != null)) {
return false;
}
if (l != null) {
Q.offer(l);
}
if (r != null) {
Q.offer(r);
}
/*else{
isLeaf = true;
}*/
//容易理解
if(l==null ||r==null){
isLeaf = true;
}
}
return true;
}
6、 求完全二叉树节点数
如果满二叉树: 2^n - 1
先求出树的高度(一直向左),然后分两种情况
1)如果根节点的右子树的左子树到达最后一层,那么根节点的左子树可以直接算出来(2^h -1), h表示左子树的高度),再递归进去算右子树。
2)如果根节点的右子树的左子树没有到达最后一层,那么右子树可以直接算出来(2 ^h -1 ,h代表右子树的高度)。再递归进去算左子树。
时间复杂度计算
bs()方法总共会执行 o(logn)次, 因为每一层只有一个结点会进入这个方法。然后每个结点求右子树的左子树能不能到达,也就是执行mostLeftLevel,又耗费o(logn),因此复杂度是o(logn)^2。
int nodeNum(Node head) {
if(head == null)return 0;
return bs(head, 1, mostLeftLevel(head, 1));
}
// 传入结点和结点所在的层, 和总高度
int bs(Node node, int l, int h) {
if (l == h) {
return 1;
}
// 询问当前结点的右孩子,的左孩子,能不能到达最底层
if (mostLeftLevel(node.right, l + 1) == h) {
// 如果能,那么当前结点的左子树直接算出来 2^(h-l)-1 ,再加上根节点的1
return (1 << (h - l) + bs(node.right, l + 1, h));
}else{
// 如果不能到达底层,那么当前结点的右子树可以直接算出来 2^(h-l-1)-1 ,再加上根节点的1
return (1 << (h - l - 1) + bs(node.left, l + 1, h));
}
}
// 传入一个结点和结点所在的层,返回向右走,能走到第几层
int mostLeftLevel(Node head, int level) {
while (head != null) {
head = head.left;
level++;
}
return level-1;
}