完全二叉树的创建,遍历与查找

1:如何创建完全二叉树?

1.1完全二叉树的基本特性

  • 1:n0和n2之间的关系:n0即为子节点为0的节点个数,n1即为子节点为1的节点个数,n2即为子节点为2的节点个数.很显然,总节点个数n = n0 + n1 + n2 等式1,然后我们找第二个等式,我们发现,树中所有连接线的个数为n-1,这是因为除了root的节点外,所以节点有且仅有一个父节点过来的连接线,而在n0和n1和n2角度来说,所以的连接线个数为n1+ 2*n2,这就是定义n1和n2的基本特性。所以有n-1= n1 + n2*2结合等式1,得到;n1 + 2*n2 = n0 + n1 + n2 -1;得到n2 = n0 -1;也就是所有,有两个子节点的节点个数等于叶子节点个数 -1.
  • 2:节点在数值位置之间的关系:首先假设节点个数为n,则我们分析知道,若构成完全二叉树,则只有前一般的节点能从当父节点,后一半的节点都是叶子节点。所有假设0到n的节点中,如果第i个节点,处于前一半即拥有子节点,则其左子节点为2*i+1,右子节点为2*i+2,当然这些子节点没有超过总节点的长度。

1.2创建完全二叉树

    //List和ArrayList的区别?用来存放所有Node的List
    public static List<Node> list=new ArrayList<Node>();    
    //由数组建树的方法
    public void creatTree(int[] array){
        //将数组改装成Node,放入list中,以方便后面创建完全二叉树
        for(int i=0;i<array.length;i++){
            Node btnode =new Node(array[i],null,null);
            list.add(btnode);
        }
        //首先在list的长度 大于1的情况下进行
        //完全二叉树,假设所有Node个数为n,则前n/2个Node都是父节点,后一半都是叶子节点,没有子节点
        //所以在创建二叉树中,只需要遍历0到n/2-1即,前一半数据,为他们在其子节点位置添加对应的lchild和rchild即可!
        //同时我们直到完全二叉树的假设该节点在数组的位置为i,且i<n/2,则其lchild节点为2i+1.rchild节点为2i+2.
        //当然,2i+1和2i+2都应该小于n才行!
        if(list.size()>0){
            //for循环比较巧妙,注意for循环的次数,i代表的是每一个带有孩子的结点,0代表的是根节点
            //长度为n的数组,0到n/2-1(包括n/2-1位置)为前一半数据
            for(int i=0;i<array.length/2;i++){
                if(2*i+1<list.size())//这里不能等于,原因是list的长度必须大于2*i+1不然会数组越界
                    list.get(i).setLchild(list.get(2 * i + 1));
                if(2*i+2<list.size())// 这里也不能等于,原因是数组会越界
                    list.get(i).setRchild(list.get(2 * i + 2));
            }
        }
    }

利用前面分析的特性2,可以通过针对对前一半的节点进行循环,依次判断其左子节点和右子节点是否依然存在,若存咋则连接上,否则null的方式来创建一个二叉树。

2:如何操作二叉树?

遍历二叉树;是一个基本的操作针对树这种结构来说,数组,链表,队列,stack等基本的线性结构的遍历很容易实现,但是在二叉树中,这是一个递归的结构,每一个节点又可以充当root进行遍历左右子节点,所以我们遍历二叉树中也采用递归遍历的方式。常规采用的方式有三种,前序遍历,中序遍历和后序遍历不同遍历的方区别在于,访问本节点的顺序,实在递归左子节点之前,之后,还是最后,下面用图来表示。

2.1前序遍历

 前序遍历;在向子节点遍历之前就已经遍历过本节点了,在本程序遍历本节点就相当于打印出来即可;在程序中,我们也可以看到,在向下递归之前就已经遍历本节点了。

    //前序遍历
    /**
     * 前序遍历的操作;先遍历根节点,在遍历左节点,再右节点,
     * 不断循环遍历即可,可采用递归的思想来遍历
     * @param root 待遍历二叉树的根节点
     */
    public void preOrder(Node root){
        //退出递归条件,若当前节点为null,
        //则return;
        if(root == null){
            return ;
        }
        //否则则前遍历本节点,即直接打印
        else{
            System.out.println(root.val);
        }
        preOrder(root.lchild);//先遍历左子树
        preOrder(root.rchild);//再遍历右子树
    }

2.2中序遍历

 

 

    //中序遍历
    public void inOrder(Node root){
        //退出递归条件,若当前节点为null,
        //则return;
        if(root == null){
            return ;
        }
        inOrder(root.lchild);//先遍历左子树
        System.out.println(root.val);//再输出本节点
        inOrder(root.rchild);//再遍历右子树

    }

