前言
二叉查找树是一棵二叉树,如果用来构造符号表的话,那么每个节点含有一个键值对,对于每个节点,其键大于其左子树中所有节点的键,且小于其右子树中所有节点的键,如果中序遍历二叉查找树并打印其键,那么键将按从小到大的顺序打印。如果仅仅是为了描述二叉查找树,是可以不用构造符号表的,每个节点只含有键就够了,但是本着实用主义,还是构造了符号表,符号表又称字典、索引,数据库的曾删改查就是基于符号表(B-树、红黑树构造的符号表)的,此外,Java集合的实现也用到了(TreeMap、TreeSet是用红黑树构造的符号表实现的)。它是二叉树遍历的应用,是AVL、红黑树、B-树等的基础。相对来说,删除操作要麻烦一点,但是理解了二叉树的递归定义和遍历,那么还是比较容易理解的。
查找操作
根据键查找值,先序遍历的应用,这里就不多说了,直接给出代码实现。
//递归实现,采用非递归的话使用一个栈模拟,也不需要驱动程序了
public int search(String key){
return search(root, key);
}
private int search(TreeNode node, String key){
if(node == null)
return -1;
//用-1表示未找到,相比之下,用泛型会更好,那样就可以返回null,但为了简单,这里没有采用泛型
int cmp = key.compareTo(node.key);
if(cmp < 0)
return search(node.left, key);//左子树中递归查找
else if(cmp > 0)
return search(node.right, key);//右子树中递归查找
else
return node.value;//当前节点的key就是要查找的key
}
插入操作
还是先序遍历的应用,递归地在左子树或右子树中插入,直到当前节点为null,此时才是真正意义上的插入操作。要 注意每次插入都要返回当前节点的引用以通知父节点 ,因为若新增加了一个节点,那么当前结点的父节点的链接之一要由null变为非null,否则该节点后面将无法访问。
public void insert(String key, int value){
root = insert(root, key, value);
}
private TreeNode insert(TreeNode node, String key, int value){
if(node == null)
return new TreeNode(key, value);
int cmp = key.compareTo(node.key);
if(cmp < 0)
node.left = insert(node.left, key, value);//在左子树中递归插入
else if(cmp > 0)
node.right = insert(node.right, key, value);//在右子树中递归插入
else
node.value = value;//在此之前树中就有该key,更新value的值
return node;
}
删除操作
首先,删除分三种情况:
1. 待删除的节点是叶子节点,即没有孩子,直接采用null替代该节点
2. 待删除的节点有一个孩子,那么用孩子替代该节点
3. 待删除的节点有两个孩子,可以采用前驱节点或后继节点替代,然后更新链接,最后删除前驱节点或后继节点即可。
前驱节点即在中序遍历中某一节点的前一个节点,后继节点即在中序遍历中某一节点的后一个节点。在BST中,前驱节点就是当前节点的左子树中含有最大键的节点(右边界的节点),后继节点就是当前结点的右子树中含有最小键的节点(左边界的节点)。这里删除前驱节点或后继节点也要注意通知父节点 ,因为前驱或后继节点的父节点的链接要由非null变为null,否则中序遍历BST时,本来要被删除的节点将被打印两次。注意,虽然有三种情况,第一种实际上是不用写的,因为第二种情况已经包括了。
//采用前驱节点替代实现删除操作
public void deleteWithPreNode(String key){
root = deleteWithPreNode(root, key);
}
private TreeNode deleteWithPreNode(TreeNode node, String key){
if(node == null)
return null;
int cmp = key.compareTo(node.key);
if(cmp < 0)
node.left = deleteWithPreNode(node.left, key);
else if(cmp > 0)
node.right = deleteWithPreNode(node.right, key);
else{
if(node.left == null)
return node.right;
if(node.right == null)
return node.left;
TreeNode temp = node;
node = max(temp.left);
node.right = temp.right;
node.left = deleteMax(temp.left);
}
return node;
}
//找到前驱节点
private TreeNode max(TreeNode node){
if(node == null)
return null;
while(node.right != null)
node = node.right;
return node;
}
//删除前驱节点
private TreeNode deleteMax(TreeNode node){
if(node.right == null)
return node.left;
node.right = deleteMax(node.right);
return node;
}
//采用后继节点替代实现删除操作
public void deleteWithPostNode(String key){
root = deleteWithPostNode(root, key);
}
private TreeNode deleteWithPostNode(TreeNode node, String key){
if(node == null)
return null;
int cmp = key.compareTo(node.key);
if(cmp < 0)
node.left = deleteWithPostNode(node.left, key);
else if(cmp > 0)
node.right = deleteWithPostNode(node.right, key);
else{
if(node.left == null)
return node.right;
if(node.right == null)
return node.left;
TreeNode temp = node;
node = min(temp.right);
node.left = temp.left;//要放在调整右链接前面,因为调整右链接可能改变树的结构
node.right = deleteMin(temp.right);
}
return node;
}
//找到后继节点
private TreeNode min(TreeNode node){
if(node == null)
return null;
while(node.left != null)
node = node.left;
return node;
}
//删除后继节点
private TreeNode deleteMin(TreeNode node){
if(node.left == null)
return node.right;
node.left = deleteMin(node.left);
return node;
}
最后附上所有代码:
package binarySearchTree;
import java.util.*;
/**
* 二叉查找树BST
* @author 小锅巴
*/
public class BST {
private TreeNode root;
private class TreeNode{
String key;//键,简单起见,没有采用泛型
int value;//值,
TreeNode left;//左链接
TreeNode right;//右链接
TreeNode(String key, int value){
this.key = key;
this.value = value;
}
}
//递归实现,采用非递归的话使用一个栈模拟,也不需要驱动程序了
public int search(String key){
return search(root, key);
}
private int search(TreeNode node, String key){
if(node == null)
return -1;
//用-1表示未找到,相比之下,用泛型会更好,那样就可以返回null,但为了简单,这里没有采用泛型
int cmp = key.compareTo(node.key);
if(cmp < 0)
return search(node.left, key);//左子树中递归查找
else if(cmp > 0)
return search(node.right, key);//右子树中递归查找
else
return node.value;//当前节点的key就是要查找的key
}
public void insert(String key, int value){
root = insert(root, key, value);
}
private TreeNode insert(TreeNode node, String key, int value){
if(node == null)
return new TreeNode(key, value);
int cmp = key.compareTo(node.key);
if(cmp < 0)
node.left = insert(node.left, key, value);//在左子树中递归插入
else if(cmp > 0)
node.right = insert(node.right, key, value);//在右子树中递归插入
else
node.value = value;//在此之前树中就有该key,更新value的值
return node;
}
//采用前驱节点替代实现删除操作
public void deleteWithPreNode(String key){
root = deleteWithPreNode(root, key);
}
private TreeNode deleteWithPreNode(TreeNode node, String key){
if(node == null)
return null;
int cmp = key.compareTo(node.key);
if(cmp < 0)
node.left = deleteWithPreNode(node.left, key);
else if(cmp > 0)
node.right = deleteWithPreNode(node.right, key);
else{
if(node.left == null)
return node.right;
if(node.right == null)
return node.left;
TreeNode temp = node;
node = max(temp.left);
node.right = temp.right;
node.left = deleteMax(temp.left);
}
return node;
}
//找到前驱节点
private TreeNode max(TreeNode node){
if(node == null)
return null;
while(node.right != null)
node = node.right;
return node;
}
//删除前驱节点
private TreeNode deleteMax(TreeNode node){
if(node.right == null)
return node.left;
node.right = deleteMax(node.right);
return node;
}
//采用后继节点替代实现删除操作
public void deleteWithPostNode(String key){
root = deleteWithPostNode(root, key);
}
private TreeNode deleteWithPostNode(TreeNode node, String key){
if(node == null)
return null;
int cmp = key.compareTo(node.key);
if(cmp < 0)
node.left = deleteWithPostNode(node.left, key);
else if(cmp > 0)
node.right = deleteWithPostNode(node.right, key);
else{
if(node.left == null)
return node.right;
if(node.right == null)
return node.left;
TreeNode temp = node;
node = min(temp.right);
node.left = temp.left;//要放在调整右链接前面,因为调整右链接可能改变树的结构
node.right = deleteMin(temp.right);
}
return node;
}
//找到后继节点
private TreeNode min(TreeNode node){
if(node == null)
return null;
while(node.left != null)
node = node.left;
return node;
}
//删除后继节点
private TreeNode deleteMin(TreeNode node){
if(node.left == null)
return node.right;
node.left = deleteMin(node.left);
return node;
}
//层序遍历
private void layerTraversal(TreeNode node){
Queue<TreeNode> s = new LinkedList<>();
s.add(node);
TreeNode curNode;
TreeNode nlast = null;
TreeNode last = node;
while(!s.isEmpty()){
curNode = s.poll();
System.out.print(curNode.key+" ");
if(curNode.left != null){
s.add(curNode.left);
nlast = curNode.left;
}
if(curNode.right != null){
s.add(curNode.right);
nlast = curNode.right;
}
if(curNode == last){
System.out.println();
last = nlast;
}
}
}
public static void main(String[] args) {
BST bst = new BST();
System.out.println("请输入节点的个数:");
Scanner s = new Scanner(System.in);
int num = s.nextInt();
System.out.println("请依次输入"+num+"个字母:");
for(int i = 1 ; i <= num; i++){
String key = s.next();
bst.insert(key, i);
}
System.out.println("层序遍历:");
bst.layerTraversal(bst.root);
System.out.println();
System.out.println("测试查找操作,请输入要查询的key:");
String key = s.next();
System.out.println("键为"+key+"的键值对中的值为"+bst.search(key));
System.out.println("测试采用前驱节点替代的删除操作,请输入要删除的节点:");
String key1 = s.next();
bst.deleteWithPreNode(key1);
System.out.println("删除"+key1+"节点后,层序遍历:");
bst.layerTraversal(bst.root);
System.out.println();
System.out.println("测试采用后继节点替代的删除操作,请输入要删除的节点:");
String key2 = s.next();
bst.deleteWithPostNode(key2);
System.out.println("删除"+key2+"节点后,层序遍历:");
bst.layerTraversal(bst.root);
System.out.println();
}
}
测试用例,为了构造一个平衡二叉树(AVL,此树同时也是个满二叉树),输入顺序是先序遍历的结果。
请输入节点的个数:
7
请依次输入7个字母:
D
B
A
C
F
E
G
层序遍历:
D
B F
A C E G
测试查找操作,请输入要查询的key:
C
键为C的键值对中的值为4
测试采用前驱节点替代的删除操作,请输入要删除的节点:
G
删除G节点后,层序遍历:
D
B F
A C E
测试采用后继节点替代的删除操作,请输入要删除的节点:
E
删除E节点后,层序遍历:
D
B F
A C
参考资料;
1. 算法第四版
2. http://www.tuicool.com/articles/Uvem2y