一.二叉樹的結構
在進行鏈表結構開發的過程之中,會發現所有的數據按照首尾相連的狀態進行保存,那么 在進行數據查詢時為了判斷數據是否存在,這種情況下它所面對的時間復雜度就是"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 }
--可以發現這種樹結構的刪除操作是非常繁瑣的,所以如果不是必須的情況下不建議使用刪除