数据结构——搜索二叉树(BST)

一、BST的基本概念

        二叉树的其中一个重要的应用,是提供一种快速查找数据的方法,即将数据节点按照某种规律形成一棵二叉树,然后利用二叉树特殊的逻辑结构减少搜索数据的次数,提高查找的效率。

        这种按照某种规律构建,用来提高搜索性能的二叉树,被称为搜索二叉树(Binary Search Tree),即BST。

        具体来说,二叉树提高搜索效率的秘诀在于:按照” 小 - 中 - 大 ”(“ 大 - 中 - 小 ”也是可以的)的规律来存储数据,即对于任意一个节点,都可以明确找到其值 ≥ 其左孩子节点,且 ≤ 其右孩子节点。

如图:

        由于树中所有的节点均满足“ 小 - 中 - 大 ”的规律,因此当从根节点开始查找某个节点时,速度比顺序查找要快得多,例如要找节点38,当发现 38 大于根节点 13 后,就可以确定 13 的左子树一定没有 38 ,这就去掉了半边子树的节点。

        因此,二叉搜索树又被称为二叉排序树、二叉查找树。

        实际上,对于一棵二叉树而言,其搜索节点的时间复杂度,最糟糕的情形是其退化为链表,最乐观的情形是完美或完全二叉树,那么其搜索时间复杂度就是介于:

二、BST的基本操作

(一)节点设计

图解:

示例代码:

// 二叉树的节点设计
typedef struct node
{
    // 左子树指针
    struct node *lchild;

    // 数据域
    int *data;

    // 右子树指针
    struct node *rchild;

}node_t, *node_p, binary_tree;

(二)初始化二叉树(包括根节点、或者其它树节点都可以)

图解:

示例代码:

/**
 * @brief    初始化树节点
 * @note     None
 * @param    data:根节点的数据
 * @retval   成功:返回指向这个根的数据节点的指针
 *           失败:返回NULL
 */ 
node_p BINARY_TREE_InitNode(int *data) 
{
    // 1、申请堆内存空间
    node_p p = malloc(sizeof(node_t));
    bzero(p, sizeof(node_t));

    // 2、对堆空间进行赋值操作
    if ( p != NULL )
    {
        // 数据域
        p->data    = malloc(sizeof(int));
        p->data[0] = *data;

        // 指针域
        p->lchild = NULL;        // 左子树指针
        p->rchild = NULL         // 右子树指针
    }
    else
        return NULL;

    // 3、成功返回p
    return p;
    
}

(三)插入数据

        对于BST来说,插入一个节点主要是要保持其“ 小 - 中 - 大 ”的逻辑不变,因此插入的节点的逻辑可以从根节点开始,一步步寻找新节点的“ 最终归宿 ”,例如在如下BST中,要插入新节点16,最终应该插入到节点17的左孩子处。

        在实现插入算法时,由于树状结构本身是递归的,因此可以使用递归函数更优雅地实现。

图解:

示例代码:

/**
 * @brief    插入数据 
 * @note     以二叉排序的方式进行插入(比根节点数据大的放右边,小的放左边)
 * @param    root_node: 根节点
 *           new_node:  要插入的新节点 
 * @retval   成功:返回指向这个二叉树根节点的指针
 *           失败:返回NULL
 */ 
node_p BINARY_TREE_InsertData(node_p root_node, node_p new_node) 
{
    // 1、设置退出条件
    if (root_node == NULL)
        return new_node;
    
    // 2、比较数据的大小,看数据要插入左子树,还是右子树
    // a、插入到左子树
    if ( new_node->data[0] < root_node->data[0])
    {
        root_node->lchild = BINARY_TREE_InsertData(root_node->lchild, new_node);
    }
    
    // b、插入到右子树
    else if ( new_node->data[0] > root_node->data[0])
    {
        root_node->rchild = BINARY_TREE_InsertData(root_node->rchild, new_node);
    }
    
    else
    {
        printf("%d的数据已经存在了,请重新输入\n", new_node->data[0]);
        free(new_node);
    }

    // 3、返回根节点
    return root_node;
}

