在二叉搜索树中,对于一个节点 x ,其左子树中所有节点的值都不大于 x 的值,其右子树中所有节点的值都不小于 x 的值。
树节点类
要实现二叉搜索树,首先需要构造一个 TreeNode
的类,在这个类中 value
属性用于存储节点的值,parent
、leftChild
和 rightChild
分别为其父节点、左孩子节点和右孩子节点。
public class TreeNode<T extends Comparable<T>> {
public T value;
public TreeNode<T> parent; // 父节点
public TreeNode<T> leftChild; // 左孩子
public TreeNode<T> rightChild; // 右孩子
public TreeNode(T value) {
this.value = value;
}
}
二叉搜索树类
然后,可以在 TreeNode 的基础上实现 BSTree (二叉搜索树)类。支持对二叉搜索树的插入、删除、查找、前序遍历、中序遍历和后序遍历等操作。
插入节点
插入新节点的思路比较简单。如果当前树的根节点为 null
,则直接把要插入的节点设置为根节点即可。否则,就从根节点开始,查找当前节点应该插入的位置,与堆不同,在二叉搜索树中新插入的节点一定是个叶子节点。
/**
* 插入一个对象
* @param value
*/
public void add(T value) {
TreeNode<T> node = new TreeNode<>(value);
this.insert(node);
}
/**
* 插入一个新节点
* @param node
*/
private void insert(TreeNode<T> node) {
TreeNode<T> current = this.root;
TreeNode<T> p = null; // 记录current的父节点
while(current!=null) {
p = current;
if(node.value.compareTo(current.value)<0) {
current = current.leftChild;
}else {
current = current.rightChild;
}
}
node.parent = p;
if(p==null) { // 二叉树为空
this.root = node;
}else { // 二叉树不为空
if(node.value.compareTo(p.value)<0) {
p.leftChild = node;
}else {
p.rightChild = node;
}
}
this.size++;
}
删除节点
相比于插入节点来讲,删除节点会麻烦一些,需要分几种情况:
- 如果要删除的节点没有孩子,直接将它删掉即可;
- 如果要删除的节点只有一个孩子,直接用它的孩子来代替它就行了;
- 如果要删除的节点有两个孩子(左孩子和右孩子),这种情况最麻烦。我们需要用它左子树中最大的节点或右子树中最小的节点来代替它。下面的代码用的是右子树中最小的节点。
为了实现方便,用 transplant(TreeNode<T> u, TreeNode<T> v)
函数来实现用节点 v
替换节点 u
的功能。
/**
* 在树中删掉一个值为 value 的节点
* @param value
*/
public void delete(T value) {
TreeNode<T> node = this.search(value);
if(node != null) {
this.delete(node);
}
}
/**
* 删除一个节点
* @param node
*/
private void delete(TreeNode<T> node) {
if(node.leftChild==null) { // 要删除的节点没有左子树
transplant(node, node.rightChild);
}else if(node.rightChild==null) { // 要删除的节点没有右子树
transplant(node, node.leftChild);
}
else { // 既有左子树也有右子树,需要用node的后继节点来代替node
TreeNode<T> current = this.minimum(node.rightChild);
if(current.parent!=node) {
this.transplant(current, current.rightChild);
current.rightChild = node.rightChild;
current.rightChild.parent = current;
}
this.transplant(node, current);
current.leftChild = node.leftChild;
current.leftChild.parent = current;
}
this.size--;
}
/**
* 在树种用 v 代替 u
* 是删除操作的一个子过程
* @param u
* @param v
*/
private void transplant(TreeNode<T> u, TreeNode<T> v) {
if(u.parent==null) {
this.root = v;
}else if(u==u.parent.leftChild) {
u.parent.leftChild = v;
}else {
u.parent.rightChild = v;
}
if(v!=null) {
v.parent = u.parent;
}
}
BSTree类的完整代码
其他的函数实现原理相对比较简单,因而不再一一详细记录。这里把 BSTree
类的完整实现放在下面,也方便以后使用时,可以直接复制。
/**
* 二叉搜索树
* @author brz
*
*/
public class BSTree<T extends Comparable<T>> {
public TreeNode<T> root;
public int size;
public BSTree() {
size = 0;
}
/**
* 插入一个对象
* @param value
*/
public void add(T value) {
TreeNode<T> node = new TreeNode<>(value);
this.insert(node);
}
/**
* 插入一个新节点
* @param node
*/
private void insert(TreeNode<T> node) {
TreeNode<T> current = this.root;
TreeNode<T> p = null; // 记录current的父节点
while(current!=null) {
p = current;
if(node.value.compareTo(current.value)<0) {
current = current.leftChild;
}else {
current = current.rightChild;
}
}
node.parent = p;
if(p==null) { // 二叉树为空
this.root = node;
}else { // 二叉树不为空
if(node.value.compareTo(p.value)<0) {
p.leftChild = node;
}else {
p.rightChild = node;
}
}
this.size++;
}
/**
* 在树中删掉一个值为 value 的节点
* @param value
*/
public void delete(T value) {
TreeNode<T> node = this.search(value);
if(node != null) {
this.delete(node);
}
}
/**
* 删除一个节点
* @param node
*/
private void delete(TreeNode<T> node) {
if(node.leftChild==null) { // 要删除的节点没有左子树
transplant(node, node.rightChild);
}else if(node.rightChild==null) { // 要删除的节点没有右子树
transplant(node, node.leftChild);
}
else { // 既有左子树也有右子树,需要用node的后继节点来代替node
TreeNode<T> current = this.minimum(node.rightChild);
if(current.parent!=node) {
this.transplant(current, current.rightChild);
current.rightChild = node.rightChild;
current.rightChild.parent = current;
}
this.transplant(node, current);
current.leftChild = node.leftChild;
current.leftChild.parent = current;
}
this.size--;
}
/**
* 在树种用 v 代替 u
* 是删除操作的一个子过程
* @param u
* @param v
*/
private void transplant(TreeNode<T> u, TreeNode<T> v) {
if(u.parent==null) {
this.root = v;
}else if(u==u.parent.leftChild) {
u.parent.leftChild = v;
}else {
u.parent.rightChild = v;
}
if(v!=null) {
v.parent = u.parent;
}
}
/**
* 对整棵树进行中序遍历
* @return
*/
public String inorderTraversal() {
return this.inorderTraversal(this.root);
}
/**
* 中序遍历以 node 为根节点的二叉搜索子树
* @param node
*/
private String inorderTraversal(TreeNode<T> node) {
if(node==null) {
return "";
}
String str1 = this.inorderTraversal(node.leftChild);
String str2 = node.value + " \t";
String str3 = this.inorderTraversal(node.rightChild);
String str = str1 + str2 + str3;
return str;
}
/**
* 对整棵树进行前序遍历
* @return
*/
public String preorderTraversal() {
return this.preorderTraversal(this.root);
}
/**
* 前序遍历以 node 为根节点的二叉搜索子树
* @param node
*/
private String preorderTraversal(TreeNode<T> node) {
if(node==null) {
return "";
}
String str1 = node.value + " \t";
String str2 = this.preorderTraversal(node.leftChild);
String str3 = this.preorderTraversal(node.rightChild);
String str = str1 + str2 + str3;
return str;
}
/**
* 对整棵树进行后序遍历
* @return
*/
public String postorderTraversal() {
return this.postorderTraversal(this.root);
}
/**
* 后序遍历以 node 为根节点的二叉搜索子树
* @param node
*/
private String postorderTraversal(TreeNode<T> node) {
if(node==null) {
return "";
}
String str1 = this.postorderTraversal(node.leftChild);
String str2 = this.postorderTraversal(node.rightChild);
String str3 = node.value + " \t";
String str = str1 + str2 + str3;
return str;
}
/**
* 在二叉搜索树种查找值为 value 的节点
* @param value
* @return
*/
public TreeNode<T> search(T value){
return this.search(this.root, value);
}
/**
* 在以 node 为根节点的子树中查找 值为 value 的节点
* @param node
* @param value
* @return
*/
private TreeNode<T> search(TreeNode<T> node, T value){
TreeNode<T> current = node;
while(current!=null) {
if(current.value.compareTo(value)==0) {
return current;
}
else if(current.value.compareTo(value)<0) {
current = current.rightChild;
}else {
current = current.leftChild;
}
}
return current;
}
/**
* 判断树种是否包含值为value的节点
* @param value
* @return
*/
public boolean contains(T value) {
TreeNode<T> node = this.search(this.root, value);
return !(node==null);
}
/**
* 返回整棵树中最小的节点
* @return
*/
public TreeNode<T> minimum(){
return this.minimum(this.root);
}
/**
* 返回以node为根节点的最小节点
* @return
*/
private TreeNode<T> minimum(TreeNode<T> node){
if(node == null) {
return null;
}
TreeNode<T> current = node;
// 一路向左即可
while(current.leftChild!=null) {
current = current.leftChild;
}
return current;
}
/**
* 返回整棵树中值最大的节点
* @return
*/
public TreeNode<T> maximum(){
return this.maximum(this.root);
}
/**
* 返回以node为根节点的子树中,值最大的节点
* @return
*/
private TreeNode<T> maximum(TreeNode<T> node){
if(node==null) {
return null;
}
TreeNode<T> current = node;
// 一路向右即可
while(current.rightChild != null) {
current = current.rightChild;
}
return current;
}
/**
* 返回 node 的后继节点
* 也就是最小的比 node 大的节点
* @return
*/
public TreeNode<T> successor(TreeNode<T> node){
if(node == null) {
return null;
}
TreeNode<T> current = node.rightChild;
while(current.leftChild != null) {
current = current.leftChild;
}
return current;
}
/**
* 返回 node 节点的前驱节点
* 也就是比 node 小的节点中最大的那个
* @param node
* @return
*/
public TreeNode<T> predecessor(TreeNode<T> node){
if(node==null)
return null;
TreeNode<T> current = node.leftChild;
while(current.rightChild != null) {
current = current.rightChild;
}
return current;
}
}
测试用例
下面是一个用于测试前面代码的例子:
public class BSTreeTest {
/**
* 测试自己实现的二叉搜索树
*/
public void myTest() {
Integer[] nums = {6, 2, 8, 1, 4, 3};
BSTree<Integer> tree = new BSTree<>();
for(int i=0; i<nums.length; i++) {
tree.add(nums[i]);
}
System.out.println("初始的树有 "+tree.size+" 个节点:");
System.out.print(" 前序遍历:\t");
System.out.println(tree.preorderTraversal());
System.out.print(" 中序遍历:\t");
System.out.println(tree.inorderTraversal());
System.out.print(" 后序遍历:\t");
System.out.println(tree.postorderTraversal());
tree.add(5);
System.out.println("增加一个节点之后的树有 "+tree.size+" 个节点:");
System.out.print(" 前序遍历:\t");
System.out.println(tree.preorderTraversal());
System.out.print(" 中序遍历:\t");
System.out.println(tree.inorderTraversal());
System.out.print(" 后序遍历:\t");
System.out.println(tree.postorderTraversal());
tree.delete(6);
System.out.println("删除一个节点之后的树有 "+tree.size+" 个节点:");
System.out.print(" 前序遍历:\t");
System.out.println(tree.preorderTraversal());
System.out.print(" 中序遍历:\t");
System.out.println(tree.inorderTraversal());
System.out.print(" 后序遍历:\t");
System.out.println(tree.postorderTraversal());
}
public static void main(String[] args) {
// TODO Auto-generated method stub
BSTreeTest test = new BSTreeTest();
test.myTest();
}
}
上面这段代码首先逐个插入元素构造了一颗二叉搜索树,然后测试了对这棵树增加和删除节点的效果,并输出了三种遍历的结果,其执行结果如下
初始的树有 6 个节点:
前序遍历: 6 2 1 4 3 8
中序遍历: 1 2 3 4 6 8
后序遍历: 1 3 4 2 8 6
增加一个节点之后的树有 7 个节点:
前序遍历: 6 2 1 4 3 5 8
中序遍历: 1 2 3 4 5 6 8
后序遍历: 1 3 5 4 2 8 6
删除一个节点之后的树有 6 个节点:
前序遍历: 8 2 1 4 3 5
中序遍历: 1 2 3 4 5 8
后序遍历: 1 3 5 4 2 8
下面这幅图显示了测试用例中的建树过程
下图则显示了插入5和删除6的过程