"二叉树"-实现数据结构算法,完全解析,通俗易懂的图文及代码讲解

二叉树的完全理解


1.概念:
1.每个节点(除了根节点)都有父节点
2.每个节点最多有两个节点

2.优点:
1.索引:提高查询效率,就是B树索引(Binary Tree)
2.比如全国十几亿个人的姓名和身份证,有同名的,想找一个人的信息,如果从头到尾逐条就太慢了.按正规来讲,在数据库中每个记录都有uid,给名字就找到对应的uid,1234...然后就可以找到同名字的各个身份证号;
3.因为二叉树是排列好的,所以可以直接确定在哪个位置.

3.二叉树的排列方式

无论哪一种,左节点肯定在右节点前面,”某”序根据的是”根”节点所在位置:
这里写图片描述
1.前序:(根节点在”前”)

    根->左->右
    7->3->1->5->9->11->21

2.中序:(根节点在”中”)

    左->根->右
    1->3->5->7->11->9->21

3.后序:(根节点在”后”)

    左->右->根
    1->5->3->11->21->9->7

4.层序(从上到下按层级)

    最简单的分层
    7->3->9->1->5->11->21
现在我们要实现一个标准的二叉树:1.有序排列的(中序),2.无重复元素的

一棵大树就一个根:root
一个节点的构成有:4部分

1.parent:父节点
2.Object:数据
3.left:左节点
4.right:右节点

节点属性图:
这里写图片描述


节点连接图:
这里写图片描述

添加数据方法add(Object data):

1.如果已经包含了,就不加入了,所以①写一个判断是否的方法.

接着:
1.创建节点;
2.给节点放入数据数据;
3.节点放入二叉树
4.如果根节点为空,根节点就等于这个节点;

如果不存在,就看应该放在哪个位置. 找它的父节点,所以②写一个找父节点的方法.
1.从根节点开始找,用传入的数据和根节点的数据比较
2.比根节点数据大就继续右边查找,小就是左边查找
3.比较"大小"注意点:是否实现了比较器 data instantsof Compale
        实现了就强转为Comparable c1
        没实现: c1 = data.toString();对比他们的字符方法,因为toString实现了;
        最后:根据 compareTo得出那个大.

以上是完成数据的添加方法的逻辑.


接下来是打印的方法:(重点讲一下,因为涉及到递归)

打印的方法,因为是中序所以左->中->右打印
see(Note root){
note!=null;
//是个递归的过程,重要,相当于打完”每一个”点的左边才到中间才到右边
see(root.left);
system.out.println(root);
see(root.right);
}
相当于: 根的左->根的左->根的左然后才递上来,递归过程挺清晰的;
因为是中序遍历,所以左中右

接下来讲一下移除的方法,也是该算法的重点和难点

只要掌握以下几点要素即可完全理解

移除的点我们分为两种:
1.该点是根节点
        1.根节点没左儿子,也没右儿子
            结论:那么根就为空了,因为就一个点
        2.根节点只有左儿子
            结论:左儿子就继位了,根赋值为左儿子
        3.根节点只有右儿子
            结论:右儿子继位了,根赋值为右儿子
        4.根节点同时有左右儿子
            (可以选择左儿子继位,也可以选右儿子,我们随便选个就选左儿子吧)
            结论:左儿子继位,那么根据中序的原理:右儿子就变成了左儿子的最右边的节点:

2.该点是非根节点
        1.此节点没左儿子,也没右儿子
        2.此节点只有左儿子
        3.此节点只有右儿子
        4.此节点同时有左右儿子

存在左右儿子,选左儿子继位图:
这里写图片描述

下面给出详细代码

public class MyTree {
    //节点的属性
    private class Node {

        Node   parent;//父节点
        Object data;//数据
        Node   left;  //左节点
        Node   right; //右
    }

    //根
    private Node root;

    public void add(Object data) {
        //存在就不添加
        if (contians(data)) {
            return;
        }
        //建一个点
        Node node = new Node();
        node.data = data;

        if (root == null) {//根是空,该点就是根,说成根就是该点更合适
            root = node;
        } else {
            //判断是否存在
            //有节点,那就:1.找到该节点的应该的父节点,2.找到了,比较判断该值和父节点的值,应该放左还是右
            Node parent = findPrent(data, root);
            node.parent = parent;//赋值再双向,确定父节点地址
            if (compare(data, parent.data)) {
                //大于就放在右边
                //node.parent = parent.right;//赋值再双向,这个是错误的,等于parent 的地址,不用加上right
                parent.right = node;
            } else {
                parent.left = node;
            }
        }

    }

    /**
     * 包含就不添加
     */
    private boolean contians(Object data) {
        //查找到点就说明包含
        Node node = findNode(data);
        return node != null;
    }

    //找到该点的父节点
    private Node findPrent(Object data, Node node) {
        //从以该点为根开始找
        Node parent = null;
        Node temp = node;
        while (temp != null) {
            //先等于当前点,然后当左右都为空的时候就找到了
            parent = temp;
            if (compare(data, temp.data)) {
                //比父节点大,继续右边找
                temp = temp.right;
            } else {
                temp = temp.left;
            }

        }
        return parent;
    }

    /**
     * compare只比大小,不比相等,这里相等就是当小于
     * 要能比较字符串和数字
     * 相等就不要添加
     * 然后看ture还是false放左右
     */
    private boolean compare(Object data1, Object data2) {
        Comparable c1;
        Comparable c2;
        if (data1 instanceof Comparable) {//看看是否传的是实现比较器的,是就转换
            c1 = (Comparable) data1;
            c2 = (Comparable) data2;
        } else {
            c1 = data1.toString();//否则就获取他们的toString();因为toString已经实现了Comparable比如数字,字符串都能比
            c2 = data1.toString();
        }
        int i = c1.compareTo(c2);
        return i > 0;
    }

