java--二叉树解析及基本实现
一.二叉树的结构
在进行链表结构开发的过程之中,会发现所有的数据按照首尾相连的状态进行保存,那么 在进行数据查询时为了判断数据是否存在,这种情况下它所面对的时间复杂度就是"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 }
--可以发现这种树结构的删除操作是非常繁琐的,所以如果不是必须的情况下不建议使用删除