关于二叉树的递归遍历就不写了,这里介绍一下非递归方式

二叉树的非递归遍历

二叉树结构如下:

public static class Node {
	  public intvalue;
	  public Node left;
	  public Node right;
	
	  public Node(int value) {
	      this.value = value;
  }
先序

需要按照根-左-右的方式遍历。

所以将根节点压入栈后,先输出它的值。接着看看根节点有无右子树,如果有则把右节点压入,然后再压左子树,因为栈是后进先出的所以后压入左子树可以保证其先输出。

public void nonRecursionPreOrder(Node head) {
        if (head != null) {
            Stack<Node> stack = new Stack<>();
            stack.push(head);
            while (!stack.empty()) {
                head = stack.pop();
                System.out.println(head.value + " ");
                if (head.right != null) {
                    stack.push(head.right);
                }
                if (head.left != null) {
                    stack.push(head.left);
                }
            }
        }
    }
中序

需要按照左-根-右的方式遍历。

public void nonRecursionPreOrder(Node head) {
      if (head != null) {
          Stack<Node> stack = new Stack<>();
          while (!stack.empty() || head != null) {
              if (head.left != null) {
                  stack.push(head.left);
                  head = head.left;
              }else {
                  head = stack.pop();
                  System.out.println(head.value + " ");
                  head = head.right;
              }
          }
      }
  }
后序

需要按照左-右-根的方式遍历

在先序遍历的时候,是先打印根节点,然后将其右、左孩子分别压栈。我们可以把其改成根-右-左的遍历方式,这很容易做到。但在这个过程中,我们把打印的位置换成一个栈,当程序执行结束后再依次弹出栈,于是便得到了左-右-根的遍历顺序。

public void nonRecursionPostOrder(Node head) {
     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().value + " ");
         }
     }
 }

树的序列化和反序列化

序列化:将树的结构从内存保存到硬盘中。

下图是先序序列化的代码:

Python判断完全二叉树并补全_数据结构


反序列化:读取树的文件加载到内存中,下图的preStr就是从文件中读取的由上一步存储到文件中的字符串。下图是先序反序列化的代码,序列化是怎么做的,反序列换同样怎么做。

Python判断完全二叉树并补全_数据结构_02


Python判断完全二叉树并补全_算法_03

按层序列化

练习题

Python判断完全二叉树并补全_算法_04


分析:

  • 若一个节点X有右子树,则它的后继节点是右子树最左的节点。
  • 若X没有右子树,则它的后继节点有两种情况:
  • 情况1如下图,节点8没有右子树,它是其父节点4的左孩子,因此节点8的后继节点就是4
  • 情况2如下图,节点11没有右子树,它是其父节点的右孩子,这意味着父节点在11节点之前就已经访问过了。继续向上移动,判断节点11的父节点5与节点5的父节点2的关系,如果5是2的右孩子,那么继续上述过程;如果5是2的左孩子,说明2就是11的后继节点。但在下图的例子中,显然5是2的右孩子,因此要看2和其父节点1的关系,2是1的左孩子,因此1就是节点11的后继节点。
  • 需要注意的是,最后一个节点,在本例中为7,是没有后继节点的。
判断一棵二叉树是否为平衡二叉树
判断一棵二叉树是否为搜索二叉树/完全二叉树

**搜索二叉树:**对于每个节点,它的左子树都比它小,右子树都比它大。

二叉树中序遍历后如果是升序的就是搜索二叉树。具体来说,对于前面给出的二叉树中序非递归代码,只要在输出语句那里加上一个判断:这次打印的值是否比上一次的大即可。

完全二叉树:

若用层序遍历:

  • 发现某个节点只有右孩子但没有左孩子,则一定不是完全二叉树
  • 上一条执行后,当第一次发现某节点的孩子不是双全的状态,即两种情况:1. 叶子节点 2. 有左孩子无右孩子。这时要求后面的节点全都要为叶子结点,否则不是完全二叉树。
  • 若程序执行到这里,说明此树为完全二叉树。

代码如下,leaf表示当前节点是否为非双全状态的节点,若为true则意味着之后的节点必须全都是叶子节点。

Python判断完全二叉树并补全_算法_05

求完全二叉树节点的个数,要求时间复杂度低于O(N),N为其节点个数

我们知道,对于高度为h的二叉树,其节点数为2^h-1。

所以执行以下过程:

  • 先向左走到头,得出二叉树的高度,记为h。
  • 然后看看根节点的右孩子的最左子树高度是多少,如果等于h,说明整棵树的左部分肯定是完全二叉树(由完全二叉树的概念可知),那么左部分的节点数就是2^(h-level) - 1,加上根节点就是2^(h-level):

现在要求的就是下图所示的树的节点个数,这是原问题的子问题,可以通过递归求解。

Python判断完全二叉树并补全_算法_06

  • 如果不等于h,说明右子树至少在level-h-1的高度是满二叉树,同样通过公式求解,别忘了把根节点加上:

Python判断完全二叉树并补全_数据结构_07


而左侧又是原问题的子问题,同样可以通过递归求解。

代码如下,h代表树的总高度。1 << (h-level)的意思是 2的(h-level)次方,它对应着上面说的根节点的右子树的最左子树高度等于树的高度h的情况,此时整个左子树是一个满二叉树,节点个数为2的(h-level)次方-1,再加上根节点,正好是 2的(h-level)次方,即1 << (h-level)。

Python判断完全二叉树并补全_数据结构_08


说明:

mostLeftLevel方法的参数是一个节点和当前的层高度(也就是层数),返回的是这个节点的最左子树的高度,可以借助下图理解。

Python判断完全二叉树并补全_算法_09


现在有这么一棵树,其高度为5,左子树是满二叉树。mostLeftLevel方法会计算出树的高度h为5,然后进入程序的主逻辑:

  • 第一个if是看当前节点的右孩子的最左子树高度是多少,由于是从当前节点的右孩子开始向左遍历,因此使用mostLeftLevel计算树的高度时应该将当前节点的高度1也算上,所以level+1。如果算出来的结果与h相等,说明当前节点的左子树一定为满二叉树,使用公式计算即可,别忘了把当前节点也加上。

Python判断完全二叉树并补全_数据结构_10

  • 接着开始下一轮递归,现在的树变成了这样:

Python判断完全二叉树并补全_数据结构_11


注意这时root的level由1变成了2,所以在用其右孩子计算最左子树的高度时,mostLeftLevel中level参数应该传2 + 1,算出来仍然等于h,即5。说明当前root的左子树为满二叉树,再次使用公式计算。

  • 再一次递归,root的level变成了3,所以mostLeftLevel的参数应该传4,这次root节点的右子树的最左子树和h不相等,这说明root的左侧可能不是满二叉树,当然也可能是。但至少能说明root的右子树是h-level-1的满二叉树,因此用公式求出root右子树的节点个数+root节点。

Python判断完全二叉树并补全_数据结构_12

  • 再一次递归,此时root的level为4,所以mostLeftLevel的参数应该传4,求出结果为5,说明root的左子树是满二叉树,仍然使用上面的公式计算节点数。

Python判断完全二叉树并补全_数据结构_13

  • 最后一次递归,只剩下了最后一个节点,其level为5,和h相等。说明是叶子节点,因此返回1。

时间复杂度:对于每一层,每次只选其中一个节点进行操作,一共有logN层,所以要选logN个节点。对于每个节点,每次都需要看看其右孩子的最左子树高度,这需要logN的时间,因此总的时间复杂度是O(logN * logN)