Bootstrap

二叉树遍历应用

preorder

在这里插入图片描述

一路向左走,当访问节点为null时,转向右边

◼ 利用栈实现

  1. 设置 node = root
  2. 循环执行以下操作
    如果 node != null
    ✓ 对 node 进行访问
    ✓ 将 node.right 入栈
    ✓ 设置 node = node.left
    如果 node == null
    ✓ 如果栈为空,结束遍历
    ✓ 如果栈不为空,弹出栈顶元素并赋值给 node

最终前序遍历:6421357,但是总是先从根节点开始访问,所以对于357根节点发现其访问顺序和前序遍历顺序正好相反,则考虑使用栈

栈中保存的全部都是右子节点,所以当第一次出栈时,弹出3,此时处理3和处理6的逻辑相同,继续循环

	public void preorder(Visitor<E> visitor) {
		if (visitor == null || root == null) return;
		Node<E> node = root;
		Stack<Node<E>> stack = new Stack<>();
		while (true) {
			if (node != null) {
				// 访问node节点
				if (visitor.visit(node.element)) return;
				// 将右子节点入栈
				if (node.right != null) {
					stack.push(node.right);
				}
				// 向左走
				node = node.left;
			} else if (stack.isEmpty()) {
				return;
			} else { 
				// 处理右边
				node = stack.pop();
			}
		}
	}
  1. 将 root 入栈
  2. 循环执行以下操作,直到栈为空
    弹出栈顶节点 top,进行访问
    将 top.right 入栈
    将 top.left 入栈
	public void preorder(Visitor<E> visitor) {
		if (visitor == null || root == null) return;
		Stack<Node<E>> stack = new Stack<>();
		stack.push(root);
		while (!stack.isEmpty()) {
			Node<E> node = stack.pop();
			// 访问node节点
			if (visitor.visit(node.element)) return;
			if (node.right != null) {
				stack.push(node.right);
			}
			if (node.left != null) {
				stack.push(node.left);
			}
		}
	}

inorder

在这里插入图片描述

中序遍历:1234567,访问节点的顺序6421,但是其遍历顺序正好相反,即仍使用栈

栈中保存的全部都是父节点,所以当第二次出栈时,弹出2,访问其右节点3,处理逻辑与6相同,继续循环

	public void inorder(Visitor<E> visitor) {
		if (visitor == null || root == null) return;
		Node<E> node = root;
		Stack<Node<E>> stack = new Stack<>();
		while (true) {
			if (node != null) {
				stack.push(node);
				// 向左走
				node = node.left;
			} else if (stack.isEmpty()) {
				return;
			} else {
				node = stack.pop();
				// 访问node节点
				if (visitor.visit(node.element)) return;
				// 让右节点进行中序遍历
				node = node.right;
			}
		}
	}

postorder

在这里插入图片描述

◼ 利用栈实现

  1. 将 root 入栈
  2. 循环执行以下操作,直到栈为空
    如果栈顶节点是叶子节点 或者 上一次访问的节点是栈顶节点的子节点(即子节点已经访问过)
    ✓ 弹出栈顶节点,进行访问
    否则
    ✓ 将栈顶节点的right、left按顺序入栈

后序遍历:1325476,访问顺序6421,依然相反,即使用栈,注意此时遍历顺序为476,所以入栈顺序为674

	public void postorder(Visitor<E> visitor) {
		if (visitor == null || root == null) return;
		// 记录上一次弹出访问的节点
		Node<E> prev = null;
		Stack<Node<E>> stack = new Stack<>();
		stack.push(root);
		while (!stack.isEmpty()) {
			Node<E> top = stack.peek();
			if (top.isLeaf() || (prev != null && prev.parent == top)) {
				prev = stack.pop();
				// 访问节点
				if (visitor.visit(prev.element)) return;
			} else {
				if (top.right != null) {
					stack.push(top.right);
				}
				if (top.left != null) {
					stack.push(top.left);
				}
			}
		}
	}

level order

◼ 实现思路:使用队列

  1. 将根节点入队
  2. 循环执行以下操作,直到队列为空
    将队头节点 A 出队,进行访问
    将 A 的左子节点入队
    将 A 的右子节点入队
	public void levelOrder(Visitor<E> visitor) {
		if (root == null || visitor == null) return;
		
		Queue<Node<E>> queue = new LinkedList<>();
		queue.offer(root);
		
		while (!queue.isEmpty()) {
			Node<E> node = queue.poll();
			if (visitor.visit(node.element)) return; // 访问节点
			
			if (node.left != null) {
				queue.offer(node.left);
			}
			
			if (node.right != null) {
				queue.offer(node.right);
			}
		}
	}

isComplete

完全二叉树(Complete Binary Tree)

在这里插入图片描述

◼ 完全二叉树:对节点从上至下、左至右开始编号,其所有编号都能与相同高度的满二叉树中的编号对应

