算法学习04:树

树的遍历

二叉树的递归遍历

// 二叉树非递归前序遍历
public static void preOrderRecur(Node head) {
	if (head == null) {
		return;
	}
	System.out.print(head.value + " ");
	preOrderRecur(head.left);
	preOrderRecur(head.right);
}

// 二叉树非递归中序遍历
public static void inOrderRecur(Node head) {
	if (head == null) {
		return;
	}
	inOrderRecur(head.left);
	System.out.print(head.value + " ");
	inOrderRecur(head.right);
}

// 二叉树非递归后序遍历
public static void posOrderRecur(Node head) {
	if (head == null) {
		return;
	}
	posOrderRecur(head.left);
	posOrderRecur(head.right);
	System.out.print(head.value + " ");
}

二叉树的非递归遍历

三种遍历中访问各节点的次序都是相同的,所不同的是打印节点的时机.
下边的三个遍历中,前序遍历和后续遍历的写法都较巧妙,并非常见写法. 而中序遍历的写法是最常见的写法,且对该中序遍历加以修改,可以得到最常见前序遍历和后序遍历写法.

前序遍历

前序遍历,应该先输出根节点,再输出左子树,再输出右子树.
因此我们先将根节点打印出来,然后先将右子树入栈,再将左子树入栈. 这样可以保证左子树比右子树先出栈被打印.

public static void preOrderUnRecur(Node head) {
	System.out.print("pre-order: ");
	if (head != null) {
		Stack<Node> stack = new Stack<Node>();
		stack.add(head);
		while (!stack.isEmpty()) {
			head = stack.pop();
			System.out.print(head.value + " ");
			// 先将右子树入栈,再将左子树入栈
			//	出栈的顺序与入栈正好相反
			if (head.right != null) {
				stack.push(head.right);
			}
			if (head.left != null) {
				stack.push(head.left);
			}
		}
	}
}

中序遍历

先将指针一路向左子树移动,若向左子树移动到头,说明左子树被遍历完了,则是时候输出根节点并将指针转向右子树了,因此将指针向右子树移动一位(接下来还要遍历完左子树).
对这个中序遍历加以修改,可以得到一般的前序遍历和后序遍历写法.

public static void inOrderUnRecur(Node head) {
	System.out.print("in-order: ");
	if (head != null) {
		Stack<Node> stack = new Stack<Node>();
		while (!stack.isEmpty() || head != null) {
			if (head != null) {
				// 若将输出语句移到这一行,即为前序遍历(第一次遇见该数就输出)
				stack.push(head);
				head = head.left;
			} else {
				head = stack.pop();
				// 输出语句在这一行,是为中序遍历(将左子树遍历完成才输出)
				System.out.print(head.value + " ");
				head = head.right;
			}
		}
	}
}

后序遍历

后序遍历的输出顺序是: 左子树,右子树,根节点.
因此我们将数据按照:根节点,右子树,左子树顺序入栈,然后再将其反向弹出,弹出的顺序自然是左子树,右子树,根节点.

public static void posOrderUnRecur(Node head) {
	System.out.print("pos-order: ");
	if (head != null) {
		Stack<Node> s1 = new Stack<Node>();
		Stack<Node> s2 = new Stack<Node>();
		s1.push(head);
		// s1栈是用来做类似前序遍历的,将节点按照[根节点,右子树,左子树]顺序入栈s2
		while (!s1.isEmpty()) {
			head = s1.pop();
			s2.push(head);
			if (head.left != null) {
				s1.push(head.left);
			}
			if (head.right != null) {
				s1.push(head.right);
			}
		}
		// 将数据反序弹出,其弹出的顺序自然是[左子树,右子树,根节点]
		while (!s2.isEmpty()) {
			System.out.print(s2.pop().value + " ");
		}
	}
}

对刚才的中序遍历加以改写,就能得到一般的后序遍历写法.
后序遍历的难点在于要将左右子树都遍历完之后才能输出根节点,因此要使用一个lastPrinted指针记载上一次被输出的值,通过这个指针来判断右子树是否被遍历完全.

