一、概念及其介绍
二分搜索树(英语:Binary Search Tree),也称为 二叉查找树 、二叉搜索树 、有序二叉树或排序二叉树。满足以下几个条件:
- 若它的左子树不为空,左子树上所有节点的值都小于它的根节点。
- 若它的右子树不为空,右子树上所有的节点的值都大于它的根节点。
它的左、右子树也都是二分搜索树。
二、适用说明
二分搜索树有着高效的插入、删除、查询操作。
平均时间的时间复杂度为 O(log n),最差情况为 O(n)。二分搜索树与堆不同,不一定是完全二叉树,底层不容易直接用数组表示故采用链表来实现二分搜索树。
查找元素 | 插入元素 | 删除元素 | |
---|---|---|---|
普通数组 | O(n) | O(n) | O(n) |
顺序数组 | O(logn) | O(n) | O(n) |
二分搜索树 | O(logn) | O(logn) | O(logn) |
三、Java代码实现增删查
public class BinarySearchTree <Key extends Comparable<Key>,Value>{
// 树中的节点为私有的类, 外部不需要了解二分搜索树节点的具体实现
private class TreeNode{
public Key key;
public Value value;
public TreeNode left,right;
public TreeNode(Key key,Value value){
this.key=key;
this.value=value;
this.left=null;
this.right=null;
}
}
private TreeNode root;//根节点
private int treeSize;//树节点总数
// 构造函数, 默认构造一棵空二分搜索树
public BinarySearchTree() {
root=null;
treeSize=0;
}
public void insert(Key key,Value value){
//初次插入节点,根节点为空
if (root==null){
root=new TreeNode(key,value);
treeSize++;
}else {
this.add(root,key,value);
}
}
//插入一个节点
// 向以node为根的二分搜索树中, 插入节点(key, value), 使用递归算法
// 返回插入新节点后的二分搜索树的根
private TreeNode add(TreeNode node,Key key,Value value){
if (node==null){
node=new TreeNode(key,value);
treeSize++;
return node;
}
if (key.compareTo(node.key)==0){
node.value=value;
}else if (key.compareTo(node.key)<0){
//如果node.left或node.right为空,则会在下轮递归被第一个if处理,所以此处
//不需判断node.left或node.right为空,即null也视为二分搜索树
node.left=add(node.left,key,value);
}else {
node.right=add(node.right,key,value);
}
return node;
}
//节点查找--是否包含该节点
// 二分搜索树没有下标, 所以针对二分搜索树的查找操作, 这里定义一个 contain 方法, 判断二分搜索树是否包含某个元素,
// 返回一个布尔型变量, 这个查找的操作一样是一个递归的过程,
private boolean contain(TreeNode node,Key key){
if (node==null){
return false;
}
if (key.compareTo(node.key)==0){
return true;
}else if(key.compareTo(node.key)<0){
return contain(node.left,key);
}else {
return contain(node.right,key);
}
}
//节点查找--获取节点值
private Value select(TreeNode node,Key key){
if (node==null){
return null;
}
if (key.compareTo(node.key)==0){
return node.value;
}else if(key.compareTo(node.key)<0){
return select(node.left,key);
}else {
return select(node.right,key);
}
}
//二分搜索树遍历分为两大类,深度优先遍历和层序遍历。
//深度优先遍历分为三种:先序遍历(preorder tree walk)、中序遍历(inorder tree walk)、后序遍历(postorder tree walk),分别为:
//1、前序遍历:先访问当前节点,再依次递归访问左右子树。
//2、中序遍历:先递归访问左子树,再访问自身,再递归访问右子树。
//3、后序遍历:先递归访问左右子树,再访问自身节点。
//前序遍历
private void preOrder(TreeNode node){
if (node!=null){
System.out.print( node.value+" ");
preOrder(node.left);
preOrder(node.right);
}
}
//中序遍历
private void inOrder(TreeNode node){
if (node!=null){
inOrder(node.left);
System.out.print(node.value+" ");
inOrder(node.right);
}
}
//后序遍历
private void postOrder(TreeNode node){
if (node!=null){
postOrder(node.left);
postOrder(node.right);
System.out.print(node.value+" ");
}
}
//层序遍历:二分搜索树的层序遍历,即逐层进行遍历,即将每层的节点存在队列当中,
// 然后进行出队(取出节点)和入队(存入下一层的节点)的操作,以此达到遍历的目的。
private void levelOrder(TreeNode root){
if(root!=null){
// 我们使用LinkedList来作为我们的队列
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
TreeNode treeNode = queue.removeFirst();
System.out.print(" " + treeNode.value);
if (treeNode.left!=null){
queue.add(treeNode.left);
}
if (treeNode.right!=null){
queue.add(treeNode.right);
}
}
}
}
// 查找以node为根的二分搜索树的最小键值所在的节点:在node左子树中递归找到不存左子树的节点
//该节点则为最小,最大值与此类似
private TreeNode minNode(TreeNode treeNode){
if (treeNode.left==null){
return treeNode;
}
return minNode(treeNode.left);
}
//找最大值
private TreeNode maxNode(TreeNode treeNode){
if (treeNode.right==null){
return treeNode;
}
return minNode(treeNode.right);
}
// 删除掉以node为根的二分搜索树中键值为key的节点, 递归算法
// 返回删除节点后新的二分搜索树的根
private TreeNode removeNode(TreeNode node,Key key){
//删除操作的基础是查找
if (node==null){
return null;
}
if (key.compareTo(node.key)<0){
node.left=removeNode(node.left,key);
return node;
} else if (key.compareTo(node.key)>0){
node.right=removeNode(node.right,key);
return node;
} else {//key==node.key
//如果目标节点的左子树为空,只有右子树,则让右子树节点代替目标节点,整个搜索树性质不变
if (node.left==null){
node=node.right;
treeSize--;
return node;
}else if (node.right==null){
//如果目标节点的右子树为空,只有左子树,则让左子树节点代替目标节点,整个搜索树性质不变
node=node.left;
treeSize--;
return node;
}else {//左右子树都有:找到右子树中最小的节点替代目标节点
TreeNode minNode = minNode(node.right);
//然后删除右子树最小节点
node.right=removeNode(node.right,minNode.key);
node.key= minNode.key;
node.value= minNode.value;
return node;
}
}
}
public static void main(String[] args) {
BinarySearchTree searchTree = new BinarySearchTree();
Integer[] arr=new Integer[]{41,22,58,15,33,50,60,13,37,42,53,59,63};
for (Integer integer:arr ) {
System.out.println("插入数据"+integer );
searchTree.insert(integer,integer);
}
/* System.out.println("searchTree = " + searchTree.root.value);
System.out.println("isContain = " + searchTree.contain(searchTree.root, 62));
System.out.println("select = " + searchTree.select(searchTree.root, 13));
searchTree.preOrder(searchTree.root);
System.out.println(" ");
searchTree.inOrder(searchTree.root);
System.out.println(" ");
searchTree.postOrder(searchTree.root);
System.out.println(" ");*/
System.out.println("数据插入完成");
searchTree.levelOrder(searchTree.root);
System.out.println("\n"+"删除");
searchTree.removeNode(searchTree.root, 60);
searchTree.levelOrder(searchTree.root);
}
}