Java数-数的基础

2018-10-24  本文已影响32人  三季人
数的特点
  1. 每个节点有0个或者多个子节点。
  2. 没有父节点的节点叫做根节点。
  3. 每一个非根节点有且只有一个父节点。
  4. 除了根节点,每一个子节点可以分为多个不相交的子树。
数的基本术语

若一个结点有子树,那么该结点称为子树根的"双亲",子树的根是该结点的"孩子"。有相同双亲的结点互为"兄弟"。一个结点的所有子树上的任何结点都是该结点的后裔。从根结点到某个结点的路径上的所有结点都是该结点的祖先。

节点的度: 节点拥有的子树的数目。
叶子节点: 度为0的节点。
分支节点:度不为0的节点。
树的度:树中节点最大的度。
层次:根节点的层次为1。其余节点的层次为,该节点的双亲的层次加上1。
树的高度:树中节点最大的层次。
无序树:树中节点的各个子树的次序不重要,可以交换次序。
有序树:树中节点的各个树的次序重要,不可交换次序。
森林:由多个不相交的树组成,给深林加上一个根,就变成树,删除根,树就变成森林。

二叉树的定义

二叉树是每个节点最多有2个子树的结构。二叉树可以是空集。他的五种基本形态如下:


image

注意: 根节点的深度是0,高度是1。

二叉树的性质

二叉树有一下几种性质:

  1. 二叉树第i层上的节点数最多为2^(i-1) (i>=1)。
  2. 深度为k的二叉树,节点最多为2^k -1个节点 (k>=1)。
  3. 包含n个节点的二叉树的高度至少为log_2(n+1)。
  4. 在任意一棵二叉树中,若叶子结点的个数为n0,度为2的结点数为n2,则n0=n2+1。
满二叉树

定义:高度为h,由2^n -1 个节点组成的树,成为满二叉树。

image
完全二叉树

定义: 一棵树中,只有最下面两层节点的可以小于2,并且,最下一层叶子节点集中在靠左的节点上。

特点: 叶子节点只能出现在最下层和次下层。且最下层的叶子节点集中在数的左部。很显然,满二叉树也是完全二叉树的一种,而完全二叉树不一定是满二叉树。

image
二叉查找树

定义:二叉查找树(Binary Search Tree),又被称为二叉搜索树。设x为二叉查找树中的一个结点,x节点包含关键字key,节点x的key值记为key[x]。如果y是x的左子树中的一个结点,则key[y] <= key[x];如果y是x的右子树的一个结点,则key[y] >= key[x]。

二叉查找树的特点:

二叉树的Java实现

public class BSTree<T extends Comparable<T>> {

    private BSTNode<T> mRoot;    // 根结点

    public class BSTNode<T extends Comparable<T>> {
        T key;                // 关键字(键值)
        BSTNode<T> left;      // 左孩子
        BSTNode<T> right;     // 右孩子
        BSTNode<T> parent;    // 父结点

        public BSTNode(T key, BSTNode<T> parent, BSTNode<T> left, BSTNode<T> right) {
            this.key = key;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }
    }

        ......
}
遍历

这里讲解前序遍历、中序遍历、后序遍历3种方式。

前序遍历
若二叉树非空,执行以下操作:

  1. 访问根节点
  2. 先序遍历左子树
  3. 先序遍历右子树

递归实现前序遍历:

/**
     * 二叉树前序遍历
     * 递归实现
     */
    public void preorderTraversalRecursion(BSTNode<String> rootNode){

        if(rootNode==null) return;

        //先添加根节点
        list.add(rootNode);
        //继续左边遍历添加
        preorderTraversalRecursion(rootNode.left);
        //继续右边的添加
        preorderTraversalRecursion(rootNode.right);

    }

非递归,利用栈实现二叉查找树的前序遍历:

/**
     * 二叉树前序遍历
     * 非递归实现
     */
    public  <T extends Comparable<T>> List<BSTNode<T>> preorderTraversal(BSTNode<T> rootNode){

        if(rootNode==null) return null;

        List<BSTNode<T>> list=new ArrayList<>();
        //利用栈stack存储
        Stack<BSTNode<T>> stack=new Stack<>();
        stack.push(rootNode);

        //遍历stack
        while (!stack.isEmpty()){
            //取出根节点,并且删除
            BSTNode<T> treeNode=stack.pop();
            if(treeNode==null) continue;

            //先保存根节点
            list.add(treeNode);

            //注意stack栈是先进后出,所以先推入右边的

            //将右节点推入stack栈
            if(treeNode.right!=null)
                stack.push(treeNode.right);
            //将左节点推入stack栈
            if(treeNode.left!=null)
                stack.push(treeNode.left);
        }
        return list;
    }
