1、重构二叉搜索树
1.1、设计思想
由于实际项目中常用的二叉树种类有很多(如:二叉搜索树、AVL
树、红黑树等),为了提高代码的复用率和可读性,我们可以通过继承二叉树的方式重构这些特殊的二叉树。
因此,我们也可以通过继承二叉树的方式实现二叉搜索树(Binary Search Tree
,BST
),如下图所示:
其中:
- 二叉树类中存放所有二叉树公共的接口。
- 二叉搜索树类中实现二叉搜索树自己特有的接口。
1.2、类的结构设计
同样地,在编程之前,我们需要先对二叉树类和二叉搜索树类的结构进行设计。
1.2.1、二叉树类设计
二叉树类需要的成员主要包括:
**注:**内部类的成员可以不写访问权限。
-
用户自定义对节点元素的处理逻辑(抽象类)
1 2 3 4 5 6 7 8
| public static abstract class Visitor<E>{ boolean isStoped;
abstract boolean visit(E element); }
|
-
接口函数
1.2.2、二叉搜索树类
二叉搜索树类需要的成员主要包括:
1.3、接口设计
1.3.1、二叉树类接口设计
- int size(); //元素的数量
- boolean isEmpty(); //是否为空
- void clear(); //清空所有元素
- void preorder(); //前序遍历
- void inorder(); //中序遍历
- void postorder(); //后序遍历
- void levelOrder(); //层序遍历
- boolean isComplete(); //是否为完全二叉树
- int height(); //计算二叉树的高度
注:
- 与动态数组的接口相比,二叉树的元素是没有索引(
index
)这一概念的。
- 用户处理(添加、删除、查找等)的是元素,他们是不关心二叉树内部节点结构的。
- 由于普通二叉树中各节点之间的逻辑关系不明确,因此实际应用中一般不使用普通二叉树存储数据,而是使用继承了该类的特定二叉树(如:二叉搜索树、
AVL
树、红黑树等)来存储数据,进行元素的增加、删除、查询等操作。
1.3.2、二叉搜索树类接口设计
- void add(E element); //添加元素
- void remove(E element); //删除元素
- boolean contains(E element); //搜索(查找二叉搜索树中是否包含某元素)
2、重新实现二叉搜索树
2.1、编程实现
新建一个项目,在其中:
(1)★实现二叉树
新建一个名称为BinaryTree
(包名为:com.atangbiji
)的类,用来实现二叉树;
BinaryTree.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
| package com.atangbiji;
import java.util.LinkedList; import java.util.Queue; import com.atangbiji.printer.BinaryTreeInfo;
@SuppressWarnings("unchecked") public class BinaryTree<E> implements BinaryTreeInfo{ protected int size; protected Node<E> root; protected static class Node<E> { E element; Node<E> left; Node<E> right; Node<E> parent; public Node(E element, Node<E> parent) { this.element = element; this.parent = parent; } public boolean isLeaf() { return left == null && right == null; } public boolean hasTwoChildren() { return left != null && right != null; } } public static abstract class Visitor<E>{ boolean isStoped;
abstract boolean visit(E element); }
public int size() { return size; }
public boolean isEmpty() { return size == 0; }
public void clear() { root = null; size = 0; }
public void preorder(Visitor<E> visitor) { preorder(root,visitor); }
private void preorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor == null || visitor.isStoped) return; visitor.isStoped = visitor.visit(node.element); preorder(node.left,visitor); preorder(node.right,visitor); } public void inorder(Visitor<E> visitor) { inorder(root,visitor); }
private void inorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor == null || visitor.isStoped) return; inorder(node.left,visitor); if (visitor.isStoped) return; visitor.isStoped = visitor.visit(node.element); inorder(node.right,visitor); }
public void postorder(Visitor<E> visitor) { postorder(root,visitor); }
private void postorder(Node<E> node, Visitor<E> visitor) { if (node == null || visitor == null || visitor.isStoped) return; postorder(node.left,visitor); postorder(node.right,visitor); if (visitor.isStoped) return; visitor.isStoped = visitor.visit(node.element); }
public void levelOrder(Visitor<E> visitor) { if (root == null || visitor == null) return; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { Node<E> node = queue.poll(); if (visitor.visit(node.element)) { return; }; if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } }
public void preorderTraversal() { preorderTraversal(root); }
private void preorderTraversal(Node<E> node) { if (node == null) return; System.out.println(node.element); preorderTraversal(node.left); preorderTraversal(node.right); }
public void inorderTraversal() { inorderTraversal(root); }
private void inorderTraversal(Node<E> node) { if (node == null) return;
inorderTraversal(node.left); System.out.println(node.element); inorderTraversal(node.right); } public void postorderTraversal() { postorderTraversal(root); }
private void postorderTraversal(Node<E> node) { if (node == null) return;
postorderTraversal(node.left); postorderTraversal(node.right); System.out.println(node.element); }
public void levelOrderTraversal() { if (root == null) return; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { Node<E> node = queue.poll(); System.out.println(node.element); if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } }
protected Node<E> predecessor(Node<E> node) { if (node == null) return null; Node<E> p = node.left; if (p != null) { while (p.right != null) { p = p.right; } return p; } while (node.parent != null && node == node.parent.left) { node = node.parent; }
return node.parent; }
protected Node<E> successor(Node<E> node) { if (node == null) return null; Node<E> p = node.right; if (p != null) { while (p.left != null) { p = p.left; } return p; } while (node.parent != null && node == node.parent.right) { node = node.parent; }
return node.parent; }
public boolean isComplete() { if (root == null) return false; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); boolean leaf = false; while (!queue.isEmpty()) { Node<E> node = queue.poll(); if (leaf && !node.isLeaf()) return false; if (node.hasTwoChildren()) { queue.offer(node.left); queue.offer(node.right); } else if (node.left == null && node.right != null) { return false; } else { leaf = true; if (node.left != null) { queue.offer(node.left); } } } return true; }
public boolean isComplete2() { if (root == null) return false; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root);
boolean leaf = false; while (!queue.isEmpty()) { Node<E> node = queue.poll(); if (leaf && !node.isLeaf()) return false; if (node.left != null) { queue.offer(node.left); } else if (node.right != null) { return false; } if (node.right != null) { queue.offer(node.right); } else { leaf = true; } } return true; }
public int height() { return height(root); }
private int height(Node<E> node) { if (node == null) return 0; return 1 + Math.max(height(node.left), height(node.right)); }
public int height2() { if (root == null) return 0; int height = 0; int levelSize = 1; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { Node<E> node = queue.poll(); levelSize--; if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); }
if (levelSize == 0) { levelSize = queue.size(); height++; } } return height; }
@Override public String toString() { StringBuilder sb = new StringBuilder(); toString(root,sb,""); return sb.toString(); }
private void toString(Node<E> node, StringBuilder sb, String prefix) { if (node == null) return; toString(node.left, sb, prefix + "L---"); sb.append(prefix).append(node.element).append("\n"); toString(node.right, sb, prefix + "R---"); }
@Override public Object root() { return root; }
@Override public Object left(Object node) { return ((Node<E>)node).left; }
@Override public Object right(Object node) { return ((Node<E>)node).right; }
@Override public Object string(Object node) { return ((Node<E>)node).element; } }
|
**注:**在父类和子类都能够访问的成员变量和方法应当设置为protected
类型。
(2)★实现二叉搜索树
新建一个名称为BinarySearchTree
(包名为:com.atangbiji
)的类,用来实现二叉搜索树;
BinarySearchTree.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
| package com.atangbiji;
import java.util.Comparator;
@SuppressWarnings("unchecked") public class BinarySearchTree<E> extends BinaryTree<E>{
private Comparator<E> comparator; public BinarySearchTree() { this(null); } public BinarySearchTree(Comparator<E> comparator) { this.comparator = comparator; }
public void add(E element) { elementNotNullCheck(element); if (root == null) { root = new Node<>(element,null); size++; return; } Node<E> node = root; Node<E> parent = null; int cmp = 0; while (node != null) { cmp = compare(element, node.element); parent = node; if (cmp > 0) { node = node.right; }else if (cmp < 0) { node = node.left; }else { node.element = element; return; } } Node<E> newNode = new Node<>(element, parent); if (cmp > 0) { parent.right = newNode; } else if (cmp < 0) { parent.left = newNode; } size++; }
public void remove(E element) { remove(node(element)); }
public boolean contains(E element) { return node(element) != null; }
private void remove(Node<E> node) { if (node == null) return; size--; if (node.hasTwoChildren()) { Node<E> s = successor(node); node.element = s.element; node = s; } Node<E> replacement = node.left != null ? node.left : node.right; if (replacement != null) { replacement.parent = node.parent; if (node.parent == null) { root = replacement; } else if (node == node.parent.left) { node.parent.left = replacement; } else { node.parent.right = replacement; } } else if (node.parent == null) { root = null; } else { if (node == node.parent.left) { node.parent.left = null; } else { node.parent.right = null; } } }
private Node<E> node(E element) { Node<E> node = root; while (node != null) { int cmp = compare(element, node.element); if (cmp == 0) return node; if (cmp > 0) { node = node.right; } else { node = node.left; } } return null; }
private int compare(E e1,E e2) { if (comparator != null) { return comparator.compare(e1, e2); } return ((Comparable<E>)e1).compareTo(e2); }
private void elementNotNullCheck(E element) { if (element == null) { throw new IllegalArgumentException("element must not null !"); } } }
|
(3)自定义节点元素对象的比较逻辑
为了与之前项目二叉搜索树的功能保持一致,我们复用之前项目中自定义的节点元素对象的比较逻辑。
Person.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package com.atangbiji;
public class Person implements Comparable<Person>{ private int age; private String name; public Person(int age, String name) { this.age = age; this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public int compareTo(Person e) { return age - e.age; } @Override public String toString() { return name + "(age="+ age +")"; } }
|
PersonComparator1.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package com.atangbiji;
import java.util.Comparator;
public class PersonComparator1 implements Comparator<Person> {
@Override public int compare(Person e1, Person e2) { return e1.getAge() - e2.getAge(); } }
|
PersonComparator2.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.atangbiji;
import java.util.Comparator;
public class PersonComparator2 implements Comparator<Person> { @Override public int compare(Person e1, Person e2) { return e2.getAge() - e1.getAge(); } }
|
(4)使用小码哥MJ
封装的二叉树打印接口
为了继续使用小码哥MJ
封装的二叉树打印接口,我们复用小码哥MJ封装的二叉树打印工具。
(5)自定义测试类
再新建一个名称为Main
(包名为:com.atangbiji
)的类,用来调用(测试)自己封装的二叉搜索树。如下图所示:
2.2、接口测试
在Main
类中新建多个BinarySearchTree
对象,用来测试自己重新封装的二叉搜索树。这里仅以添加、删除、遍历节点元素等为例进行测试,其它接口的测试方法与此类似,这里不再赘述。
Main.java
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
| package com.atangbiji;
import java.util.Comparator;
import com.atangbiji.BinaryTree.Visitor; import com.atangbiji.printer.BinaryTrees;
public class Main { public static void main(String[] args) {
Integer data[] = new Integer[] { 7,4,2,1,3,5,9,8,11,10,12 }; BinarySearchTree<Integer> bst = new BinarySearchTree<>(); for(int i = 0; i < data.length; i++){ bst.add(data[i]); } System.out.println("一、节点元素为整数的二叉搜索树: \n"); System.out.println("1、采用(中序)遍历法打印二叉搜索树: \n"); System.out.println(bst); System.out.println("2、使用MJ封装的接口打印二叉搜索树: \n"); BinaryTrees.println(bst); System.out.println("二叉树高度(递归实现)为: " + bst.height() + "\n"); System.out.println("二叉树高度(层序遍历实现)为: " + bst.height2() + "\n");
PersonComparator1 cmp1 = new PersonComparator1(); BinarySearchTree<Person> bst1 = new BinarySearchTree<>(cmp1); bst1.add(new Person(12, "Tom")); bst1.add(new Person(15, "Jim")); bst1.add(new Person(10, "Lucy")); bst1.add(new Person(13, "ATang")); bst1.add(new Person(18, "Jan")); bst1.add(new Person(8, "Lily")); System.out.println("\n 二、节点元素为对象的二叉搜索树: "); System.out.println("\n 1、自定义比较逻辑1(Comparator接口法):假定年龄大的人所对应的节点元素大 \n"); BinaryTrees.println(bst1); PersonComparator2 cmp2 = new PersonComparator2(); BinarySearchTree<Person> bst2 = new BinarySearchTree<>(cmp2); bst2.add(new Person(12, "Tom")); bst2.add(new Person(15, "Jim")); bst2.add(new Person(15, "Jim")); bst2.add(new Person(10, "Lucy")); bst2.add(new Person(13, "ATang")); bst2.add(new Person(18, "Jan")); bst2.add(new Person(8, "Lily")); System.out.println("\n 2、自定义比较逻辑2(Comparator接口法):假定年龄大的人所对应的节点元素小 \n"); BinaryTrees.println(bst2); BinarySearchTree<Person> bst3 = new BinarySearchTree<>(); bst3.add(new Person(12, "Tom")); bst3.add(new Person(15, "Jim")); bst3.add(new Person(15, "Jim")); bst3.add(new Person(10, "Lucy")); bst3.add(new Person(13, "ATang")); bst3.add(new Person(18, "Jan")); bst3.add(new Person(8, "Lily")); System.out.println("\n 3、默认比较逻辑(Comparable接口法):假定年龄大的人所对应的节点元素大 \n"); BinaryTrees.println(bst3); BinarySearchTree<Person> bst4 = new BinarySearchTree<>(new Comparator<Person>() {
@Override public int compare(Person arg0, Person arg1) { return arg0.getAge() - arg1.getAge(); } }); bst4.add(new Person(12, "Tom")); bst4.add(new Person(15, "Jim")); bst4.add(new Person(15, "Jim")); bst4.add(new Person(10, "Lucy")); bst4.add(new Person(13, "ATang")); bst4.add(new Person(18, "Jan")); bst4.add(new Person(8, "Lily")); System.out.println("\n 4、通过匿名类自定义比较逻辑(Comparator接口法):假定年龄大的人所对应的节点元素大 \n"); BinaryTrees.println(bst4); System.out.println("\n" + "\n 三、遍历二叉搜索树(全部节点): "); System.out.println("\n 使用MJ封装的接口打印二叉搜索树: \n"); BinaryTrees.println(bst); System.out.println("\n" + "1、层序遍历二叉搜索树(全部节点):"); bst.levelOrder(new Visitor<Integer>() { @Override public boolean visit(Integer element) { System.out.print("_" + element + "; "); return false; } }); System.out.println("\n" + "2、前序遍历二叉搜索树(全部节点):"); bst.preorder(new Visitor<Integer>() {
@Override public boolean visit(Integer element) { System.out.print("_" + element + "; "); return false; } }); System.out.println("\n" + "3、中序遍历二叉搜索树(全部节点):"); bst.inorder(new Visitor<Integer>() {
@Override public boolean visit(Integer element) { System.out.print("_" + element + "; "); return false; } }); System.out.println("\n" + "4、后序遍历二叉搜索树(全部节点):"); bst.postorder(new Visitor<Integer>() {
@Override public boolean visit(Integer element) { System.out.print("_" + element + "; "); return false; } }); System.out.println("\n" + "\n 四、遍历二叉搜索树(部分节点): "); System.out.println("\n" + "使用MJ封装的接口打印二叉搜索树: \n"); BinaryTrees.println(bst); System.out.println("\n" + "1、层序遍历二叉搜索树(部分节点):"); bst.levelOrder(new Visitor<Integer>() { @Override public boolean visit(Integer element) { System.out.print("_" + element + "; "); return element == 8 ? true : false; } }); System.out.println("\n" + "2、前序遍历二叉搜索树(部分节点):"); bst.preorder(new Visitor<Integer>() {
@Override public boolean visit(Integer element) { System.out.print("_" + element + "; "); return element == 8 ? true : false; } }); System.out.println("\n" + "3、中序遍历二叉搜索树(部分节点):"); bst.inorder(new Visitor<Integer>() {
@Override public boolean visit(Integer element) { System.out.print("_" + element + "; "); return element == 8 ? true : false; } }); System.out.println("\n" + "4、后序遍历二叉搜索树(部分节点):"); bst.postorder(new Visitor<Integer>() {
@Override public boolean visit(Integer element) { System.out.print("_" + element + "; "); return element == 8 ? true : false; } });
System.out.println("\n" + "\n 五、删除二叉搜索树中的元素: "); System.out.println("\n" + "使用MJ封装的接口打印二叉搜索树(删除前): \n"); BinaryTrees.println(bst); System.out.println("\n" + "1、先删除元素值为5的叶子节点:"); bst.remove(5); System.out.println("\n" + "使用MJ封装的接口打印二叉搜索树(删除元素值为5的叶子节点后): \n"); BinaryTrees.println(bst); System.out.println("\n" + "2、在上述基础上,再删除元素值为4(度为1)的节点:"); bst.remove(4); System.out.println("\n" + "使用MJ封装的接口打印二叉搜索树(再删除元素值为4(度为1)的节点后): \n"); BinaryTrees.println(bst); System.out.println("\n" + "3、在上述基础上,再删除元素值为9(度为2)的节点:"); bst.remove(9); System.out.println("\n" + "使用MJ封装的接口打印二叉搜索树(再删除元素值为9(度为2)的节点后): \n"); BinaryTrees.println(bst); } }
|
运行该程序,输出结果为:
(本讲完,系列博文持续更新中…… )
参考文献:
[1] 《恋上数据结构与算法》,小码哥MJ
[2] 《数据结构与算法》,邓俊辉
如何获取源代码?
关注 “阿汤笔迹” 微信公众号,在后台回复关键词 “数据结构与算法设计与实现” ,即可获取项目源代码。
原文地址:http://www.atangbiji.com/2023/08/24/BinarySearchTree2/
博主最新文章在个人博客 http://www.atangbiji.com/ 发布。