◼ 叶子节点只会出现最后 2 层,最后 1 层的叶子结点都靠左对齐
◼ 完全二叉树从根结点至倒数第 2 层是一棵满二叉树
◼ 满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树

完全二叉树的性质

◼ 度为 1 的节点只有左子树

◼ 度为 1 的节点要么是 1 个,要么是 0 个

◼ 同样节点数量的二叉树,完全二叉树的高度最小

◼ 假设完全二叉树的高度为 h ( h ≥ 1 ),那么
至少有 2h − 1 个节点 ( 20 + 21 + 22 + ⋯ + 2h−2 + 1 )
最多有 2h − 1 个节点( 20 + 21 + 22 + ⋯ + 2h−1 ,满二叉树 )
总节点数量为 n
✓ 2h − 1 ≤ n < 2h
✓ h − 1 ≤ log2n < h
✓ h = floor( log2n ) + 1

◼ 如果一棵完全二叉树有 768 个节点,求叶子节点的个数
假设叶子节点个数为 n0,度为 1 的节点个数为 n1,度为 2 的节点个数为 n2
总结点个数 n = n0 + n1 + n2,而且 n0 = n2 + 1 (每个节点都有父结点,但是根节点没有)
✓ n = 2n0 + n1 – 1

完全二叉树的 n1 要么为 0,要么为 1
✓ n1为1时,n = 2n0,n 必然是偶数
➢ 叶子节点个数 n0 = n / 2,非叶子节点个数 n1 + n2 = n / 2
✓ n1为0时,n = 2n0 – 1,n 必然是奇数
➢ 叶子节点个数 n0 = (n + 1) / 2,非叶子节点个数 n1 + n2 = (n – 1) / 2

叶子节点个数 n0 = floor( (n + 1) / 2 ) = ceiling( n / 2 )
非叶子节点个数 n1 + n2 = floor( n / 2 ) = ceiling( (n – 1) / 2 )
因此叶子节点个数为 384

◼ 一棵有 n 个节点的完全二叉树(n > 0),从上到下、从左到右对节点从 0 开始进行编号,对任意第 i 个节点
如果 i = 0 ,它是根节点
如果 i > 0 ,它的父节点编号为 floor( (i – 1) / 2 )

如果 2i + 1 ≤ n – 1 ,它的左子节点编号为 2i + 1
如果 2i + 1 > n – 1 ,它无左子节点

如果 2i + 2 ≤ n – 1 ,它的右子节点编号为 2i + 2
如果 2i + 2 > n – 1 ,它无右子节点

按索引找到第一个叶子节点,之后所有节点都是叶子节点

判断一棵树是否为完全二叉树

在这里插入图片描述

◼ 如果树为空,返回 false
◼ 如果树不为空,开始层序遍历二叉树(用队列)
如果 node.left!=null,将 node.left 入队
如果 node.left=null && node.right!=null,返回 false (此时度为1,那么为J,只能是left不为空right为空)
如果 node.right!=null,将 node.right 入队
如果 node.right=null
✓ 那么后面遍历的节点应该都为叶子节点,才是完全二叉树
✓ 否则返回 false
遍历结束,返回 true

	public boolean isComplete() {
		if (root == null) return false;
		Queue<Node<E>> queue = new LinkedList<>();
		queue.offer(root);
		
		boolean leaf = false;
		while (!queue.isEmpty()) {
			Node<E> node = queue.poll();
			if (leaf && !node.isLeaf()) return false;

			if (node.left != null) {
				queue.offer(node.left);
			} else if (node.right != null) {
				return false;
			}
			
			if (node.right != null) {
				queue.offer(node.right);
			} else { // 后面遍历的节点都必须是叶子节点
				leaf = true;
			}
		}
		
		return true;
	}

height

递归

整个树的高度 = 左子树高度和右子树高度的最大值 + 1(父节点 )

	public int height() {
		return height(root);
	}
	
	private int height(Node<E> node) {
		if (node == null) return 0;
		return 1 + Math.max(height(node.left), height(node.right));
	}

迭代

层序遍历的特点:每扫描完一层,树的高度+1,即遍历完所有节点,就求出树的高度

要记录每一层的节点数量,每次从队列中出队一个节点,levelsize–

当levelsize==0时意味着该层已经遍历完,要继续访问下一层,并重新赋值levelsize

	public int height() {
		if (root == null) return 0;
		
		// 树的高度
		int height = 0;
		// 存储着每一层的元素数量
		int levelSize = 1;
		Queue<Node<E>> queue = new LinkedList<>();
		queue.offer(root);
		
		while (!queue.isEmpty()) {
			Node<E> node = queue.poll();
			levelSize--;
			
			if (node.left != null) {
				queue.offer(node.left);
			}
			
			if (node.right != null) {
				queue.offer(node.right);
			}

			if (levelSize == 0) { // 意味着即将要访问下一层
				levelSize = queue.size();
				height++;
			}
		}
		
		return height;
	}

Reference:小码哥MJ

;