(四)删除数据

        删除一个BST的节点同样要遵循一个原则,即删除节点后仍要保持“ 小 - 中 - 大 ”的逻辑关系。

        假设要删除的节点是x,思路:

        1. 若要删除的节点 < 根节点,则递归地在左子树中删除x;

        2. 若要删除的节点 > 根节点,则递归地在右子树中删除x;

        3. 若要删除的节点恰好就是根节点,则分如下几种情况:

        (1)根节点若有左子树,则用左子树中最大的节点 max 替换根节点,并在左子树中递归删除max;

        (2)根节点若有右子树,则用右子树中最小的节点 min 替换根节点,并在右子树中递归删除min;

        (3)根节点没有子树,直接删除。

        如图,假设在一棵二叉树中要删除节点 15 ,在找到节点之后,判断其有左子树,那么就沿着其左子树找到最右下角(最大)的节点 12 ,替换要删除的节点 15 ,然后再将多余的节点 12 删掉。

        如果要删除的节点没有左子树,只有右子树,那么情况是完全对称的,如下图,假设要删除节点 25 ,由于 25 没有左子树,因此找到其右子树中最左下角(最小)的节点 26 ,替换要删除的节点 25 ,然后再将多余的节点 26 删掉。

图解:

示例代码:

/**
 * @brief    删除数据
 * @note     None
 * @param    root_node: 根节点
 *           del_data:   要删除的数据
 * @retval   成功:返回指向这个二叉树根节点的指针
 *           失败:返回NULL
 */ 