我们发小中序遍历和前序遍历在程序上,只是调节了

        inOrder(root.lchild);//先遍历左子树
        System.out.println(root.val);//再输出本节点
        inOrder(root.rchild);//再遍历右子树

 顺序,先遍历左子树,然后再本节点,或者先右子树,然后再本节点。

2.3后序遍历

 

2.4按层遍历

按层遍历二叉树需要借助大量的集合类,用来缓存每一层的所有兄弟类,因为兄弟类之间没有指针指向,所以必须一次性都存入集合中,然后再依次进行遍历,当然再遍历后也要将该点的下一层节点也存入集合依次循环。

    //按层遍历二叉树
    public List<Integer> levelOrderBottom(TreeNode root) {
        List<Integer> result = new LinkedList<Integer>();
        if(root == null)
            return result;
        LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()) {
            int levelSize = queue.size();//记录每层的元素个数,
            for(int i = 0; i < levelSize; i++) {//按层循环,循环次数为每层的元素个数
                TreeNode node = queue.poll();//访问完后从队列删除,保证queue.size()等于每层的元素个数
                if(node.left != null) queue.offer(node.left);
                if(node.right != null) queue.offer(node.right);
                result.add(node.val);//第一个参数为index,从链表头插入
            }
        }
        return result;
    }

这里的方法是通过记录当前层的节点数来实现按层遍历的,我们可以使用queue的先进先出的特性来解决这个问题,将最近的节点放在后面,通过直接获取的方法可以保证当该层所有节点都获取完成了,才能开始获取下一层的节点,就不用统计每一层节点的个数了。

class Solution {
    public int[] levelOrder(TreeNode root) {
        TreeNode temp = null;
        Queue<TreeNode> queue = new LinkedList<>();
        ArrayList<Integer> ans = new ArrayList<>();
        if(root == null){
            return new int[0];
        }
        queue.add(root);
        while(true){
            //如果当前queue为空,则直接退出即可
            if(queue.isEmpty()){
                break;
            }
            //再queue中取出一个treeNode
            temp = queue.poll();
            //加入ans中,
            ans.add(temp.val);
            //然后判断如果该节点有左右子节点,则依次加入queue。
            if(temp.left!=null){
                queue.add(temp.left);
            }
            if(temp.right!=null){
                queue.add(temp.right);
            }
        }

        int[] res = new int[ans.size()];
        int index = 0;
        //把LinkedList转化成array,以供输出来用。
        for(Integer i:ans){
            res[index++] = i;
        }

        return res;
    }
}

2.5查找节点

//前序遍历查找节点,在树A中,查找节点B
public TreeNode serachNode(TreeNode A, TreeNode B){
TreeNode temp;
if(A == null)//如果A为null,这及return,
return null;
if(A.val == B.val){
return A;
}
else {
temp = serachNode(A.left, B);
if(temp ==null){
temp = serachNode(A.right, B);
}
return temp;
}
}

 

2.5判断二叉树

判断以节点A和节点B为根的两颗二叉树中,B是不是A的子树?我们采用递归的思想进行判断。递归三要素如下:

  1. 递归结束条件:这里递归结束条件要考虑全,首先如果B为null,意味着在递归过程中B已经进入了null节点,则应该接受此次递归,向其他分支递归。如果A==null且B!=null则意味着,A判断完了,但是B还没有判断完全,所以此时应该接受递归,B不可能是A的子树,因为同样位置下A!=B.
  2. 结束情况处理:如果是进入B的null节点,则应该接受此次递归,向其他分支递归。如果是此次递归不成功,则应返回结束所有递归即可!
  3. 递归逻辑:如果当前node.val相等,则
    //判断是否是子树,
    //采用递归的方式来判断是不是子树
    //递归结束条件:B为null意味着此B的子节点为null,应该返回true,以进行其他子节点的判断。
    //递归结束条件:A为null且B不为null,则意味着找遍了A也没有实现其匹配,return false
    //递归逻辑:如果此时A.val == B.val则递归下去,直到上面两个情况发生!
    public boolean issub(TreeNode A, TreeNode B){
        if(B == null){
            return true;
        }
        if(A == null && B != null){
            return false;
        }
        if(A.val == B.val){
            //只有当A和B的左右节点都相等才能 return true。
            return issub(A.left,B.left) && issub(A.right,B.right);
        }
        return false;
    }

 

posted @ 2020-03-13 10:03  大朱123  阅读(944)  评论(0编辑  收藏  举报