目录
概念
BinarySeachTree,是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树.上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
总而言之就是:左子树中的所有节点值<根节点<右子树的所有节点值
二分搜索树中一般不考虑值相等的情况(元素不重复),JDK中的搜索树就不存在相同的值(TreeMap-key)
对二分搜索树进行元素的查找过程实际上就是一个二分查找,所以在进行查找时的效率是非常高的,它的时间复杂度是 logN 级别的。
向BST中添加一个元素

可以看到,新添加的元素一定是作为叶子节点存在
代码实现:
//基于整型的普通二分搜索树
public class BST {
private class TreeNode{
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val){
this.val = val;
}
}
private TreeNode root;
private int size;
//插入节点
public void add(int val){
root = addNode(root,val);
}
//向以root为根节点的BST中插入值为val的节点
public TreeNode addNode(TreeNode root,int val){
TreeNode newNode = new TreeNode(val);
if(root == null){
size ++;
return newNode;
}
if(val < root.val){
//在左子树插入
root.left = addNode(root.left,val);
}else {
//在右子树插入
root.right = addNode(root.right,val);
}
return root;
}
}
public class BSTTest {
public static void main(String[] args) {
BST bst = new BST();
int[] arr = {28,16,30,13,22,29,42};
for (int i : arr){
bst.add(i);
}
System.out.println();
}
}
使用 debug 来查看结果:
覆写 toString 方法查看结果:
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
generaBSTString(root,0,sb);
return sb.toString();
}
//前序遍历以root为根节点的BST中,将基础存储在sb中
private void generaBSTString(TreeNode root, int height, StringBuffer sb) {
if(root == null){
sb.append(generatHeightStr(height)).append("NULL\n");
return;
}
sb.append(generatHeightStr(height)).append(root.val).append("\n");
//递归访问左子树
generaBSTString(root.left,height+1,sb);
//递归访问右子树
generaBSTString(root.right,height+1,sb);
}
//按照当前所处的树的层次打印 -- ,每多一层,就多两个 --
private String generatHeightStr(int height) {
StringBuffer sb = new StringBuffer();
for(int i = 0;i < height;i++){
sb.append("--");
}
return sb.toString();
}
public class BSTTest {
public static void main(String[] args) {
BST bst = new BST();
int[] arr = {28,16,30,13,22,29,42};
for (int i : arr){
bst.add(i);
}
System.out.println(bst);
}
}
输出结果:
可以看到,16和30是第二层,所有有一组“--”,处在第三层的有两组“--”,所有generatHeightStr方法就是当进入子树时,高度就加一,就多打印一组“--”。
查找二分搜索树中是否包含值val
//查找树中是否包含值val
public boolean contains(int val){
return contains(root,val);
}
//判断以root为根节点的树中是否包含值val
private boolean contains(TreeNode root, int val) {
if(root == null){
return false;
}
if(root.val == val){
return true;
}else if (val < root.val){
//当前值小于根,去左树找
return contains(root.left,val);
}else {
return contains(root.right,val);
}
}
public static void main(String[] args) {
BST bst = new BST();
int[] arr = {28,16,30,13,22,29,42};
for (int i : arr){
bst.add(i);
}
System.out.println(bst.contains(29));
System.out.println(bst.contains(100));
}
//输出:true
false
在BST中查找最值
对于任意一颗二分搜索树,它的最小值一定在左树的最左侧。但是最小值一定在树的叶子节点吗,如下图的第二棵树,显然16不是叶子节点,所以,这句话不对。
根据BST的性质,可以得出,不断向左树递归查找,找到的第一个左子树为空的节点一定是最小值。
同理,在BST中找最大值,一定在右树的最右侧,也就是不断向右树递归查找,找到的第一个右子树为空的节点一定是最大值。
代码实现:
//找最小值
public int findMinVal(){
if(size == 0){
throw new NoSuchElementException("BST is empty!");
}
TreeNode minNode = finMinNode(root);
return minNode.val;
}
//找到以root为根节点的树的最小值
private TreeNode finMinNode(TreeNode root) {
if(root.left == null){
return root;
}else {
return finMinNode(root.left);
}
}
//找最大值
public int findMaxVal(){
if(size == 0){
throw new NoSuchElementException("BST is empty!");
}
TreeNode maxNode = findMaxNode(root);
return maxNode.val;
}
//找到以root为根节点的树的最大值
private TreeNode findMaxNode(TreeNode root) {
if(root.right == null){
return root;
}else {
return findMaxNode(root.right);
}
}
public static void main(String[] args) {
BST bst = new BST();
int[] arr = {28,16,30,13,22,29,42};
for (int i : arr){
bst.add(i);
}
System.out.println("最小值为:"+bst.findMinVal());
System.out.println("最大值为:"+bst.findMaxVal());
}
//输出:最小值为:13
最大值为:42
删除操作
1.删最值
//删除最小值,并且将该值返回
public int removeMin(){
int min = findMinVal();
root = removeMin(root);
return min;
}
//在当前以root为根的树中删除最小值节点,返回更新后的树根
private TreeNode removeMin(TreeNode root) {
if(root.left == null){
//此时根节点就是最小值
TreeNode rightNode = root.right;
// root.right = root = null;
root.right = null;
root = null;
size --;
return rightNode;
}
//去左子树中删除
root.left = removeMin(root.left);
return root;
}
public static void main(String[] args) {
BST bst = new BST();
int[] arr = {28,16,30,13,22,29,42};
for (int i : arr){
bst.add(i);
}
bst.removeMin();
System.out.println(bst);
}
输出结果:
root.right = null是为了删除16右子树的那根线,root =null为了让JVM删除16这个节点
以下面的二分搜索树为例,画出递归图,便于我们对代码的实现加深了解。
删除最大值和最小值的方法思路一致,读者可以自己去实现以下。
2.删除任意值
删除任意值时有下面这三种情况:
- 要删除的节点只有左子树
- 要删除的节点只有右子树
- 要删除的节点左右子树都有
代码实现:
//删除任意值
public void remove(int val){
root = removeVal(root, val);
}
//删除以root为根节点的树中的val值,返回删除后的新的根节点
private TreeNode removeVal(TreeNode root, int val) {
if(!contains(val)){
//树中不包含Val
throw new NoSuchElementException("BST中没有值为"+val +"val的节点");
}else if(root.val == val){
if(root.right == null){
//右子树为空,将左子树作为根节点
TreeNode leftNode = root.left;
root.left = root = null;
size--;
return leftNode;
}else if ( root.left == null){
//左子树为空,将右子树作为根节点
TreeNode rightNode = root.right;
root.right = root = null;
size --;
return rightNode;
}
//此时说明左右都不为空,去找右子树第一个大于根节点的节点
//也就是找以右子树为根节点的树的最小值
TreeNode prev = finMinNode(root.right);
//先拼接右子树
prev.right = removeMin(root.right);
//再拼接左子树
prev.left = root.left;
//删除节点root
root.left = root.right = root = null;
return prev;
}else if(val < root.val){
//去左子树找
root.left = removeVal(root.left,val);
return root;
}else {
//此时val > root.val,去右子树走
root.right = removeVal(root.right,val);
return root;
}
}
public static void main(String[] args) {
BST bst = new BST();
int[] arr = {41,58,50,60,42,53,59,63};
for (int i : arr){
bst.add(i);
}
bst.remove(58);
System.out.println(bst);
}