算法与数据结构系列之[二分搜索树-上]

前几篇介绍了二叉树以及二叉树的遍历,接下来三篇介绍下二分搜索树。

1.什么是二分搜索树

二分搜索树(Binary Search Tree)是一种特殊的二叉树,也叫二叉排序树或二叉查找树,简称:BST。顾名思义,二分搜索树可以通过二分法快速实现元素的查找的,而且,二分搜索树插入和删除元素的效率也很高。

二分搜索树或者树一棵空树,获取满足下面性质:

  • 若左子树不为空,则左子树上所有节点的值均小于其根节点的值
  • 若有子树不为空,则右子树上所有节点的值均大于其根节点的值
  • 左 右子树也分别为二分搜索树‘’

如下图是一棵二分搜索树:

在这里插入图片描述
2.添加元素

二分搜索树添加元素时可以采用递归算法,也可以用非递归的方法,我参考的大多数书籍都是非低递归的方法,这里我习惯于采用递归方法。添加一个元素时,先看树是否为空,如果为空,就将要添加的元素添加到根节点的位置。如果树不为空,先和根节点进行比较,如果小于根节点的左子节点比较,若是根节点的左子节点为空,就插入到根节点左子节点的位置,不为空就接着向下递归。如果大于根节点就在根节点的右子树递归,直到找到空的位置插入。

比如要插入的元素为28,28先和根节点比较,小于根节点,所以要的根节点的左子树上递归,又28小于58,再和58的左子节点46比较,小于46,再和46的左子节点30比较,小于30,和30的左子节点比较,又30的左子节点为空,所以28添加到30的左子节点的位置。

在这里插入图片描述
代码实现:

BinarySearchTree add(BinarySearchTree bst,int e){

   if(!bst){
       size++;
       bst = malloc(sizeof(BinarySearchNode));
       bst->e = e;
       bst->left = bst->right = NULL;
       return bst;
   }

   if(e < bst->e)
      bst->left = add(bst->left,e);
   else if (e > bst->e)
      bst->right = add(bst->right,e);
   return bst;
}

void Insert(int e){
   root = add(root,e);
}

3.二分搜索树的查找操作

首先,我们看如何在二叉查找树中查找一个节点。我们先取根节点,如果它等于我们要查找的数据,那就返回。如果要查找的数据比根节点的值小,那就在左子树中递归查找;如果要查找的数据比根节点的值大,那就在右子树中递归查找。

代码实现:

int FindBST(BinarySearchTree bst,int e){
   if(bst == NULL)
       return 0;
   if(e == bst->e)
       return 1;
   else if(e < bst->e)
       return FindBST(bst->left,e);
   else
       return FindBST(bst->right,e);
}

int  Contains(int e){
   return FindBST(root,e);
}

4.二分搜索树的删除操作

二分搜索树的添加和查找操作都相对简单,但删除操作就相对复杂了。针对要删除节点的子节点的个数,我们需要考虑如下三种情况:(当然在代码实现层面,一二两种情况可以合并,没有子节点可以理解为左右子节点为空)

第一种情况是,如果要删除节点没有子节点,只需要将该节点直接删除即可,也就是直接将父节点中指向该节点的指针删除掉即可。比如下图中删除节点66.

第二种情况是,要删除的节点只有一个子节点,我们只需要更新父节点中指向删除节点的指针,让它指向要删除节点的子节点即可。比如删除节点30.

第三种情况是,如果要删除的节点左右子节点都有,这时就比较复杂,我们需要找到要删除的这个节点的后继,即这个节点的右子树中的最小节点,把它替换到要删除的节点上。比如删除下图中的88

在这里插入图片描述
接下来我们分成三步来实现二分搜索树的删除操作

第一步:删除二分搜索树的最小元素

//删除二分搜索树中的最小元素
BinarySearchTree RemoveMinBST(BinarySearchTree bst){
   if(bst->left == NULL){ //只有右子节点,没有左子节点的情况
       BinarySearchTree rightNode = bst->right;
       bst->right = NULL;
       size--;
       return rightNode;
   }
   bst->left =  RemoveMinBST(bst->left);
   return bst;
}

int RemoveMin(){
   int min = Minimum();//Minimum方法返回二分搜索树的最小元素
   root = RemoveMinBST(root);
   return min;
}

