导航

二叉查找树

Posted on 2018-08-14 10:15  困或  阅读(210)  评论(0编辑  收藏  举报

1.定义

  二叉查找树或者是一棵空树,或者是具有下列性质的二叉树:
    1)若左子树不空,则左子树上所有结点的值均小于或等于它的根节点的值;
    2)若右子树不空,则右子树上所有结点的值均大于或等于它的根节点的值;
    3)左、右子树也分别为二叉排序树;

2.操作
  [1]查找,从根开始查找,小于根的查左子树,否则查由子树。
  [2]插入,小于根的往左子树插入,否则往右子树插入。插入的节点必定是插到最下面那一层,不会插入到树的中间位置。
  [3]删除,如果释放节点的右子树为空,则把左节点接到释放节点位置。
           如果左子树为空,则把右节点接到释放节点位置。
      如果左右都不为空,则把删除节点的左子树的最右节点(释放节点,注意这个节点的右子树是空的,因为这个节点已经是最右节点)替换到要删除的节点,然后把释放节点的左子树接到其父节点上面。
struct pnode{
    int data;
    struct pnode *lchild;
    struct pnode *rchild;
};

//查找
static struct pnode *search(struct pnode * p, int x)
{
    int find = 0;

    while (p && !find) {
        if (x == p->data) 
            find = 1
        else if (x < p->data)
            p = p->lchild;
        else
            p = p->rchild;
    }

    if (p == NULL)
        printf("not found\n");
    return p;
}

//插入
struct pnode *insert(struct pnode *root, int key)
{
    if (root == NULL){
        root = malloc(sizeof(struct pnode));
        root->lchild = root->rchild = NULL;
        root->data = key;
        return root;
    }
 
    if (key < root->data) 
        root->lchild = insert(root->lchild, key);
    else
        root->rchild = insert(root->rchild, key);
 
    return root;
}

//删除
int do_delete(struct pnode **p)
{
    struct pnode *q, s;
    
    if ((*p)->rchild == NULL) {        //右子树空,则只需要重接它的左子树
        q = *p;
        *p = (*p)->lchild;
        free(q);
    }else if ((*p)->lchild == NULL){  //左子树空,则只需要重接它的右子树    
        q = *p;
        *p = (*p)->rchild;
        free(q);
    }else{
        q = *p;
        s = (*p)->lchild;
        
        while (s->rchild) {
            q = s;
            s = s->rchild;
        }
        
        /*
            p是要删除的节点,此时用节点s覆盖此节点,然后删除s。
            s是要覆盖到p的节点。递归查找p的左子树的最右结点就是s。
            q是s的父节点。
        */
        (*p)->data = s->data;
        
        /*
            此时释放节点是没有右节点的。释放节点是其父节点的右节点。
            q != *p,表示要删除的节点和要释放的节点父节点不一样,此时要把释放节点的左子树接到释放节点的父节点的右节点。
            q == *p,表示要删除的节点和要释放的节点父节点一样,此时要把释放节点的左子树接到释放节点的父节点的左节点。
        */
        if (q != *p)
            q->rchild = s->lchild;  //因为此时s要释放掉,而s没有右节点,所以把s的左节点赋值给s父节点的右节点。
        else
            q->lchild = s->lchild;  //此时表示p的左侧
        
        free(s);
    }
    
    return 0;
}

int delete(struct pnode **root, int key)
{
    if (!*root)
        return -1;
        
    if (key == (*root)->data)
        return do_delete(root);
    else if (key < (*root)->data)
        return delete(&(*root)->lchild, key);
    else
        return delete(&(*root)->rchild, key);
}

 3.相关定义

  [1]前驱和后继,就是按照某种顺序遍历二叉树后,这个排列中位于这个节点前面和后面的结点,就是这个节点的前驱和后继。因此,找一个节点的前驱和后继,对于不同的遍历方式,会得到不同的值。

   另外,网上提到的下面的定义:

     前驱结点:节点val值小于该节点val值并且值最大的节点。
     后继节点:节点val值大于该节点val值并且值最小的节点。

   这只是在中序遍历的条件下所满足的定义。

   另外,对于各种遍历方式查找前驱和后继的方法不再描述。

   查找前驱和后继的用途:例如在二叉查找树删除时,删除节点的左右子树都不为空,这时候就是找到删除节点的前驱或后继节点(注意这里是中序遍历方式下的前驱或后继)来替换到删除的位置。

4.总结