二叉搜索树

本文详细介绍了二叉搜索树的基本概念、属性及操作方法,包括中序遍历、查询、插入、删除等核心操作,并提供了具体的算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        二叉搜索树是基于二叉树的,树中的每个结点代表一个对象,每个结点有以下属性:key、value、p(指向父结点)、left(指向左结点)、right(指向右结点),如果某个子节点或父节点不在,则对应的属性值为NIL。同时这棵二叉树还满足以下性质:对于某个结点来说,其左子树上的所有结点的值都小于或等于该结点的值,其右子树的值都大于或等于该结点的值。

 

顺序访问

二叉搜索树的中序遍历也就是按属性变量了。

getByOrder(x){//x为根结点
    if(x != NIL){
        getByOrder(x.left);
        print x.key;
        getByOrder(x.rigth);
    }
} 

查询

查询也可以通过递归实现,或者通过循环展开递归。

search1(x,K){ //x是根节点,k是需要查找的键
    if(x==NIL || x.key == K)
        return x;
    if(x.k < key) 
        return search1(x.left);
    else
        return search2(x.rigth);
}

search2(x,k){
    while(x !=null && x.key != k){
        if(k < x.key)
            x = x.left;
        else
            x = x.right;
    }
    return x;
}

最小结点和最大结点

最小结点就是二叉搜索树的最左边的结点,最大结点就是二叉搜索树的最右边结点。

minNode(x){//x是根结点
    if x == NIL
        return x;
    while(x.left != NIL)
        x = x.left;
    return x;
}

maxNode(x){
    if x == NIL
        return x;
    while(x.right != NIL)
        x = x.right;
    return x;
}

前驱和后继

对于x前驱结点,如果x的左子树存在,就是左子树的最大结点;如果左子树不存在,x的前驱结点是父辈中第一个作为右结点的结点的父结点,如果不存在这样的父辈结点,那x就是整个数中最小的结点,前驱结点为NIL。

// 查询x前驱结点
predecessor(x){
    if(x.left != NIL){
        x = x.left;
        while(x.right!= null)
            x = x.left;
        return x;
    }
    y = x.p;
    while(y != NIL && x != y.right){
        x = y;
        y = x.p;
    }
    return y;        
}

查找x的后继结点,如果x存在右结点,那个x的后驱结点就是x的右子树的最小结点。如果不存在右子树,那么x的后驱结点就是父辈结点中第一个作为左结点的结点的父节点,不过不存在这样的结点,那么x就是整个树的最大结点,不存在右结点。

//查询 y 的后继结点
successor(x){
    if(x.right != NIL){
        x = x.right;
        while(x.left != null)
            x = x.left;
        return x;
    }
    y = x.p
    while(y!=NIL || x != y.left){
        x = y;
        y = x.p;
    }  
    return y;
}

看到有人问如果不用递归和栈遍历二叉树,网上有答案可以通过不停查找结点的后继结点遍历,这样的做法是可以的,但是这样遍历的时间和递归遍历的时间是有差别的。有n个结点的二叉搜索树,像上面所说的中序遍历时间是O(n),对于一棵高度为h的二叉搜索树,找到后继或前驱结点的时间复杂度是O(h),那么对于一棵理想的二叉搜索树,通过后继结点的方法遍历树,时间复杂度是nO(logn)。

插入

一棵非空的二叉搜索树,插入一个值时一定可以作为叶子节点插入,对于任何一个结点小于等于的都可以插入它的左子树,大于的都可以插入其右子树,这样搜索下来会找到一个叶子结点的左右结点。插入是O(h),h是书的高度。插入可能会导致更加不平衡。

insert(T,x){ //x.key是要插入的值
    if(T.root = NIL)  //如果是空树,x就是根结点
        T.root = x;
        return;
    y = NIL;
    z = T.root;
    while(z != NIL){  // while循环中查找x的父节点
        y = z;
        if(x.key <= z.key)
            z = z.left;
        else
            z = z.right;
    }
    if(x.key <= y.key) //这种情况 y.left 为空
        y.left = x;
    else
       y.right = x; //这种情况 y.right为空
    x.p = y; 
}

删除

删除有三种情况,假设x是要删除的结点:

  • x没有子节点,直接删除;
  • x有只有一个结点y,用y替x结点,y子树保持不变;
  • 如果x有两个结点,找到x的后继结点(前驱结点也是一样),用后继节点替换y,相当删除y,删除y可以使用前两种情况。
replace(T,x,y){ // 用y为根的子树替换x为根的子树(替换整个子树,x子树中的结点都不在T树中)
    if(x.p == NIL)
        T.root = y;
     else x == x.p.right
        x.p.right = y;
        y.p = x.p;
     else 
        x.p.left = y;
        y.p = x.p;
}

delete(T,x){ // 删除x结点
    if(x.left == NIL && x.right == NIL)
        x.p = NIL;                   //情况1
    if(x.left == NIL)
        replace(T,x, x.right);       //情况2
    if(x.right == NIL)
        replace(T,x, x.left);        //情况2
    else
        y = MIN(x.right)             //其实就是找到y的后继结点,有右子树情况下
        if(y.p != z) 
            replace(T, y, y.right)
            y.right = z.right;       //若y.p == z,这两句赋值导致y.right = y,y.p = y
            z.right.p = y;
        y.left = z.left;
        z.left.p = y;
        replace(T,z,y)
}

上面是通过找到该结点的后继结点删除该结点,也可以通过找到前驱结点删除。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值