二叉树之下

算法学习(七): 二叉树和二叉树搜索

2018-06-29  本文已影响55人  squall1744

树的定义


下面的就是典型的树:

下面这两种就不是树:

二叉树


二叉树
上面的链接是关于二叉树的介绍, 大家可以先通过链接学习二叉树的相关概念

二叉树的属性

二叉树的遍历


二叉树遍历的框架

import java.util.Stack;

public class PreInPosTraversal {

    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    public static void preOrderUnRecur(Node head) {}

    public static void inOrderUnRecur(Node head) {}

    public static void posOrderUnRecur(Node head) {}


    public static void main(String[] args) {
        Node head = new Node(5);
        head.left = new Node(3);
        head.right = new Node(8);
        head.left.left = new Node(2);
        head.left.right = new Node(4);
        head.left.left.left = new Node(1);
        head.right.left = new Node(7);
        head.right.left.left = new Node(6);
        head.right.right = new Node(10);
        head.right.right.left = new Node(9);
        head.right.right.right = new Node(11);

    
        // unrecursive
        System.out.println("============unrecursive=============");
        preOrderUnRecur(head);
        inOrderUnRecur(head);
        posOrderUnRecur(head);
    }
}

非递归实现前序遍历

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);
      }
    }
  }
  System.out.println();
}

非递归实现中序遍历

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;
      }
    }
  }
  System.out.println();
}

非递归实现后序遍历

用两个栈实现后序遍历:


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);
    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 + " ");
    }
  }
  System.out.println();
}



用一个栈实现后序遍历:
这个有兴趣的可以了解下, 平时不太能用的到, 因为用两个栈的空间复杂度跟一个一样, 并且代码简单, 所以通常用两个栈来实现后序遍历就可以了



def postOrderTraversalOneStack(self):
      nodeStack = stack.Stack()
      curNode = self.root
      newNode = None
      while not nodeStack.isEmpty() or curNode != None:
        while curNode != None:
          newNode = NodeWithFlag(curNode, False)
          nodeStack.push(newNode)
          curNode = curNode.left

        newNode = nodeStack.pop()
        curNode = newNode.node
        if not newNode.flag:
          newNode.flag = True
          nodeStack.push(newNode)
          curNode = curNode.right
        else:
          print(curNode.key)
          curNode = None

二叉树相关算法


得到一个二叉树节点的后继节点

有一个二叉树, 给定一个节点, 找出他的后继节点,
这个二叉树的节点有个特殊的地方, 每个节点不但有指向左孩子的left和右孩子的right, 还有一个指向父节点的parent,在二叉树的中序遍历的序列中, node的下一个节点叫做node的后继节点

分析:
这里先要给出、几个概念:

算法流程:

public SuccessorNode {

    public static class Node {
        public int value;
        public Node left;
        public Node right;
        public Node parent;

        public Node(int data) {
            this.value = data;
        }
    }

    public static Node getSuccessorNode(Node node) {
        if (node == null) {
            return node;
        }
        if (node.right != null) {
            return getLeftMost(node.right);
        } else {
            Node parent = node.parent;
            while (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;
    }
}

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



二叉树序列化的方法如下:

  1. 先规定按某个方式遍历, 遇到元素时,保存元素的值, 并用下滑下_隔开, 如:
    我们遍历第一个元素为1, 第二个为23, 则记录方式为1_23_
  2. 遍历某个元素的左节点时, 如果其左节点为空, 用#符号记录, 同样的遍历到右节点时, 若右节点为空, 也用#记录
  3. 整个二叉树遍历完后记录的字符串结构即为序列化结果




我们还可以利用按层遍历二叉树来序列化:

  1. 准备一个队列, 在把head加入队列前先打印head的值,比如head的值为1, 我们记录为1_
  2. 从队列弹出一个数, 打印head的左孩子和右孩子的值, 然后把左孩子和右孩子存入队列中, 如果某个孩子不存在, 仅打印#_, 无需存入队列
  3. 继续从队列中弹出一个数, 然后继续打印head左孩子的左孩子和右孩子的值,并存入队列, 跟上面一样如果没有就仅打印#_
  4. 继续从队列弹出一个数, 打印head右孩子的左孩子和右孩子的值,并存入队列,如果没有就打印#_
  5. 重复弹出一个数, 然后打印下一个元素的左右孩子, 存入队列, 没有就打印#_, 直到队列中所有元素都弹出为止




按层遍历的反序列化:

序列化和反序列化的代码比较简单, 请大家自己试着写出来吧

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

此类问题的套路:
适用情况为考察每一个节点为头的整棵子树
利用递归来解决问题

  1. 先写主函数框架
public static boolean isBalance() {
  ...
}
  1. 分析我们需要得到的数据
    判断一棵树是否是平衡二叉树需要满足两点: 左子树和右子树都是平衡二叉树, 左子树和右子树的高度差不大于1。 为了判断是否平衡二叉树, 我们需要得到以下数据:
    • 左子树是否平衡二叉树
    • 右子树是否平衡二叉树
    • 左子树的高度
    • 右子树的高度
  2. 整合信息,定义抽象数据类型(ADT)
    进一步抽象数据, 把所有数据整合, 设计返回整合后数据的抽象数据类型
    我们发现如果不考虑左子树和右子树, 对于每一个节点,我们都需要两个数据: 以该节点为根的二叉树是否为平衡二叉树, 节点的高度。我们可以用一个抽象数据类型来获得这两个数据, 这道题的情况是左右刚好需要的数据是一样的, 如果左右子树需要的数据不一样, 我们把所有需要的数据都整合成一个ADT, 在递归时对左右子树返回其各自需要的数据即可
    • isBalance 布尔类型
    • height 整型
public static class ReturnData {
  public boolean isBalance;
  public int height;
  public ReturnData(boolean, isBalance, int height) {
    this.isBalance = isBalance;
    this.height = height;
  }
}
  1. 设计递归函数
    先确定简单情况
    设计递归时, 默认左树返回所需的信息, 默认右树也返回所需的信息, 然后自身也同样返回相同的所需信息

通过递归先得到所有节点的左子树和右子树是否为平衡二叉树以及他们的高度, 并根据这些数据判断以该节点为根的二叉树是否为平衡二叉树, 层层往上, 最终就能得到整个二叉树是否为平衡二叉树

public static ReturnData process(Node node) {
  //简单情况
  if (node == null) {
    return new ReturnData(true, 0);
  }
  //默认左子树返回isBalance和height
  ReturnData leftData = process(node.left);

  //默认右子树返回isBalance和height
  ReturnData rightData = process(node.right);

  //自身判断并返回相同的所需信息
  int height = 0;
  boolean isBalance = true;
  if (!leftData.isBalance || !rightData.isBalance ) {
    isBalance = false;
  }
  if (Math.abs(leftData.height - rightData.height) > 1) {
    isBalance = false;
  }
  height = Math.max(leftData.height, rightData.height) + 1
  return new ReturnData(isBalance, height)
}
  1. 在主函数中调用递归函数
public static boolean isBalance() {
  return process(head).isBalance;
}

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

思路:
按层遍历

  1. 如果某个节点有右孩子没有左孩子, 则不是完全二叉树
  2. 如果某个节点k左右孩子不全, 则他之后的节点必须都是叶节点
  public static boolean isCBT(Node head) {
    if (head == null) {
      return true;
    }
    Queue<Node> queue = new LinkedList<Node>();
    boolean leaf = false; // 触发检测k之后节点全是叶节点的开关
    Node left = null;
    Node right = null;
    queue.offer(head);
    while (!queue.isEmpty()) {
      head = queue.poll();
      left = head.left;
      right = head.right;
      if ((left == null && right != null) || (leaf && (left !=null || right != null))) {
        return false;
      }
      if (left == null && right == null) {
        leaf = true;
      }
      if (left !=null) {
        queue.offer(left);
      }
      if (right != null) {
        queue.offer(right);
      }
    }
    return true;
  }

已知一棵完全二叉树, 求其节点的个数

要求: 时间复杂度低于O(n), N为这棵树的节点个数

// 保证head是一棵完全二叉树
public static int nodeNum(Node node) {
  if (node == null) {
    return 0;
  }
  return bs(head, 0, mostLeftLevel(head, 0));
}

public static int bs(Node node, int i, int allLevel) {
  if (i == allLevel) {
    return 1;
  }
  if (mostLeftLevel(node.right, i + 1) == allLevel) {
    return (1<<(allLevel - i)) + bs(node.right, i + 1, allLevel);
  }else {
    return (1<<(allLevel - i - 1)) + bs(node.left, i + 1, allLevel);
  }
}

public static int mostLeftLevel(Node node, int i) {
   while (node != null) {
    i++;
    node = node.left;
  }
  return i - 1;
}
上一篇下一篇

猜你喜欢

热点阅读