中序遍历

若二叉树为非空执行以下操作:

  1. 中序遍历左子树
  2. 访问根节点
  3. 中序遍历右子树

递归实现中序遍历代码:

/**
     * 二叉树中遍历
     * 递归实现
     */
    public void preorderTraversalRecursion(BSTNode<String> rootNode){

        if(rootNode==null) return;

        //继续左边遍历添加
        preorderTraversalRecursion(rootNode.left);

        //先添加根节点
        list.add(rootNode);

        //继续右边的添加
        preorderTraversalRecursion(rootNode.right);

    }

只改变了,中间代码执行的顺序

非递归实现中序遍历:

  /**
     * 二叉树中序遍历
     * 非递归实现
     * 思路:利用栈从根结点,一直将左子树入栈,直到左子树为null,
     * 然后逐个将结点出栈,同时遍历该结点的右子树,
     * 遍历右子树为根的子树时,用同样的方法从根节点,一直将左子树入栈。
     * test:(null),(单结点),(默认二叉树)
     */
    public  <T extends Comparable<T>> List<BSTNode<T>> preorderTraversal(BSTNode<T> rootNode){

        if(rootNode==null) return null;

        List<BSTNode<T>> list=new ArrayList<>();
        //利用栈stack存储
        Stack<BSTNode<T>> stack=new Stack<>();
        BSTNode<T> curr=rootNode;//当前的节点
        stack.push(curr);
        //遍历stack
        while (!stack.isEmpty()){
            //取出根节点
            BSTNode<T>roNode=stack.peek();

            //将该节点的所有左边节点压入栈
            while(roNode.left!=null){

                stack.push(roNode.left);
                roNode=rootNode.left;
            }
            //此时的roNode是树的最左边的节点
            //访问,节点,退出栈
            BSTNode<T>temp=stack.pop();
            list.add(temp);

            //判断这个节点的右边是否有节点,有就先计算这个节点右边右边
            if(temp.right!=null)
                stack.push(temp.right);
        }
        return list;
    }

先保存左节点,再将右节点和根节点分别推入栈中

后续遍历

若二叉树非空,执行以下操作

  1. 后续遍历左字数
  2. 后续遍历右字数
  3. 访问根节点

后续遍历递归实现:

/**
     * 二叉树后续遍历
     * 递归实现
     */
    public void preorderTraversalRecursion(BSTNode<String> rootNode){

        if(rootNode==null) return;

        //继续左边遍历添加
        preorderTraversalRecursion(rootNode.left);
        //继续右边的添加
        preorderTraversalRecursion(rootNode.right);
        //先添加根节点
        list.add(rootNode);
        
    }

后续遍历非递归实现:

 /**
     * 二叉树后续
     * 非递归实现
     * 思路:先将左边节点加入栈,再将右边节点加入栈,最后是根节点
     */
    public  <T extends Comparable<T>> List<BSTNode<T>> preorderTraversal(BSTNode<T> rootNode){

        if(rootNode==null) return null;

        List<BSTNode<T>> list=new ArrayList<>();
        //利用栈stack存储
        Stack<BSTNode<T>> stack=new Stack<>();
        BSTNode<T> curr;//当前的节点
        BSTNode<T>pre=null;//当前节点的上一个节点
        stack.push(rootNode);

        //遍历stack
        while (!stack.isEmpty()){
            //取出根节点
            curr=stack.peek();

            if(curr.right==null||curr.left==null//当前节点是叶子节点,直接访问
                    ||(pre!=null&&(pre==curr.left||pre==curr.right))//当访问到根节点,直接访问
                    ){
                list.add(curr);
                stack.pop();
                pre=curr;
            }else{
                //将左右压入栈
                if(curr.right!=null)
                    stack.push(curr.right);

                if(curr.left!=null)
                    stack.push(curr.left);

            }
            
        }
        return list;
    }
二叉树查找树的 查找

查找二叉树中键值为key的节点
递归版本实现:

/**
     * (递归实现)查找"二叉树x"中键值为key的节点
     */
    public <T extends Comparable<T>> BSTNode<T> search(BSTNode<T> bstNode,T key){

        if(key==null||bstNode==null) return null;

        int cmp=key.compareTo(bstNode.key);
        //查找的数值大于当前节点,继续查找,此节点的右节点
        if(cmp>0){
            return search(bstNode.right,key);
        }else if(cmp<0){
            //查找树小于此节点,继续查找此节点的左节点
            return search(bstNode.left,key);
        }else{
            return bstNode;
        }

    }

