术语:
二叉树(Binary Tree)是一种特殊的树,树中的每一个节点最多有两个子节点,这两个这两个节点一个叫做左孩子,一个叫做右孩子
二叉查找树(Binary Search Tree)是一颗二叉树,对于树上的每一个节点
x
,它的左子树上所有后代节点的值总是小于x
的值,它的右子树上所有后代的值总是大于x
的值
一颗二叉查找树
1. 基本实现:
一般的BST都是以键值对的形式记录数据,本文为了简单起见,以字符串作为键的类型,以Value
作为值的类型(Value为泛型类型,其类型在创建该树的时候指定)。感兴趣的可以看《算法(第四版)》
public class BST<Value> {
private Node root;
private class Node{
private String key;
private Value value;
private Node left;
private Node right;
Node(String key,Value value){this.key=key;this.value=value;}
}
/*关于二叉查找树的一些方法,具体实现见下文*/
//添加节点
public void add(String key,Value value){}
//根据键来获取值
public Value get(String key){return 0;}
//获取键最小的那个节点
public Node min(){return null;}
//获取键最大的那个节点
public Node max(){return null;}
//删除节点
public void delete(String key){}
//对二叉树进行中序遍历
public Iterable<String> inorderTraverse(){return null;}
}
复制代码
2. 根据键查找值:
根据键查找记录。如果树包含该键则查找命中,返回键对应的值;如果不包含则查找未命中,返回null
//根据键来获取值
public Value get(String key){
return get(root,key);
}
private Value get(Node x,String key){
//在以x节点为根节点的树中查找key
//如果能找到,则返回键对应的值,如果找不到则返回null
if(x==null) return null; //如果当前节点为null,则直接返回null
int cmp=key.compareTo(x.key); //比较当前节点和待找节点的key
if(cmp>0) return get(x.right,key); //在当前节点的右子树中继续查找
else if(cmp<0) return get(x.left,key); //在当前节点的左子树中继续查找
else return x.value; //两者的key相等,则查找命中
}
复制代码
3. 插入键值对:
给出一个键值对,如果树中没有该键,则新建一个节点并将该节点挂到查找末尾的空链接上;如果树中有该键,则更新对应的值
//插入新节点
public void add(String key,Value value){
root=add(root,key,value);
}
private Node add(Node x,String key,Value value){
//在以x节点为根节点的树中插入节点
if(x==null) return new Node(key,value); //x为空,表示已经到达树的叶子节点
//则直接新键节点并挂在空链接上
int cmp=key.compareTo(x.key); //比较将要插入的节点和节点x的key
if(cmp>0) x.right=add(x.right,key,value); //在x的右子树中插入节点
else if(cmp<0) x.left=add(x.left,key,value); //在x的左子树中插入节点
else x.value=value; //树已经有key对应的节点,则直接更新其值
return x; //返回节点x的引用;
}
复制代码
4. 获取键最小的那个节点:
如果根节点的左链接为空,则树中最小节点即为根节点
如果根节点的左链接不为空,则根节点的左子树中的最小节点即为全树的最小节点
因此我们可以根据递归来找到最小节点
获取最大节点的算法于此类似,区别就是递归节点的右子树
//获取键最小的那个节点
public Node min(){
return min(root);
}
private Node min(Node x){
if(x.left==null) return x;
return min(x.left);
}
复制代码
5. 删除键最小的那个节点:
//删除键最小的那个节点
public void deleteMin(){
if(root==null) return;
root=deleteMin(root);
}
private Node deleteMin(Node x){
if(x.left==null) x=x.right; //如果x的左子节点为空,则说明x即为将要删除的最小节点
//此时将x引用(x父节点的左链接)赋予x的右子节点
//原来的节点因为没有指向它的引用而被垃圾回收
else x.left=deleteMin(x.left);
return x;
}
复制代码
6. 删除任意节点:
根据键删除二叉树的节点的算法比较复杂,基本步骤如下:
- 找到要删除的节点,使用临时变量t指向该节点
- 找到节点t右子树中的最小节点,使用临时变量x指向该节点
- 使用deleteMin方法删除t右子树中的最小节点并恢复右子树
- x的左链接指向t的左子树,x的右链接指向t的右子树
- 将x与t的父节点建立连接
//删除节点
public void delete(String key){
root=delete(root,key);
}
private Node delete(Node x,String key){
if(x==null) return null; //未找到键值为key的节点
int cmp=key.compareTo(x.key);
if(cmp>0) x.right=delete(x.right,key); //从x的左节点删除节点
else if(cmp<0) x.left=delete(x.left,key); //从x的右节点删除节点
else{
if(x.right==null) return x.left;
if(x.left==null) return x.right;
Node t=x;
x=min(t.right);
x.right=deleteMin(t.right); //删除t的右子树中的最小节点并恢复右子树
x.left=t.left;
}
return x;
}
复制代码
7. 对二叉查找树进行中序遍历
对二叉查找树的中序遍历会得到一个根据键有序节点的集合
使用递归的方式进行遍历的代码并不复杂
//对二叉树进行中序遍历
public List<Node> inorderTraverse(){
List<Node> list=new ArrayList<>();
inorderTraverse(root,list);
return list;
}
private void inorderTraverse(Node x,List<Node> list){
if(x==null) return;
inorderTraverse(x.left,list);
list.add(x);
inorderTraverse(x.right,list);
}
复制代码
8. 在控制台中打印该二叉查找树
public void print(){
System.out.println("***************************************");
print(root,0);
System.out.println("***************************************");
}
private void print(Node x,int floor){
if(x==null) return ;
print(x.left,floor+1);
for (int i = 0; i < floor; i++) {
System.out.print("----");
}
System.out.println(x.key+" "+x.value);
print(x.right,floor+1);
}
复制代码
9. 测试用例
例子中的键为String类型而值为Integer类型。测试用例接受在控制台以$add string
的形式添加数据,string为key,对应的值为字符串添加的顺序,$del string
为删除,$show
为打印,$exit
为退出
public static void main(String[] args) {
BST<Integer> sibst = new BST<>();
int i=1;
String str;
boolean flag=true;
Scanner in = new Scanner(System.in);
String[] words;
while(flag){
str=in.nextLine();
if(str.equals("")) continue;
words = str.split(" ");
switch (words[0]){
case "$exit": {
flag=false;
break;
}
case "$add":{
sibst.add(words[1],i++);
break;
}
case "$del":{
sibst.delete(words[1]);
break;
}
case "$show":{
sibst.print();
break;
}
default:{
System.out.println("错误命令");
}
}
}
}
复制代码
10. 完整的代码
BST.java
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class BST<Value> {
private Node root;
private class Node{
private String key;
private Value value;
private Node left;
private Node right;
Node(String key,Value value){
this.key=key;
this.value=value;
}
}
//添加节点
public void add(String key,Value value){
root=add(root,key,value);
}
public Node add(Node x,String key,Value value){
//在以x节点为根节点的树中插入节点
if(x==null) return new Node(key,value); //x为空,表示已经到达树的叶子节点
//则直接新键节点并挂在空链接上
int cmp=key.compareTo(x.key); //比较将要插入的节点和节点x的key
if(cmp>0) x.right=add(x.right,key,value); //在x的右子树中插入节点
else if(cmp<0) x.left=add(x.left,key,value); //在x的左子树中插入节点
else x.value=value; //树已经有key对应的节点,则直接更新其值
return x; //更新节点x;
}
//根据键来获取值
public Value get(String key){
return get(root,key);
}
private Value get(Node x,String key){
//在以x节点为根节点的树中查找key
//如果能找到,则返回键对应的值,如果找不到则返回null
if(x==null) return null; //如果当前节点为null,则直接返回null
int cmp=key.compareTo(x.key); //比较当前节点和待找节点的key
if(cmp>0) return get(x.right,key); //在当前节点的右子树中继续查找
else if(cmp<0) return get(x.left,key); //在当前节点的右子树中继续查找
else return x.value; //两者的key相等,则查找命中
}
//获取键最小的那个节点
public Node min(){
return min(root);
}
private Node min(Node x){
if(x.left==null) return x;
return min(x.left);
}
//删除键最小的那个节点
public void deleteMin(){
if(root==null) return;
root=deleteMin(root);
}
private Node deleteMin(Node x){
if(x.left==null) x=x.right;
else x.left=deleteMin(x.left);
return x;
}
//获取键最大的那个节点
public Node max(){
return null;
}
//删除节点
public void delete(String key){
root=delete(root,key);
}
private Node delete(Node x,String key){
if(x==null) return null; //未找到键值为key的节点
int cmp=key.compareTo(x.key);
if(cmp>0) x.right=delete(x.right,key); //从x的左节点删除节点
else if(cmp<0) x.left=delete(x.left,key); //从x的右节点删除节点
else{
if(x.right==null) return x.left;
if(x.left==null) return x.right;
Node t=x;
x=min(t.right);
x.right=deleteMin(t.right);
x.left=t.left;
}
return x;
}
//对二叉树进行中序遍历
public List<Node> inorderTraverse(){
List<Node> list=new ArrayList<>();
inorderTraverse(root,list);
return list;
}
private void inorderTraverse(Node x,List<Node> list){
if(x==null) return;
inorderTraverse(x.left,list);
list.add(x);
inorderTraverse(x.right,list);
}
public void print(){
System.out.println("***************************************");
print(root,0);
System.out.println("***************************************");
}
private void print(Node x,int floor){
if(x==null) return ;
print(x.left,floor+1);
for (int i = 0; i < floor; i++) {
System.out.print("----");
}
System.out.println(x.key+" "+x.value);
print(x.right,floor+1);
}
public static void main(String[] args) {
BST<Integer> sibst = new BST<>();
int i=0;
String str;
boolean flag=true;
Scanner in = new Scanner(System.in);
String[] words;
while(flag){
str=in.nextLine();
if(str.equals("")) continue;
words = str.split(" ");
switch (words[0]){
case "$exit": {
flag=false;
break;
}
case "$add":{
sibst.add(words[1],i++);
break;
}
case "$del":{
sibst.delete(words[1]);
break;
}
case "$show":{
sibst.print();
break;
}
default:{
System.out.println("错误命令");
}
}
}
}
}
复制代码