关于二叉树算法和数据结构

291 阅读6分钟
1. 数据结构

二叉树是由根节点和左子节点,右子节点构成的数据结构,左子节点和右子节点不一定存在。

// 这就是二叉树在Java语言中的定义
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
  }
2. 二叉树常规的遍历
  • 前序遍历
    头节点->左子节点->右子节点
  • 中序遍历
    左子节点->头节点->右子节点
  • 后序遍历
    左子节点->右子节点->头节点
  • 层遍历
    一般采用循环和队列来实现

前序遍历,中序遍历,后序遍历采用的都是递归序的方式,每个节点在遍历过程中都会经历三次。

//递归的方法体
private void f(TreeNode root){
  if (root == null) return;
  pre(root.left);
  pre(root.right);
}

二叉树

结合 f 方法和上图做如下说明:  
11 节点进f方法:1
2,程序告诉它走左边:2
3,程序告诉它走左边:4
4,程序告诉它走左边:null,返回 4
5,程序告诉它走右边:null,返回 4
6,返回到:2
7,程序告诉它走右边:5
8,程序告诉它走右边:null,返回 5
9,程序告诉它走右边:null,返回 5
10,返回 2
......  
如此得到的结果:1 2 4 4 4 2 5 5 5 2 1 3 6 6 6 3 7 7 7 3 1
前序遍历结果就是将第一次出现的数连起来;1 2 4 5 3 6 7
中序遍历结果就是将第二次出现的数连起来;4 2 5 1 6 3 7
后序遍历结果就是将第三次出现的数连起来;4 5 2 6 7 3 1

关于层遍历,从队列头取出节点后打印,然后将该节点的左右节点依次放到队列中。最终结果就是层打印的结果

3,二叉搜索树

二叉搜索树是左节点的值比根节点的值小,右节点的值比根节点的值大。二叉搜索树的中序遍历出来的序列是递增序列。

