二叉树

二叉树的完全理解


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.此节点同时有左右儿子

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

下面给出详细代码

  1 public class MyTree {
  2     //节点的属性
  3     private class Node {
  4 
  5         Node   parent;//父节点
  6         Object data;//数据
  7         Node   left;  //左节点
  8         Node   right; //
  9     }
 10 
 11     //
 12     private Node root;
 13 
 14     public void add(Object data) {
 15         //存在就不添加
 16         if (contians(data)) {
 17             return;
 18         }
 19         //建一个点
 20         Node node = new Node();
 21         node.data = data;
 22 
 23         if (root == null) {//根是空,该点就是根,说成根就是该点更合适
 24             root = node;
 25         } else {
 26             //判断是否存在
 27             //有节点,那就:1.找到该节点的应该的父节点,2.找到了,比较判断该值和父节点的值,应该放左还是右
 28             Node parent = findPrent(data, root);
 29             node.parent = parent;//赋值再双向,确定父节点地址
 30             if (compare(data, parent.data)) {
 31                 //大于就放在右边
 32                 //node.parent = parent.right;//赋值再双向,这个是错误的,等于parent 的地址,不用加上right
 33                 parent.right = node;
 34             } else {
 35                 parent.left = node;
 36             }
 37         }
 38 
 39     }
 40 
 41     /**
 42      * 包含就不添加
 43      */
 44     private boolean contians(Object data) {
 45         //查找到点就说明包含
 46         Node node = findNode(data);
 47         return node != null;
 48     }
 49 
 50     //找到该点的父节点
 51     private Node findPrent(Object data, Node node) {
 52         //从以该点为根开始找
 53         Node parent = null;
 54         Node temp = node;
 55         while (temp != null) {
 56             //先等于当前点,然后当左右都为空的时候就找到了
 57             parent = temp;
 58             if (compare(data, temp.data)) {
 59                 //比父节点大,继续右边找
 60                 temp = temp.right;
 61             } else {
 62                 temp = temp.left;
 63             }
 64 
 65         }
 66         return parent;
 67     }
 68 
 69     /**
 70      * compare只比大小,不比相等,这里相等就是当小于
 71      * 要能比较字符串和数字
 72      * 相等就不要添加
 73      * 然后看ture还是false放左右
 74      */
 75     private boolean compare(Object data1, Object data2) {
 76         Comparable c1;
 77         Comparable c2;
 78         if (data1 instanceof Comparable) {//看看是否传的是实现比较器的,是就转换
 79             c1 = (Comparable) data1;
 80             c2 = (Comparable) data2;
 81         } else {
 82             c1 = data1.toString();//否则就获取他们的toString();因为toString已经实现了Comparable比如数字,字符串都能比
 83             c2 = data1.toString();
 84         }
 85         int i = c1.compareTo(c2);
 86         return i > 0;
 87     }
 88 
 89     @Override
 90     public String toString() {
 91         return print();
 92     }
 93 
 94     /**
 95      * 打印方法,要采用递归,同汉诺塔原理
 96      * 中序
 97      */
 98     public String print() {
 99         StringBuffer sb = new StringBuffer();
100         see(root, sb);
101         String s = sb.toString();
102         if (s.length() <= 1) {
103             return "[" + s + "]";
104         }
105         s = s.substring(0, s.length() - 1);
106         return "[" + s + "]";
107     }
108 
109     //把根点放入进去
110     private void see(Node root, StringBuffer sb) {
111         if (root != null) {
112             //先打印左
113             see(root.left, sb);
114             //再打印中
115             //System.out.println(root.data);
116             sb.append(root.data + ",");
117             //再打印右边
118             see(root.right, sb);
119         }
120     }
121 
122     /**
123      * 移除方法
124      * 判断删除的是根节点还是非根节点
125      * 然后分别判断该点有无节点;只有左,还是只有右,还是都有,再双向判断
126      */
127     public void remove(Object data) {
128         Node node = findNode(data);
129         if (node != null) {//存在才去做
130             //删的是根节点
131             if (node.parent == null) {//等同node==root;
132                 //没有子节点
133                 if (node.left == null && node.right == null) {
134                     root = null;//根就就空了
135                 }
136                 //该根节点只有左节点无右
137                 else if (node.right == null) {
138                     node.parent = null;
139                     root = node.left;//根就变左节点
140                 }
141                 //该根节点有右节点无左
142                 else if (node.left == null) {
143                     node.parent = null;
144                     root = node.right;
145                 }
146                 //该根节点有左也有右
147                 else {
148                     //找最左儿子的最后边的节点,以此时的左儿子为根然后找到 此时右儿子应该的父节点,画图
149                     //右儿子变成左儿子的"最右边"节点
150                     //双向指向
151                     //把一个点分裂,让左儿子继承即可
152                     Node left = split(node);
153                     //新的父节点,变成左儿子
154                     root = left;
155                     root.parent = null;
156                 }
157                 //非根节点,同样的判断
158             } else {
159                 //该点无左无右
160                 if (node.left == null && node.right == null) {
161                     //判断自己是左还是右,通过对比父节点可知
162                     if (compare(data, node.parent.data)) {
163                         //该点是右
164                         node.parent.right = null;
165                     } else {
166                         //是左
167                         node.parent.left = null;
168                     }
169                 }
170                 //该点有左无右
171                 else if (node.right == null) {
172                     //判断该点是左右
173                     if (compare(data, node.parent.data)) {
174                         //是右
175                         node.parent.right = node.left;
176                     } else {
177                         node.parent.left = node.left;
178                     }
179                 }
180                 //该点有右节点
181                 else if (node.left == null) {
182                     //判断左右
183                     if (compare(data, node.parent)) {
184                         //右边
185                         node.parent.right = node.right;
186                     } else {
187                         node.parent.left = node.right;
188                     }
189                     //该点有左右
190                 } else {
191                     //分裂该节点
192                     Node left = split(node);
193                     //判断该点是左是右边
194                     if (compare(data, node.parent.data)) {
195                         //
196                         node.parent.right = left;//删除的位置由左儿子继承
197                     } else {
198                         //
199                         node.parent.left = left;
200                     }
201                     //双向
202                     left.parent = node.parent;
203                 }
204             }
205         }
206     }
207 
208     private Node split(Node node) {
209         Node parentL = findPrent(node.right.data, node.left);//以右边数据为参考,从该点左侧开始找
210         parentL.right = node.right;//双向;
211         node.right.parent = parentL;//双向
212         return node.left;//返回的是继承的左儿子
213     }
214 
215     //寻找该数据所在的的点
216     private Node findNode(Object data) {
217         //从根节点遍历,先默认为根,不等于的话就接着改变
218         Node node = root;
219         while (node != null) {
220             //相等就获取到了
221             if (data.equals(node.data) && data.hashCode() == node.data.hashCode()) {
222                 return node;//等同break;
223             } else {
224                 if (compare(data, node.data)) {
225                     node = node.right;
226                 } else {
227                     node = node.left;
228                 }
229             }
230         }
231         return node;
232     }
233 
234     //更新方法
235     public void update(int oldData, int newData) {
236         //包含才操作
237         if (contians(oldData)) {
238             //删除旧的,放新的
239             remove(oldData);
240             add(newData);
241         }
242     }
243 }
View Code

 

新建测试类进行测试

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.最重要的就是逻辑的判断:确定删除的点是否为根节点,再确定删除的点的儿子情况:有无儿子,有哪个儿子,有多少个儿子,选择继承的儿子.

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

posted @ 2018-04-17 16:14  kdy  阅读(195)  评论(0编辑  收藏  举报