一.二叉树的结构
在进行链表结构开发的过程之中,会发现所有的数据按照首尾相连的状态进行保存,那么 在进行数据查询时为了判断数据是否存在,这种情况下它所面对的时间复杂度就是"O(n)",如果说它现在的数据量比较小(<30)是不会对性能造成什么影响的,而一旦保存的数据量很大,这个时候时间复杂度就会严重损耗程序的运行性能,那么对于数据的存储结构就必须发生改变,应该尽可能的减少检索次数为出发点进行设计.对于现在的数据结构而言,最好的性能就是"O(logn)",现在想要实现它,就可以使用二叉树的结构来完成.
如果想要实现一颗树结构的定义,那么就需要去考虑数据的存储形式,在二叉树的实现中,基本原理如下:取第一个保存的数据为根节点,当比根节点小或相等的数据需要放在根的左子树,而大于节点的数据要放在该节点的右子树.同时,在每一个树节点中需要保存的东西有如下:父节点,数据,左子树,右子树
--当要进行数据检索时,此时就需要进行每个节点的判断,例如现在我们要查找数据23,那么我们可以知道23比25小,那么查询25的左子树,而25的左子树为20比数据23小,则查询他的右子树,其右子树23就是我们所需要的数据.其时间复杂度为O(logn).
--对于二叉树的查询,也有三种形式,分别为:前序遍历(根-左-右),中序遍历(左-根-右),后序遍历(左-右-根),以中序遍历为例,则以上的数据在中序遍历的时候最终的结果就是(10,18,20,23,25,40,50,100),可以发现二叉树中的内容全部都是排序的结果.
二.二叉树的基础实现
二叉树实现的关键问题在于数据的保存,而且数据由于牵扯到对象比较的问题,那么一定要有比较器的支持,而首选的比较器就是Comparable,以Person数据为例:
1 package 常用类库.二叉树的实现; 2
3 import javax.jws.Oneway; 4 import java.lang.reflect.Array; 5 import java.util.Arrays; 6
7 /**
8 * @author : S K Y 9 * @version :0.0.1 10 */
11 class Person implements Comparable<Person> { 12 private String name; 13 private int age; 14
15 public Person() { 16 } 17
18 public Person(String name, int age) { 19 this.name = name; 20 this.age = age; 21 } 22
23 public String getName() { 24 return name; 25 } 26
27 public void setName(String name) { 28 this.name = name; 29 } 30
31 public int getAge() { 32 return age; 33 } 34
35 public void setAge(int age) { 36 this.age = age; 37 } 38
39 @Override 40 public int compareTo(Person o) { 41 return this.age - o.age; 42 } 43
44 @Override 45 public String toString() { 46 return "Person{" +
47 "name='" + name + '\'' +
48 ", age=" + age +
49 '}'; 50 } 51 } 52
53 class BinaryTree<T extends Comparable<T>> { 54 private class Node { 55 private Comparable<T> data; //存放Comparable,可以比较大小
56 private Node parent; //存放父节点
57 private Node left; //保存左子树
58 private Node right; //保存右子树
59
60 public Node(Comparable<T> data) { //构造方式直接实现数据的存储
61 this.data = data; 62 } 63
64 /**
65 * 实现节点数据的适当位置的存储 66 * 67 * @param newNode 创建的新节点 68 */
69 void addNode(Node newNode) { 70 if (newNode.data.compareTo((T) this.data) <= 0) { //比当前的节点小
71 if (this.left == null) { //没有左子树,进行保存
72 this.left = newNode; 73 newNode.parent = this; //保存父节点
74 } else { //需要向左边继续判断
75 this.left.addNode(newNode); //继续向下判断
76 } 77 } else { //比根节点的数据要大
78 if (this.right == null) { //没有右子树
79 this.right = newNode; 80 newNode.parent = this; //保存父节点
81 } else { 82 this.right.addNode(newNode); //继续向下进行
83 } 84 } 85 } 86
87 /**
88 * 实现所有数据的获取处理,按照中序遍历的形式来完成 89 */
90 void toArrayNode() { 91 if (this.left != null) { //存在左子树
92 this.left.toArrayNode(); //递归调用
93 } 94 BinaryTree.this.returnData[BinaryTree.this.foot++] = this.data; 95 if (this.right != null) { 96 this.right.toArrayNode(); 97 } 98 } 99
100 } 101
102 /*===========以下是二叉树的功能实现=============*/
103 private Node root; //保存的根节点
104 private int count; //保存数据个数
105 private Object[] returnData; //返回的数据
106 private int foot = 0; //脚标控制
107
108 /**
109 * 进行数据的增加 110 * 111 * @param data 需要保存的数据 112 * @throws NullPointerException 保存的数据不允许为空 113 */
114 public void add(Comparable<T> data) { 115 if (data == null) { 116 throw new NullPointerException("保存的数据不允许为空"); 117 } 118 //所有的数据本身不具备有节点关系的匹配,那么一定要将其包装在Node类之中
119 Node newNode = new Node(data); //保存节点
120 if (this.root == null) { //表名此时没有根节点,那么第一个保存的数据将作为根节点
121 this.root = newNode; 122 } else { //需要将其保存到一个合适的节点
123 this.root.addNode(newNode); 124 } 125 count++; 126 } 127
128
129 /**
130 * 以对象数组的形式返回数据,如果没有数据则返回null 131 * 132 * @return 全部数据 133 */
134 public Object[] toArray() { 135 if (this.count == 0) return null; 136 this.foot = 0; //脚标清零
137 this.returnData = new Object[count]; 138 this.root.toArrayNode(); 139 return returnData; 140 } 141
142 } 143
144 public class MyBinaryTree { 145 public static void main(String[] args) { 146 BinaryTree<Person> tree = new BinaryTree<>(); 147 tree.add(new Person("小红", 20)); 148 tree.add(new Person("小光", 80)); 149 tree.add(new Person("小亮", 40)); 150 tree.add(new Person("小龙", 25)); 151 tree.add(new Person("小C", 77)); 152 tree.add(new Person("小D", 66)); 153 tree.add(new Person("小九", 35)); 154 tree.add(new Person("小Q", 54)); 155 Object[] objects = tree.toArray(); 156 System.out.println(Arrays.toString(objects)); 157 } 158 }
--运行结果
[Person{name='小红', age=20}, Person{name='小龙', age=25}, Person{name='小九', age=35}, Person{name='小亮', age=40}, Person{name='小Q', age=54}, Person{name='小D', age=66}, Person{name='小C', age=77}, Person{name='小光', age=80}] Process finished with exit code 0
--在以上的代码实现中采用了递归算法的操作,采用递归算法,相对而言其代码更加的简介明了,但是此时在进行数据添加的时候,只是实现了节点关系的保存,而这种关系保存后的结果就是所有的数据都是有序排列的.
三.数据删除
二叉树的数据删除操作是非常复杂的,因为在进行数据删除的时候需要考虑的情况是比较多的:
--1.如果删除的节点没有子节点,那么直接删除该节点即可
--2.如果待删除节点只有一个子节点,那么删除该节点之后,考虑两种情况的分析:
a.只有一个左子树:将其左子树放置于原来父节点的位置
b.只有一个右子树:也是将其右子树放置于原来父节点的位置
--3.如果删除节点存在两个子节点,那么删除该节点,首先需要找到当前节点的后继节点,这个后继节点就是其右子树的左侧叶子节点(及该节点下的最后一个左子树)
--具体的代码实现
1 package 常用类库.二叉树的实现; 2
3 import java.util.Arrays; 4
5 /**
6 * @author : S K Y 7 * @version :0.0.1 8 */
9 class Person implements Comparable<Person> { 10 private String name; 11 private int age; 12
13 public Person() { 14 } 15
16 public Person(String name, int age) { 17 this.name = name; 18 this.age = age; 19 } 20
21 public String getName() { 22 return name; 23 } 24
25 public void setName(String name) { 26 this.name = name; 27 } 28
29 public int getAge() { 30 return age; 31 } 32
33 public void setAge(int age) { 34 this.age = age; 35 } 36
37 @Override 38 public int compareTo(Person o) { 39 return this.age - o.age; 40 } 41
42 @Override 43 public String toString() { 44 return "Person{" +
45 "name='" + name + '\'' +
46 ", age=" + age +
47 '}'; 48 } 49 } 50
51 class BinaryTree<T extends Comparable<T>> { 52 private class Node { 53 private Comparable<T> data; //存放Comparable,可以比较大小
54 private Node parent; //存放父节点
55 private Node left; //保存左子树
56 private Node right; //保存右子树
57
58 public Node(Comparable<T> data) { //构造方式直接实现数据的存储
59 this.data = data; 60 } 61
62 /**
63 * 实现节点数据的适当位置的存储 64 * 65 * @param newNode 创建的新节点 66 */
67 void addNode(Node newNode) { 68 if (newNode.data.compareTo((T) this.data) <= 0) { //比当前的节点小
69 if (this.left == null) { //没有左子树,进行保存
70 this.left = newNode; 71 newNode.parent = this; //保存父节点
72 } else { //需要向左边继续判断
73 this.left.addNode(newNode); //继续向下判断
74 } 75 } else { //比根节点的数据要大
76 if (this.right == null) { //没有右子树
77 this.right = newNode; 78 newNode.parent = this; //保存父节点
79 } else { 80 this.right.addNode(newNode); //继续向下进行
81 } 82 } 83 } 84
85 /**
86 * 实现所有数据的获取处理,按照中序遍历的形式来完成 87 */
88 void toArrayNode() { 89 if (this.left != null) { //存在左子树
90 this.left.toArrayNode(); //递归调用
91 } 92 System.out.println(foot + " " + this.data + " parent:" + this.parent + " left:" + this.left + " right:" + this.right); 93 BinaryTree.this.returnData[BinaryTree.this.foot++] = this.data; 94 if (this.right != null) { 95 this.right.toArrayNode(); 96 } 97 } 98
99 @Override 100 public String toString() { 101 return "Node{" +
102 "data=" + data +
103 '}'; 104 } 105 } 106
107 /*===========以下是二叉树的功能实现=============*/
108 private Node root; //保存的根节点
109 private int count; //保存数据个数
110 private Object[] returnData; //返回的数据
111 private int foot = 0; //脚标控制
112
113 /**
114 * 进行数据的增加 115 * 116 * @param data 需要保存的数据 117 * @throws NullPointerException 保存的数据不允许为空 118 */
119 public void add(Comparable<T> data) { 120 if (data == null) { 121 throw new NullPointerException("保存的数据不允许为空"); 122 } 123 //所有的数据本身不具备有节点关系的匹配,那么一定要将其包装在Node类之中
124 Node newNode = new Node(data); //保存节点
125 if (this.root == null) { //表名此时没有根节点,那么第一个保存的数据将作为根节点
126 this.root = newNode; 127 } else { //需要将其保存到一个合适的节点
128 this.root.addNode(newNode); 129 } 130 count++; 131 } 132
133 /**
134 * 返回树中当前的节点,如果存在 135 * 136 * @param data 所需要在树中获取节点的对象 137 * @return 书中的当前节点, 如果不存在, 则返回null 138 */
139 private Node getNode(Comparable<T> data) { 140 Node compareNode = BinaryTree.this.root; //当前比较的Node节点
141 int i; //当前的比较结果
142 while ((i = data.compareTo((T) compareNode.data)) != 0) { 143 if (i < 0) { //当前节点比此节点小
144 compareNode = compareNode.left; 145 } else { //当前节点比此节点大
146 compareNode = compareNode.right; 147 } 148 if (compareNode == null) return null; //不存在此节点,跳出循环,说明未找到数据
149 } 150 return compareNode; 151 } 152
153 /**
154 * 判断当前节点是否存在 155 * 156 * @param data 需要判断的加节点 157 * @return 如果当前节点存在则返回true, 不存在则返回false 158 * @throws NullPointerException 查询的数据不允许为空 159 */
160 public boolean contains(Comparable<T> data) { 161 if (data == null) return false; //当前对象为空
162 if (this.count == 0) return false; //当前不存在数据
163 return getNode(data) != null; 164 } 165
166
167 /**
168 * 执行节点的删除处理 169 * 170 * @param data 需要删除的节点数据 171 */
172 public void remove(Comparable<T> data) { 173 if (this.contains(data)) { //要删除的数据存在 174 //首先需要找到要删除的节点
175 Node removeNode = this.getNode(data); 176 if (removeNode.left == null && removeNode.right == null) { //情况1:当前节点不存在子节点 177 //此时只要断开该删除节点的连接即可
178 if (removeNode.equals(removeNode.parent.left)) { 179 removeNode.parent.left = null; 180 } else { 181 removeNode.parent.right = null; 182 } 183 removeNode.parent = null; //断开删除节点的引用
184 } else if (removeNode.left == null) { //此时说明只存在right子树
185 if (removeNode.equals(removeNode.parent.left)) { 186 removeNode.parent.left = removeNode.right; 187 } else { 188 removeNode.parent.right = removeNode.right; 189 } 190 removeNode.right.parent = removeNode.parent; 191 removeNode.parent = null; 192 } else if (removeNode.right == null) { //此时说明只存在left子树
193 if (removeNode.equals(removeNode.parent.left)) { 194 removeNode.parent.left = removeNode.left; 195 } else { 196 removeNode.parent.right = removeNode.left; 197 } 198 removeNode.left.parent = removeNode.parent; 199 removeNode.parent = null; 200 } else { //两边都有节点
201 Node needMoveNode = removeNode.right; //所需移动的节点
202 System.out.println("needMoveNode: " + needMoveNode.data); 203 while (needMoveNode.left != null) { 204 needMoveNode = needMoveNode.left; 205 } //此时已经获取删除节点的最小左节点,需要将其替代原来的节点 206 //考虑删除节点的右节点不存在左节点的情况,及删除节点的右节点就是最终的needMoveNode
207 if (needMoveNode.equals(needMoveNode.parent.right)) { 208 needMoveNode.parent.right = needMoveNode.right; 209 } else { 210 needMoveNode.parent.left = needMoveNode.right; 211 } 212 //替换节点的数据内容
213 removeNode.data = needMoveNode.data; 214 //断开needMoveNode的连接
215 needMoveNode.parent = null; 216
217 } 218 this.count--; 219 } 220 } 221
222 /**
223 * 以对象数组的形式返回数据,如果没有数据则返回null 224 * 225 * @return 全部数据 226 */
227 public Object[] toArray() { 228 if (this.count == 0) return null; 229 this.foot = 0; //脚标清零
230 System.out.println("count: " + count); 231 this.returnData = new Object[count]; 232 this.root.toArrayNode(); 233 return returnData; 234 } 235
236 } 237
238 public class MyBinaryTree { 239 public static void main(String[] args) { 240 //为了验证算法结构的准确性,将其内容设置为与图示相同
241 BinaryTree<Person> tree = new BinaryTree<>(); 242 tree.add(new Person("小红", 25)); 243 tree.add(new Person("小光", 20)); 244 tree.add(new Person("小亮", 40)); 245 tree.add(new Person("小龙", 18)); 246 tree.add(new Person("小C", 23)); 247 tree.add(new Person("小D", 50)); 248 tree.add(new Person("小九", 10)); 249 tree.add(new Person("小Q", 22)); 250 tree.add(new Person("小Q", 24)); 251 tree.add(new Person("小Q", 100)); 252 Object[] objects = tree.toArray(); 253 System.out.println(Arrays.toString(objects)); 254 //删除23节点
255 System.out.println("=======删除22节点========"); 256 tree.remove(new Person("小Q", 22)); 257 System.out.println(Arrays.toString(tree.toArray())); 258 System.out.println("=======删除18节点========"); 259 tree.add(new Person("小Q", 22)); 260 tree.remove(new Person("小龙", 18)); 261 System.out.println(Arrays.toString(tree.toArray())); 262 System.out.println("=======删除50节点========"); 263 tree.add(new Person("小龙", 18)); 264 tree.remove(new Person("小D", 50)); 265 System.out.println(Arrays.toString(tree.toArray())); 266 System.out.println("=======删除23节点========"); 267 tree.add(new Person("小D", 50)); 268 tree.remove(new Person("小C", 23)); 269 System.out.println(Arrays.toString(tree.toArray())); 270 System.out.println("=======删除20节点========"); 271 tree.add(new Person("小C", 23)); 272 tree.remove(new Person("小光", 20)); 273 System.out.println(Arrays.toString(tree.toArray())); 274 System.out.println("=======删除25根节点========"); 275 tree.add(new Person("小光", 20)); 276 tree.remove(new Person("小红", 25)); 277 System.out.println(Arrays.toString(tree.toArray())); 278 } 279 }
--可以发现这种树结构的删除操作是非常繁琐的,所以如果不是必须的情况下不建议使用删除