算法学习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;
}
}
}
}
}
注意:将某节点输出后,该节点就失去了利用价值.要注意把遍历指针置空,下次进入循环时会取栈顶,若不置空,则下次进入循环将继续访问此作废节点
二叉树中的后继节点和前驱节点
二叉树的前驱节点
和后继节点
指的是某节点在中序遍历
中的上一个节点和下一个节点.
- 寻找后继节点要分两种情况:
- 若该节点有右子树,则寻找其右子树的最左节点(从右子树根一路向左孩子遍历到底)
- 若该节点没有右子树,则该节点应是一个左子树的最后一项,因此向上找到第一个作为左孩子的祖宗节点,并返回此祖宗节点的父节点.
- 寻找前驱节点的方法正相反.
二叉树的序列化和反序列化leetcode297
二叉树的序列化就是将二叉树存储成一个字符串,反序列化就是将二叉树由字符串解析回二叉树.
二叉树的序列化过程其实就是遍历过程,若遇到空节点,要输出占位符,否则会发生混淆.
二叉树的套路化问题: 递归函数很好用
递归函数可以访问一个节点三次,访问过左右子树后会回到根节点,这会将左右子树的信息带回给根节点.
判断一棵二叉树是否是平衡二叉树leetcode 110
递归函数应返回两个信息: 子树的高度
和 子树是否平衡
, 由左右子树返回的这两个信息可以确定根节点这两个信息.
递归函数的改进: 将
子树的高度
和子树是否平衡
融合为一个信息,若子树不平衡,则高度无意义,因此可以设计该函数平衡时返回高度,不平衡时返回-1.
判断一棵树是否是搜索二叉树leetcode 98
中序遍历,查看打印的数是否有序.
通常来讲,搜索二叉树不包含重复节点. 一般来说,每个数节点作为key,其value可以是一个列表,存放多个信息.
完全二叉树
判断一棵树是否是完全二叉树leetcode 958
对一个完全二叉树层次遍历,其节点的孩子状况必然经过: 左右双全
->有左无右
->左右均无
的过程.
解法
- 对这棵树层次遍历:
- 若遇到一个节点,
有右无左
,则这种节点不可能出现在完全二叉树中,返回false
- 若遇到一个节点,
有左无右
或者左右均无
,则其后边遇到的所有节点均必须是叶结点. - 若遇到一个节点,
左右双全
,说明这时还在遍历的第一种状态,若此时状态要求遇到叶结点,则返回false
.
- 若遇到一个节点,
- 若遍历完成,还没有遇到不合适节点,说明这棵树是一棵完全二叉树,返回
true
.
求一棵完全二叉树节点数目leetcode 222
解决这道题应用到完全二叉树的两个性质:
- 一棵高度为h的二叉树,其节点个数为2h-1.
- 对一棵完全二叉树:
若不是满二叉树,则其左子树和右子树中至少一个是满二叉树.
若是满二叉树,其左右子树均是完全二叉树.
因此想到这道题的解法:
- 先遍历树的左边界到底,得到树的高度.
- 再遍历树的右边界到底,可以判断出树是否是满二叉树.
- 若这棵树是满二叉树,则直接返回其节点数
- 若这棵树不是满二叉树,则遍历根节点左子树的右边界,判断左右两棵子树哪个是满二叉树.满子树的节点数可以算出,对非满子树,递归调用此函数计算节点数.