node_p BINARY_TREE_DelData(node_p root_node, int del_data)
{
    // 1、如果根节点为空,直接返回NULL即可
    if (root_node == NULL)
        return NULL;

    // 2、若del_data小于根节点,则递归地在左子树上删除它
    if ( del_data < *(root_node->data))
    {
        root_node->lchild = BINARY_TREE_DelData(root_node->lchild, del_data);
    }
    
    // 3、若del_data大于根节点,则递归地在右子树中删除它
    else if (del_data > *(root_node->data))
    {
        root_node->rchild = BINARY_TREE_DelData(root_node->rchild, del_data);
    }
    
    // 4.若要删除的节点恰好就是根节点,则分如下几种情况:
    else
    {
        // a、根节点若有左子树,则用左子树中最大的节点max替换根节点,并在左子树中递归删除max
        if (root_node->lchild != NULL)
        {
            // 建立中间值(树节点)(左子树最大的节点的值)
            node_p max_node = NULL;

            // 找到左子树最大值
            for (max_node = root_node->lchild; max_node->rchild != NULL; max_node = max_node->rchild);

            // 将要删除的根节点,和其左子树中的最大值进行交换
            *(root_node->data) = *(max_node->data);

            // 将左子树中的最大值删除
            root_node->lchild = BINARY_TREE_DelData(root_node->lchild, *(max_node->data));
            
        }
        
        // b、根节点若有右子树,则用右子树中最小的节点min替换根节点,并在右子树中递归删除min
        else if (root_node->rchild != NULL)
        {
            // 建立中间值(树节点)(左子树最大的节点的值)
            node_p min_node = NULL;

            // 找到右子树最大值
            for (min_node = root_node->rchild; min_node->lchild != NULL; min_node = min_node->lchild);

            // 将要删除的根节点,和其右子树中的最小值进行交换
            *(root_node->data) = *(min_node->data);

            // 将右子树中的最小值删除
            root_node->rchild = BINARY_TREE_DelData(root_node->rchild, *(min_node->data));
        }
        
        else
        {   
            free(root_node->data);
            free(root_node);
            return NULL;
        }
    }
    
    // 3、返回root_node
    return root_node;
}

    (五)遍历数据

    图解:

    示例代码:

    /**
     * @brief  前序遍历
     * @note   None
     * @param  root_node: 根节点
     * @retval None
     */ 
    void BINARY_TREE_PreorderShow(node_p root_node) 
    {
        // 1、如果根节点为空,那就证明执行完了这个二叉树(退出条件)
        if (root_node == NULL)
            return ;
    
        // 2、先访问根节点
        printf("%d\t", root_node->data[0]);           // 根  
    
        // 3、再次使用前序遍历,遍历左子树、右子树
        BINARY_TREE_PreorderShow(root_node->lchild);  // 左
        BINARY_TREE_PreorderShow(root_node->rchild);  // 右
        
    }
    
    /**
     * @brief  中序遍历
     * @note   None
     * @param  root_node: 根节点
     * @retval None
     */ 
    void BINARY_TREE_InfixOrderShow(node_p root_node) 
    {
        // 1、如果根节点为空,那就证明执行完了这个二叉树(退出条件)
        if (root_node == NULL)
            return ;
    
        // 2、
        BINARY_TREE_InfixOrderShow(root_node->lchild);  // 左
        printf("%d\t", root_node->data[0]);             // 根  
        BINARY_TREE_InfixOrderShow(root_node->rchild);  // 右
        
    }
    
    /**
     * @brief  后序遍历
     * @note   None
     * @param  root_node: 根节点
     * @retval None
     */ 
    void BINARY_TREE_EpilogueShow(node_p root_node) 
    {
        // 1、如果根节点为空,那就证明执行完了这个二叉树(退出条件)
        if (root_node == NULL)
            return ;
    
        // 2、
        BINARY_TREE_EpilogueShow(root_node->lchild);  // 左
        BINARY_TREE_EpilogueShow(root_node->rchild);  // 右
        printf("%d\t", root_node->data[0]);           // 根  
        
    }
    
    
    /**
     * @brief  按层遍历
     * @note   利用的是队列对其数据进行处理
     * @param  root_node: 根节点
     * @retval None
     */ 
    void BINARY_TREE_FloorShow(node_p root_node) 
    {
        // 1、如果根节点为空,那么就证明执行完了这个二叉树
        if (root_node == NULL)
            return;
    
        // 2、将根节点的数据入队
        link_queue_p p = init_queue();  // 初始化队列
        en_queue(p, root_node);         // 将根节点的数据入队
    
        // 3、
        node_p tmp_p = NULL;
        while (!is_empty_q(p))          // 如果链式队列不是空的话,就一直执行下去
        {
            // a、出队并访问队头
            out_queue(p, &tmp_p);
            printf("%d\t", *(tmp_p)->data);
    
            // b、依次将左右子树入队
            if ( tmp_p->lchild != NULL) 
            {
                en_queue(p, tmp_p->lchild);
            }
            
            else if (tmp_p->rchild != NULL)
            {
                en_queue(p, tmp_p->rchild);
            }
        }
    }

      (六)查改数据

      图解:

      示例代码:

      /**
       * @brief  查改数据
       * @note   None
       * @param  root_node: 根节点
       *         find_data:  要查找的数据
       * @retval None
       */ 
      int* BINARY_TREE_FindeData(node_p root_node, int find_data) 
      {
          // 1、如果根节点为空,那么就证明执行完了这个二叉树
          if (root_node == NULL)
              return NULL;
      
          // 2、将根节点的数据入队
          link_queue_p p = init_queue();  // 初始化队列
          en_queue(p, root_node);         // 将根节点的数据入队
      
          // 3、
          node_p tmp_p = NULL;
          while ( !is_empty_q(p) )          // 如果链式队列不是空的话,就一直执行下去
          {
              // a、出队并访问队头
              out_queue(p, &tmp_p);
              
              if (*(tmp_p)->data == find_data)
              {
                  return &(*(tmp_p)->data);
              }
              
              // b、依次将左右子树入队
              else if ( tmp_p->lchild != NULL) 
              {
                  en_queue(p, tmp_p->lchild);
              }
              
              else if (tmp_p->rchild != NULL)
              {
                  en_queue(p, tmp_p->rchild);
              }
          }
      }

      (七)销毁二叉树

      图解:

        示例代码:

        /**
         * @brief  销毁二叉树
         * @note   None
         * @param  root_node: 根节点
         * @retval None
         */ 
        void BINARY_TREE_UnInit(node_p root_node) 
        {
            // 1、如果根节点为空,则直接返回即可
            if (root_node == NULL)
                return;
        
            // 2、递归的销毁左子树
            BINARY_TREE_UnInit(root_node->lchild); 
        
            // 3、递归的销毁右子树
            BINARY_TREE_UnInit(root_node->rchild); 
        
            // 4、销毁根节点
            free(root_node->data);
            free(root_node);
        
        }

        (网页查看链表情况)

                提示:为了检验创建出来的二叉树是否正确,可以使用如下代码将二叉树以网页形式展现出来:

        #define TREENODE node // 声明自定义的二叉树节点为TREENODE
        #include "drawtree.h" // 包含画树代码
        
        int main()
        {
            ...
        
            // 以网页形式展现二叉树
            draw(root);
        }

                其中,node是自己定义的二叉树的节点的类型,在包含 drawtree.h 之前定义 TREENODE 这个宏为 node ,即可展示对应二叉树。

        点击下载:drawtree.h

        以上内容就是我的课后笔记了。

        好的,我可以为您实现搜索二叉树BST)以及相关的操作。 首先,让我们定义一个BST节点的结构体: ``` typedef struct node { int key; struct node *left; struct node *right; } node_t; ``` 这个结构体包括了节点的值(key)以及左右子节点的指针。现在我们可以定义一个BST的结构体,这个结构体包含了BST的根节点指针: ``` typedef struct bst { node_t *root; } bst_t; ``` 现在,我们可以实现以下操作: 1. 创建一个新的节点 ``` node_t *new_node(int key) { node_t *node = (node_t *) malloc(sizeof(node_t)); node->key = key; node->left = NULL; node->right = NULL; return node; } ``` 2. 向BST中插入一个节点 ``` void insert(bst_t *bst, int key) { node_t *parent = NULL; node_t *current = bst->root; while (current != NULL) { parent = current; if (key < current->key) { current = current->left; } else { current = current->right; } } node_t *node = new_node(key); if (parent == NULL) { bst->root = node; } else if (key < parent->key) { parent->left = node; } else { parent->right = node; } } ``` 3. 获取BST的最小值 ``` node_t *min_value(node_t *node) { while (node->left != NULL) { node = node->left; } return node; } int get_min(bst_t *bst) { return min_value(bst->root)->key; } ``` 4. 获取BST的最大值 ``` node_t *max_value(node_t *node) { while (node->right != NULL) { node = node->right; } return node; } int get_max(bst_t *bst) { return max_value(bst->root)->key; } ``` 5. 在BST搜索一个节点 ``` node_t *search(node_t *node, int key) { if (node == NULL || node->key == key) { return node; } if (key < node->key) { return search(node->left, key); } return search(node->right, key); } node_t *get_node(bst_t *bst, int key) { return search(bst->root, key); } ``` 6. 获取一个节点的前驱节点 ``` node_t *predecessor(node_t *node) { if (node == NULL) { return NULL; } if (node->left != NULL) { return max_value(node->left); } node_t *parent = node->parent; while (parent != NULL && node == parent->left) { node = parent; parent = parent->parent; } return parent; } node_t *get_predecessor(bst_t *bst, int key) { node_t *node = get_node(bst, key); if (node == NULL) { return NULL; } return predecessor(node); } ``` 7. 前序遍历BST ``` void preorder(node_t *node) { if (node != NULL) { printf("%d ", node->key); preorder(node->left); preorder(node->right); } } void traverse_preorder(bst_t *bst) { preorder(bst->root); } ``` 现在,我们已经成功地实现了搜索二叉树以及相关操作。
        评论
        添加红包

        请填写红包祝福语或标题

        红包个数最小为10个

        红包金额最低5元

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

        抵扣说明:

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

        余额充值