二叉树
二叉树的完全理解
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 }
新建测试类进行测试
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.最重要的就是逻辑的判断:确定删除的点是否为根节点,再确定删除的点的儿子情况:有无儿子,有哪个儿子,有多少个儿子,选择继承的儿子.
以上就是二叉树算法的完全解析和归纳总结,希望能帮助大家更好的理解