第二步:删除二分搜索树的最大元素

BinarySearchTree RemoveMaxBST(BinarySearchTree bst){
   if(bst->right == NULL){ //只有左子节点没有右子节点的情况
       BinarySearchTree leftNode = bst->left;
       bst->left = NULL;
       size--;
       return leftNode;
   }

   bst->right = RemoveMaxBST(bst->right);
   return bst;
}

int RemoveMaxmum(){
   int max = Maxmum();//Maxmum方法返回二分搜索树的最大元素
   root = RemoveMaxBST(root);
   return max;
}

第三步:删除二分搜索树中的任意元素

BinarySearchTree RemoveBST(BinarySearchTree bst,int e){
   if(bst == NULL)
       return NULL;
   if(e < bst->e){
       bst->left = RemoveBST(bst->left,e);
       return bst;
   }
   else if(e > bst->e){
       bst->right = RemoveBST(bst->right,e);
       return bst;
   }
   else{ //e == node.e
       //待删除节点的左子树为空,右子树不为空
       if(bst->left == NULL){ //只有右子节点,没有左子节点的情况
           BinarySearchTree rightNode = bst->right;
           bst->right = NULL;
           size--;
           return rightNode;
       }

       //待删除节点的右子树为空,左子树不为空
       if(bst->right == NULL){ //只有左子节点没有右子节点的情况
           BinarySearchTree leftNode = bst->left;
           bst->left = NULL;
           size--;
           return leftNode;
       }

       //待删除节点的左右子树都不为空
       BinarySearchTree successor = MinimumBST(bst->right); //successor  待删除节点的后继,也就是待删除节点右子树的最小节点
       successor->right = RemoveMinBST(bst->right);//不用再size--,因为RemoveMinBST中已经执行了size
       successor->left = bst->left;
       bst->right = bst->left = NULL;
       return successor;
   }
}

void Remove(int e){
  root =  RemoveBST(root,e);
}

对待删除节点都不为空的图解

在这里插入图片描述
5.二分搜索树的非递归前序遍历

在介绍二叉树的时候详细介绍了前、中、后序遍历的递归实现,代码相对简单,但是非递归实现相对复杂一些,尤其是中、后序遍历的非递归实现。这了我们只介绍一下前序遍历的非递归实现。

前序遍历的非递归实现依然是先输出当前节点,在遍历当前节点的左子树,在遍历当前节点的右子树,只是需要将要操作的元素依次放入栈,输出一个元素就相当于出栈一个元素。

如图,先将根节点28入栈,再输出28,然后依次将28的右子节点和左子节点入栈,接着16出栈,再依次将16的右孩子和左孩子入栈,接着13,22依次出栈,再将30的右孩子和左孩子依次入栈,接着29,42依次出栈,此时栈里只剩下30这个节点,将30出栈即可,之后栈为空,遍历结束。

在这里插入图片描述
代码实现(java代码):

public void preOrderNR(){
   Stack<Node> stack = new ArrayStack<>();
   stack.push(root);
   while (!stack.isEmpty()){
       Node cur = stack.pop();
       System.out.print(cur.e);
       if(cur.right != null)
           stack.push(cur.right);
       if(cur.left != null)
           stack.push(cur.left);
   }
}

6.二分搜索树的层序遍历

二分搜索树的层序遍历,也就是一层一层的遍历,是按广度优先策略遍历,而二分搜索树的前、中、后序遍历都是按深度优先策略遍历。二分搜索树的层序遍历可以用队列实现,每次遍历对应队列的入队和出队,与前序遍历非递归实现不同的是,层序遍历每次子节点入队时,先入队左子节点,再入队右子节点。

在这里插入图片描述
代码实现(java代码):

public void levelOrder(){
    Queue<Node> queue = new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()){
        Node cur = queue.remove();
        System.out.print(cur.e);
        if(cur.left != null)
            queue.add(cur.left);
        if(cur.right != null)
            queue.add(cur.right);
    }
}

7.总结

这篇介绍了二分搜索树的概念,以及添加,查找,删除,遍历等操作。其中删除操作比较复发一些。接下来的两篇将二分搜索树的完整代码贴出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值