前言
树是数据结构中必学的一种数据结构。在实际应用中,常见的树结构有二叉搜索树、B树、B+树、AVL树、红黑树、字典树等。
对应的用途列举如下:
- B/B+树:主要用于文件系统以及数据库中做索引
- AVL树:平衡二叉树,windows对进程地址空间的管理用到了AVL
- 红黑树:平衡二叉树的一种改进,广泛的应用在C++STL中,如map、set,以及JDK中的HashMap、TreeMap等
- Trie(字典树):又经常叫做前缀树,主要用于字符串检索、文本预测、词频统计等
二叉搜索树(BST Binary Search Tree)
概述
二叉搜索树也叫做二叉排序树,二叉搜索树采用二分思维将数据按照规则组装在一个树形结构中,大大提高了数据检索的效率。对于一颗普通的二叉树进行中序遍历,即可获取一个有序的数序列
特点
- 非空左子树的所有键值小于根节点的值
- 非空右子树的所有键值大于根节点的值
- 左右子树都是二叉搜索树
局限性
从图中可见,在改变一串数列组内顺序后,会得到不同的二叉搜索树。最好的情况下,搜索数据的时间复杂度为O(logN),最坏的情况下,该树会退化为线性表,导致时间复杂度变为O(N),因此,在二叉搜索树的基础上,又衍生出了AVL树和红黑树。后者基于二叉搜索树,做出了更多的限制。
代码实现
首先,我们需要构建一颗二叉树。首当其冲,我们顶一个数的节点类
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
| public class Node<T extends Comparable<T>> {
public T data;
public int height;
public Node<T> leftChild;
public Node<T> rightChild;
public Node(T data) { this(data, null, null); }
public Node(T data, Node<T> leftChild, Node<T> rightChild) { this.data = data; this.leftChild = leftChild; this.rightChild = rightChild; }
}
|
data字段由于不限定传入的对象类型,在这里使用泛型可以很好的规范
构建二叉树
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
| public class BsTree<T extends Comparable<T>> {
private Node<T> root;
public void createTree(T data) { if (data == null) { return; } root = insert(root, data); }
private Node<T> insert(Node<T> root, T data) { if (root == null) { return new Node<>(data); } else { if (compare(root, data) > 0) { root.leftChild = insert(root.leftChild, data); } else if (compare(root, data) < 0) { root.rightChild = insert(root.rightChild, data); } return root; } }
private T getMin(Node<T> root) { Node<T> temp = root; while (temp.leftChild != null) { temp = temp.leftChild; } return temp.data; }
private T getMax(Node<T> root) { Node<T> temp = root; while (temp.rightChild != null) { temp = temp.rightChild; } return temp.data; }
private int compare(Node<T> root, T data) { return root.data.compareTo(data); }
public void printTree() { System.out.print("前序遍历: "); preOrder(root); System.out.println(); System.out.print("中序遍历: "); inOrder(root); System.out.println(); System.out.print("后序遍历: "); postOrder(root); System.out.println(); }
private void preOrder(Node<T> root) { if (root != null) { System.out.print(root.data); System.out.print(" "); preOrder(root.leftChild); preOrder(root.rightChild); } }
private void inOrder(Node<T> root) { if (root != null) { inOrder(root.leftChild); System.out.print(root.data); System.out.print(" "); inOrder(root.rightChild); } }
private void postOrder(Node<T> root) { if (root != null) { postOrder(root.leftChild); postOrder(root.rightChild); System.out.print(root.data); System.out.print(" "); } } }
|
createTree
方法,每当要插入一个新节点时,便调用该函数。该函数会递归的搜索整棵树,确保找到一个合适的位置插入新的节点(比根节点小则插在左子树,大则插在右子树)
printTree
方法,用于打印建造完成的树。底层提供前序、中序、后序三种遍历方式
compare
方法用于比较两个节点的大小,这里简单使用hashcode
来作比较。众所周知,Integer
对象的hashcode
即为本身
删除节点
在二叉搜索树中,删除一个节点算是相当复杂的一份工作。主要面临三种情况
- 节点为叶子节点,则直接删除
- 只包含左子树或者右子树的节点,则将父节点指向待删除的节点的唯一直接子节点
- 包含左右子树的节点,则寻找右子树最小值或左子树最大值替换该节点
示例代码:
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
| public void deleteNode(T data) { if (data == null) { return; } root = delete(root, data); }
private Node<T> delete(Node<T> root, T data) { if (compare(root, data) < 0) { root.rightChild = delete(root.rightChild, data); } else if (compare(root, data) > 0) { root.leftChild = delete(root.leftChild, data); } else { if (root.leftChild != null && root.rightChild != null) { T temp = getMin(root.rightChild); root.data = temp; root.rightChild = delete(root.rightChild, temp); } else { if (root.leftChild != null) { root = root.leftChild; } else { root = root.rightChild; } } } return root; }
|
其中,getMin
方法用于寻找树中的最小节点。在一颗二叉排序树中,一目了然的,最小节点就是这棵树最靠左的节点
1 2 3 4 5 6 7
| private T getMin(Node<T> root) { Node<T> temp = root; while (temp.leftChild != null) { temp = temp.leftChild; } return temp.data; }
|
测试检验
编写单元测试,检验结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private static final Integer[] arrays = new Integer[]{50, 66, 60, 26, 21, 30, 70, 68};
@Test public void createBsTree() { BsTree<Integer> bsTree = new BsTree<>(); for (Integer data : arrays) { bsTree.createTree(data); } bsTree.printTree(); }
@Test public void deleteNode() { BsTree<Integer> bsTree = new BsTree<>(); for (Integer data : arrays) { bsTree.createTree(data); } bsTree.deleteNode(50); bsTree.printTree(); }
|
其中createBsTree
方法建立的二叉搜索树为:
1 2 3
| 前序遍历: 50 26 21 30 66 60 70 68 中序遍历: 21 26 30 50 60 66 68 70 后序遍历: 21 30 26 60 68 70 66 50
|
deleteNode
方法测试结果为:
1 2 3
| 前序遍历: 60 26 21 30 66 70 68 中序遍历: 21 26 30 60 66 68 70 后序遍历: 21 30 26 68 70 66 60
|
观察得到,中序遍历的结果就是一组升序排列的数列
相关代码可以在 https://github.com/Cheung0-bit/tree-practice 中找到