public static void posOrderUnRecur(Node head) {
	System.out.print("pos-order: ");
	if (head != null) {
		Stack<Node> stack = new Stack<Node>();
		Node lastPrinted = null; // 记录上次被输出的节点
		while (!stack.isEmpty() || head != null) {
			// 与中序遍历相同,往左子树方向走到底,之后再回头遍历右子树
			if (head != null) {
				// 向左子树方向移动
				stack.push(head);
				head = head.left;
			} else {
				// 向左子树方向移动到底,下面遍历其右子树
				head = stack.pop();
				if (head.right == null || head.right == lastPrinted) {
					// 若右子节点为空,或右子节点(后序遍历时右子树的最后一项)上次已被输出,则此节点也应该被输出
					System.out.print(head.value + " ");
					lastPrinted = head;
					// 该节点(及其子树)已被输出,失去利用价值.
					// 要注意把遍历指针置空,这样下次进入循环会从栈顶拿一个新节点,否则循环一致同在次节点了.
					head = null;
				} else {
					// 若右子节点还没被输出,则根节点也不应被输出,将其放回栈
					stack.push(head);
					head = head.right;
				}
			}
		}
	}
}

注意:将某节点输出后,该节点就失去了利用价值.要注意把遍历指针置空,下次进入循环时会取栈顶,若不置空,则下次进入循环将继续访问此作废节点

二叉树中的后继节点和前驱节点

二叉树的前驱节点后继节点指的是某节点在中序遍历中的上一个节点和下一个节点.

  1. 寻找后继节点要分两种情况:
    1. 若该节点有右子树,则寻找其右子树的最左节点(从右子树根一路向左孩子遍历到底)
    2. 若该节点没有右子树,则该节点应是一个左子树的最后一项,因此向上找到第一个作为左孩子的祖宗节点,并返回此祖宗节点的父节点.
  2. 寻找前驱节点的方法正相反.

二叉树的序列化和反序列化leetcode297

二叉树的序列化就是将二叉树存储成一个字符串,反序列化就是将二叉树由字符串解析回二叉树.
二叉树的序列化过程其实就是遍历过程,若遇到空节点,要输出占位符,否则会发生混淆.

二叉树的套路化问题: 递归函数很好用

递归函数可以访问一个节点三次,访问过左右子树后会回到根节点,这会将左右子树的信息带回给根节点.

扫描二维码关注公众号,回复: 5517376 查看本文章

判断一棵二叉树是否是平衡二叉树leetcode 110

递归函数应返回两个信息: 子树的高度子树是否平衡, 由左右子树返回的这两个信息可以确定根节点这两个信息.

递归函数的改进: 将子树的高度子树是否平衡融合为一个信息,若子树不平衡,则高度无意义,因此可以设计该函数平衡时返回高度,不平衡时返回-1.

判断一棵树是否是搜索二叉树leetcode 98

中序遍历,查看打印的数是否有序.

通常来讲,搜索二叉树不包含重复节点. 一般来说,每个数节点作为key,其value可以是一个列表,存放多个信息.

完全二叉树

判断一棵树是否是完全二叉树leetcode 958

对一个完全二叉树层次遍历,其节点的孩子状况必然经过: 左右双全->有左无右->左右均无的过程.
解法

  1. 对这棵树层次遍历:
    1. 若遇到一个节点,有右无左,则这种节点不可能出现在完全二叉树中,返回false
    2. 若遇到一个节点,有左无右或者左右均无,则其后边遇到的所有节点均必须是叶结点.
    3. 若遇到一个节点,左右双全,说明这时还在遍历的第一种状态,若此时状态要求遇到叶结点,则返回false.
  2. 若遍历完成,还没有遇到不合适节点,说明这棵树是一棵完全二叉树,返回true.

求一棵完全二叉树节点数目leetcode 222

解决这道题应用到完全二叉树的两个性质:

  1. 一棵高度为h的二叉树,其节点个数为2h-1.
  2. 对一棵完全二叉树:
    若不是满二叉树,则其左子树和右子树中至少一个是满二叉树.
    若是满二叉树,其左右子树均是完全二叉树.

因此想到这道题的解法:

  1. 先遍历树的左边界到底,得到树的高度.
  2. 再遍历树的右边界到底,可以判断出树是否是满二叉树.
    1. 若这棵树是满二叉树,则直接返回其节点数
    2. 若这棵树不是满二叉树,则遍历根节点左子树的右边界,判断左右两棵子树哪个是满二叉树.满子树的节点数可以算出,对非满子树,递归调用此函数计算节点数.

猜你喜欢

转载自blog.csdn.net/ncepu_Chen/article/details/88350724
今日推荐