    @Override
    public String toString() {
        return print();
    }

    /**
     * 打印方法,要采用递归,同汉诺塔原理
     * 中序
     */
    public String print() {
        StringBuffer sb = new StringBuffer();
        see(root, sb);
        String s = sb.toString();
        if (s.length() <= 1) {
            return "[" + s + "]";
        }
        s = s.substring(0, s.length() - 1);
        return "[" + s + "]";
    }

    //把根点放入进去
    private void see(Node root, StringBuffer sb) {
        if (root != null) {
            //先打印左
            see(root.left, sb);
            //再打印中
            //System.out.println(root.data);
            sb.append(root.data + ",");
            //再打印右边
            see(root.right, sb);
        }
    }

    /**
     * 移除方法
     * 判断删除的是根节点还是非根节点
     * 然后分别判断该点有无节点;只有左,还是只有右,还是都有,再双向判断
     */
    public void remove(Object data) {
        Node node = findNode(data);
        if (node != null) {//存在才去做
            //删的是根节点
            if (node.parent == null) {//等同node==root;
                //没有子节点
                if (node.left == null && node.right == null) {
                    root = null;//根就就空了
                }
                //该根节点只有左节点无右
                else if (node.right == null) {
                    node.parent = null;
                    root = node.left;//根就变左节点
                }
                //该根节点有右节点无左
                else if (node.left == null) {
                    node.parent = null;
                    root = node.right;
                }
                //该根节点有左也有右
                else {
                    //找最左儿子的最后边的节点,以此时的左儿子为根然后找到 此时右儿子应该的父节点,画图
                    //右儿子变成左儿子的"最右边"节点
                    //双向指向
                    //把一个点分裂,让左儿子继承即可
                    Node left = split(node);
                    //新的父节点,变成左儿子
                    root = left;
                    root.parent = null;
                }
                //非根节点,同样的判断
            } else {
                //该点无左无右
                if (node.left == null && node.right == null) {
                    //判断自己是左还是右,通过对比父节点可知
                    if (compare(data, node.parent.data)) {
                        //该点是右
                        node.parent.right = null;
                    } else {
                        //是左
                        node.parent.left = null;
                    }
                }
                //该点有左无右
                else if (node.right == null) {
                    //判断该点是左右
                    if (compare(data, node.parent.data)) {
                        //是右
                        node.parent.right = node.left;
                    } else {
                        node.parent.left = node.left;
                    }
                }
                //该点有右节点
                else if (node.left == null) {
                    //判断左右
                    if (compare(data, node.parent)) {
                        //右边
                        node.parent.right = node.right;
                    } else {
                        node.parent.left = node.right;
                    }
                    //该点有左右
                } else {
                    //分裂该节点
                    Node left = split(node);
                    //判断该点是左是右边
                    if (compare(data, node.parent.data)) {
                        //右
                        node.parent.right = left;//删除的位置由左儿子继承
                    } else {
                        //左
                        node.parent.left = left;
                    }
                    //双向
                    left.parent = node.parent;
                }
            }
        }
    }

    private Node split(Node node) {
        Node parentL = findPrent(node.right.data, node.left);//以右边数据为参考,从该点左侧开始找
        parentL.right = node.right;//双向;
        node.right.parent = parentL;//双向
        return node.left;//返回的是继承的左儿子
    }

    //寻找该数据所在的的点
    private Node findNode(Object data) {
        //从根节点遍历,先默认为根,不等于的话就接着改变
        Node node = root;
        while (node != null) {
            //相等就获取到了
            if (data.equals(node.data) && data.hashCode() == node.data.hashCode()) {
                return node;//等同break;
            } else {
                if (compare(data, node.data)) {
                    node = node.right;
                } else {
                    node = node.left;
                }
            }
        }
        return node;
    }

    //更新方法
    public void update(int oldData, int newData) {
        //包含才操作
        if (contians(oldData)) {
            //删除旧的,放新的
            remove(oldData);
            add(newData);
        }
    }
}

新建测试类进行测试

public class MyTreeTest {
    public static void main(String[] args) {
        MyTree tree = new MyTree();
        System.out.println("--------------------add----------------------");
        tree.add(3);
        tree.add(4);
        tree.add(2);
        tree.add(2);//重复
        tree.add(21);
        tree.add(11);
        tree.add(12);
        tree.add(7);
        System.out.println(tree);
        tree.remove(3);
        System.out.println("--------------------remove 3----------------------");
        System.out.println(tree);
        tree.remove(21);
        System.out.println("--------------------remove 21----------------------");
        System.out.println(tree);
        tree.update(12, 77);
        System.out.println("--------------------把12 改成 21----------------------");
        System.out.println(tree);

    }
}

打印结果
--------------------add---------------------
[2,3,4,7,11,12,21]
--------------------remove 3----------------
[2,4,7,11,12,21]
--------------------remove 21---------------
[2,4,7,11,12]
--------------------把12 改成 21-------------
[2,4,7,11,77]

总结:

对于二叉树数据结构算法,大家只要理解一下几点就可完全明白算法过程:

1.节点的4个属性
2.选择中序排序的 左->中->右 特点
3.各种小方法构成大方法,小方法:
    1.根据数据获取节点:findNode(data)
    2.包含的方法
4.大方法:添加,删除
5.最重要的就是逻辑的判断:确定删除的点是否为根节点,再确定删除的点的儿子情况:有无儿子,有哪个儿子,有多少个儿子,选择继承的儿子.

以上就是二叉树算法的完全解析和归纳总结,希望能帮助大家更好的理解

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值