4. 常见算法
  1. 寻找两个节点最近的祖先节点

    递归查找,根据搜索二叉树的原理可知,当两个节点一个比根节点小一个比根节点 大,则公共的祖先节点为根节点,因为2个节点分在树的2边;当搜索的2个节点其中一个就 是根节点,则公共最近的祖先节点就是该节点。如果以上条件不满足,当2个节点都比根节 点小则向左子树递归;当2个节点都比根节点大则向右子树递归。

  2. 二叉搜索树单节点(k)剔除,剔除范围外[l,r]的节点

    单节点剔除,可以根据二叉搜索树特性,判断递归向左节点还是向右节点。当找到节点时,如果节点的左节点==null返回右节点,反之亦然;当左右节点都存在则将左子树挂在右子树的最后一个左子节点上,返回右子树。 范围节点剔除,如果root节点的值比高范围(r)还大,说明返回的树在左子树上,因为二叉搜索树的特性右子树的值一定比根节点的值大,则右子树的节点值一定在高范围(r)外;如果root节点的值比低范围(l)还小,说明返回的树在右子树上,因为二叉搜索树的特性左子树的值一定比根节点的值小,则左子树的节点值一定在低范围(l)外。如果根节点的值在范围内,则继续进行递归。

  3. 根据二叉树的前序,后序或前序,中序或中序,后序遍历的结果构造二叉树

    • 前序,后序构造二叉树

    假如前序遍历的数组为pre,后序遍历的数组为post。
    1,确定待构建的二叉树根节点为pre[0]。
    2,确定左子树范围,假设左子树存在,左分支的头节点为 pre[1],但左子树的头节点也出现在左分支 > 的后序遍历最后。假定左子树有L个节点,则出现等式 pre[1] = post[L-1],可以用循环post来确定L的 值。左子树节点在pre[1 : L+1] 和 post[0 : L]中,按上续步骤依次递归。
    3,确定右子树的范围,右子树节点在pre[L : N] 和 post[L+1 : N-1]中,按上续步骤依次递归,N为数的 长度。

    public TreeNode constructFromPrePost(int[] pre, int[] post) {
      if (pre.length == 0){
          return null;
      }
      TreeNode root = new TreeNode(pre[0]);
      if (pre.length == 1){
          return root;
      }
      //查找左子树的长度
      int leftL = 0;
      for (int i = 0; i < post.length; i++) {
          if (post[i] == pre[1]){
              leftL = i + 1;
              break;
          }
      }
      root.left = constructFromPrePost(Arrays.copyOfRange(pre,1,leftL+1),
                                      Arrays.copyOfRange(post,0,leftL));
      root.right = constructFromPrePost(Arrays.copyOfRange(pre,leftL + 1,pre.length),
              Arrays.copyOfRange(post,leftL,post.length-1));
    
      return root;
    }
    
    • 前序,中序构造二叉树

    假如前序遍历的数组为pre,中序遍历的数组为in。
    1,待构建二叉树的根节点为pre[0]
    2,确定左子树范围和右子树的范围,可以通过根节点的值在in数组中找到根节点在后序遍历的位置,因为有 中序遍历数组,在中序遍历数组中根节点的左侧为左子树,右侧为右子树。

        public TreeNode constructFromPreIn(int[] pre, int[] in) {
        
          if (preorder.length == 0){
              return null;
          }
          TreeNode root = new TreeNode(preorder[0]);
          int inorderRootIndex = 0;
          for (int i = 0; i < inorder.length; i++) {
              if (preorder[0] == inorder[i]){
                  inorderRootIndex = i;
                  break;
              }
          }
          root.left = constructFromPreIn(Arrays.copyOfRange(preorder,1,inorderRootIndex + 1),
                  Arrays.copyOfRange(inorder,0,inorderRootIndex));
          root.right = constructFromPreIn(Arrays.copyOfRange(preorder,inorderRootIndex + 1,inorder.length),
                  Arrays.copyOfRange(inorder,inorderRootIndex + 1,inorder.length));
          return root;
    
    }
    
  4. 完全二叉树

    • 说明

    除最后一层外,其他各层的节点都是全的,最后一层的节点都集中在左侧。 完全二叉树 非完全二叉树,最后一层节点不是靠左

    • 判断二叉树是否为完全二叉树可以用层遍历和标记节点序号2种方式

    层遍历,对二叉树进行层遍历,如果遇到null节点则标记为最后一个节点,继续层遍历,如果在后续中 存在不为null的节点则认为非完全二叉树。

    public boolean isCompleteTree(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        boolean isLastNode = false;
        while (!queue.isEmpty()) {
            TreeNode treeNode = queue.poll();
            //已经挑出了最后一个节点,但是还存在后续节点
            if (treeNode != null && isLastNode) {
                return false;
            }
            //如果节点为null,则认为是最后一个节点
            if (treeNode == null) {
                isLastNode = true;
                continue;
            }
            queue.offer(treeNode.left);
            queue.offer(treeNode.right);
        }
        return true;
      }
    

    标记节点序号,如果为二叉树的节点依次标记为自增整数时,会发现如下图 假如,我们定义root节点的序号为rootCode,左节点的序号为 lCode,右节点的序号为 rCode。会出现 等式关系 lcode = rootCode * 2 rCode = rootCode * 2 +1。 切入正题,如果完全二叉树,节点的数量等于最后一个节点的序号。如上图,节点数量是5,最后一个节 点的code也是 5。

    public boolean isCompleteTree1(TreeNode root) {
        List<ANode> aNodes = new ArrayList<>();
        //默认1号节点的序号为1
        aNodes.add(new ANode(1,root));
        //统计用的,用来确定节点的个数
        int i = 0;
        while (i < aNodes.size()){
            ANode aNode = aNodes.get(i);
            if (aNode.treeNode != null){
                //为节点包装序号
                aNodes.add(new ANode(aNode.code * 2,aNode.treeNode.left));
                aNodes.add(new ANode(aNode.code * 2 + 1,aNode.treeNode.right));
            }
            i++;
        }
        //获取最后节点的序号
        return aNodes.get(i -1).code == aNodes.size();
    }
    class ANode {
        int code;
        TreeNode treeNode;
    
        public ANode(int code, TreeNode treeNode) {
            this.code = code;
            this.treeNode = treeNode;
        }
      }