查找非递归实现:

/**
     * (递归实现)查找"二叉树x"中键值为key的节点
     */
    public <T extends Comparable<T>> BSTNode<T> iterativeSearch(BSTNode<T> bstNode,T key){

        if(key==null||bstNode==null) return null;

        while (bstNode!=null){
            int cmp=key.compareTo(bstNode.key);
            //查找的数值大于当前节点,继续查找,此节点的右节点
            if(cmp>0){
                bstNode=bstNode.right;
            }else if(cmp<0){
                //查找树小于此节点,继续查找此节点的左节点
                bstNode=bstNode.left;
            }else{
                return bstNode;
            }
        }
        return null;
    }
最大值和最小值

查找最大值代码

/*
     * 查找最大结点:返回tree为根结点的二叉树的最大结点。
     * 思路:返回树的最右边的叶子节点
     */
    private <T extends Comparable<T>>  BSTNode<T> maximum(BSTNode<T> tree) {

        if(tree==null) return null;

        while (tree.right!=null){
            tree=tree.right;
        }

        return tree;
    }

最小值代码

/*
     * 查找最小结点:返回tree为根结点的二叉树的最小结点。
     * 思路:返回树的最左边节点
     */
    private <T extends Comparable<T>>  BSTNode<T> minimum(BSTNode<T> tree) {
        
        if(tree==null) return null;
        
        while (tree.left!=null){
            tree=tree.left;
        }
        
        return tree;
        
    }
前驱和后继

节点的前驱:该节点的左子树中的最大节点。
节点的后继:该节点的右子树的最小节点。

查找前驱节点代码:

/*
     * 找结点(x)的前驱结点。即,查找"二叉树中数据值小于该结点"的"最大结点"。
     *
     */
    public <T extends Comparable<T>> BSTNode<T> predecessor(BSTNode<T> x) {

        if(x==null) return  null;

        //如果节点存在左子树,直接求左子树的最大节点
        if(x.left!=null) return maximum(x.left);

        /**
         * 如果左子树为空,存在两种情况
         * 1. 这个节点是父节点的右节点
         * 这种情况,父节点就是该节点的前驱节点
         * 2. 这个节点是父亲节点的左节点
         *
         * 如果需要找一个比这个节点小的,且最大的节点
         * 需要找到最近的父亲节点P1 ,已经P1的父亲P2,且P1在P2的右边
         *
         * P2<   P1
         * 根据查找二叉树的特点,右边的大于左边的
         * 所以 x>P2
         *
         */

        BSTNode<T>y=x.parent;
        //第一个是当没有父类的时候,停止
        //第二个是当父亲节点刚好是在右边的,停止
        while (y!=null&&x==y.left){

            x=y;
            y=y.parent;
        }

        return y;
    }

后继代码:

/*
     * 找结点(x)的后继结点。即,查找"二叉树中数据值大于该结点"的"最小结点"。
     */
    public <T extends Comparable<T>> BSTNode<T> successor(BSTNode<T> x) {

        if(x==null) return null;

        //如果有右子树,直接找右子树最小的
        if(x.right!=null)
            return minimum(x.right);

        /**
         * 如果右子树为空有两种情况
         * 1. 该节点是父亲节点的左子树
         * 这种情况,后继节点就是父亲节点
         * 2. 该节点是父亲节点的右子树
         *
         * 需要找到父亲节点,的父亲节点,且父亲的父亲的的左节点是父亲节点
         */

        BSTNode<T>y=x.parent;
        while (y!=null&&x==y.right){
            x=y;
            y=y.parent;
        }

        return y;
    }
插入

插入节点的代码

/*
     * 将结点插入到二叉树中
     *
     * 参数说明:
     *     tree 二叉树的
     *     z 插入的结点
     */
    private <T extends Comparable<T>> void insert(BSTree<T> bst, BSTNode<T> z) {

        if(z==null||bst==null||bst.mRoot==null) return;

        BSTNode<T>rot=bst.mRoot;
        BSTNode<T>y=null;//用来存储当前比较的值
        int temp=0;
        //查找插入的位置
        while (rot!=null){
            y=rot;
            temp=z.key.compareTo(rot.key);
            //z比当前的值大,查找右边
            if(temp>0){
                rot=rot.right;
            }else if(temp<0){
                rot=rot.left;
            }
        }
        //z插入到当前比较的值
        z.parent=y;
        //如果y是空的
        if(y==null){
            //一个parent为空的节点是,根节点
            bst.mRoot=z;
        }else{
            //判断是插入在y的左边还是右边
            temp=z.key.compareTo(y.key);
            //在右边
            if(temp>0)
                y.right=z;
            else
                y.left=z;
        }
    }
删除

删除节点存在三种情况:

  1. 没有左右子节点,可以直接删除
  2. 存在左节点或者右节点,删除后需要移动节点
  3. 存在左右节点。不能简单删除,需要通过寻找后继节点,转换为上述两种情况

非递归代码如下:

 /*
     * 删除结点(z),并返回被删除的结点
     *
     * 参数说明:
     *     bst 二叉树
     *     z 删除的结点
     *
     * 1. 左右节点都为空,直接删除
     * 2. 左右节点有一个为空,删除节点,移动子节点到父亲节点下
     * 3. 左右节点都有,删除后继节点,把后继节点的值转移到当前节点。
     */
    private <T extends Comparable<T>> BSTNode<T> remove(BSTree<T> bst, BSTNode<T> z) {
        if(bst==null||z==null||bst.mRoot==null) return null;

        BSTNode<T>x=null;//存储子被删除的节点的子节点child
        BSTNode<T>y=null;//要删除的节点

        //只有一个节点或者没有节点
        if(z.left!=null||z.right!=null){
            //z就是要删除的节点
            y=z;
        }else {
            //有两个节点,删除后继节点
            y=successor(z);
        }

        //获取被删除节点的子节点,无论是在左还是右
        if(y.left!=null)
            x=y.left;
        else
            x=y.right;

        //如果存在子节点,关联子节点和父节点
        if(x!=null){
            x.parent=y.parent;
        }

        //如果父节点为空,说明删除的是根节点
        if(y.parent==null)
            bst.mRoot=z;

        //如果要删除的节点是父节点的左节点,关联左节点
        else if(y==y.parent.left){
            y.parent.left=x;
        }
        //如果要删除的是父亲节点的右节点
        else if(y==y.parent.right){
            y.parent.right=x;
        }

        //如果要删除的节点和刚开始传入的节点不一样就是后继节点
        //需要将值传递过去
        if(z!=y)
        z.key=y.key;

        return y;

    }
打印二叉树

代码如下:

/*
     * 打印"二叉查找树"
     *
     * key        -- 节点的键值
     * direction  --  0,表示该节点是根节点;
     *               -1,表示该节点是它的父结点的左孩子;
     *                1,表示该节点是它的父结点的右孩子。
     */
    private <T extends Comparable<T>>  void print(BSTNode<T> tree, T key, int direction) {

        if(tree==null) return;

        if(direction==0)    // tree是根节点
            System.out.printf("%2d is root\n", tree.key);
        else                // tree是分支节点
            System.out.printf("%2d is %2d's %6s child\n", tree.key, key, direction==1?"right" : "left");

        //打印左节点
        print(tree.left,key,-1);
        //打印右节点
        print(tree.left,key,1);

    }
销毁

销毁二叉树的代码

/*
     * 销毁二叉树
     */
    private <T extends Comparable<T>>  void destroy(BSTNode<T> tree) {

        if(tree==null) return;

        //先删除左子树
        if(tree.left!=null)
            destroy(tree.left);

        //再删除右子树
        if(tree.right!=null)
            destroy(tree.right);

        tree=null;
    }
树的深度/广度优先遍历

树的深度优先遍历需要用到额外的数据结构-> 栈。

深度遍历代码和前序遍历类似。参考上面👆。

树的广度优先遍历需要 队列的辅助。

非递归实现代码:

/**
     * 广度优先遍历
     * 采用非递归实现
     * 需要辅助数据结构:队列
     */
    public <T extends Comparable<T>> void levelOrderTraversal(BSTNode<T> node){

        if(node==null) return;
        ArrayDeque<BSTNode<T>> arrayDeque=new ArrayDeque<>();
        //添加根节点
        arrayDeque.add(node);

        //编列队列
        while (!arrayDeque.isEmpty()){
            //移除根节点
            BSTNode<T>bstNode=arrayDeque.remove();
            // 输出当前节点,或者处理当前节点
            System.out.print(node.key+"    ");

            //推入当前节点的左节点
            if(bstNode.left!=null)
            arrayDeque.add(bstNode.left);

            //推入当前节点的右节点
            if(bstNode.right!=null)
                arrayDeque.add(bstNode.right);
        }
    }

参考

上一篇下一篇

猜你喜欢

热点阅读