二叉查找树(排序树)——C

一.定义

二叉查找树(Binary Search Tree),又被称为二叉搜索树。设x为二叉查找树中的一个结点,结点x的key值记为key[x]。如果y是x的左子树中的一个结点,则key[y]<=key[x];如果y是x的右子树的一个结点,则key[y]>=key[x]。
在这里插入图片描述

二.性质

在二叉查找树中:

  1. 若任意节点的左子树不为空,则左子树上的所有结点的值均小于它的根节点的值;
  2. 若任意节点的右子树不为空,则右子树上的所有结点的值均大于它的根节点的值;
  3. 任意节点的左、右子树也分别为二叉查找树。
  4. 没有键值相等的节点。

三.实现

1.节点定义

1.1节点定义

二叉查找树的节点包含的基本信息:

  1. key – 它是关键字,是用来对二叉查找树的节点进行排序的。
  2. left – 它指向当前节点的左孩子。
  3. right – 它指向当前节点的右孩子。
  4. parent – 它指向当前节点的父结点。
typedef int Type;
typedef struct BSTreeNode{
    Type key;                   //键值
    struct BSTreeNode *left;    //左孩子
    struct BSTreeNode *right;   //右孩子
    struct BSTreeNode *parent;  //父节点
}Node, *BSTree;

1.2创建节点

给节点分配空间及成员赋值:

static Node * Create_bstree_node(Type key,Node *parent,Node *left,Node *right){
    Node *p;
    if ((p == (Node *)malloc(sizeof(Node)))==NULL)  //分配内存
        return NULL;
    p->key = key;
    p->left = left;
    p->right = right;
    p->parent = parent;
    return p;
}

2. 遍历

这里只讲解先序遍历
若二叉树非空,则执行以下操作:

  1. 访问根节点;
  2. 递归先序遍历左子树;
  3. 递归先序遍历右子树。

图例介绍:
在这里插入图片描述
前序遍历结果: 3 1 2 5 4 6

void preorder_bstree(BSTree tree)       //tree作为根节点
{
    if(tree != NULL)
    {
        printf("%d ", tree->key);       //先访问根节点
        preorder_bstree(tree->left);	//递归遍历左子树
        preorder_bstree(tree->right);	//递归遍历右子树
    }
}

3. 查找

给定一个节点的key,进行查找:

  1. key与当前树x的根结点比较,若大于根节点的key值,则去左子树中查找,否则去右子树中查找。
  2. 若找到,返回该节点,否则返回空
//递归实现:
Node* bstree_search(BSTree x, Type key)
{
    if (x==NULL || x->key==key)
        return x;

    if (key < x->key)
        return bstree_search(x->left, key);
    else
        return bstree_search(x->right, key);
}
//非递归实现:
Node* iterative_bstree_search(BSTree x, Type key)
{
    while ((x!=NULL) && (x->key!=key))
    {
        if (key < x->key)
            x = x->left;
        else
            x = x->right;
    }

    return x;
}

4.查找最大值和最小值

根据性质:右子树 > 父节点 > 左子树。

  • 最大值:往右子树的右子树的方向一直寻找。若到最后某个节点没有右子树,则返回该节点(比左子树大)。
  • 最小值:往左子树的左子树的方向一直寻找。若到最后某个节点没有左子树,则返回该节点(比右子树小)。
/*非递归实现:*/
Node* bstree_maximum(BSTree tree)
{
    if (tree == NULL)
        return NULL;

    while(tree->right != NULL)
        tree = tree->right;
    return tree;
}

Node* bstree_minimum(BSTree tree)
{
    if (tree == NULL)
        return NULL;

    while(tree->left != NULL)
        tree = tree->left;
    return tree;
}

5. 前驱和后继

节点的前驱:是该节点的左子树中最大的节点(对一棵二叉树进行中序遍历,遍历后的顺序,当前节点的前一个节点为该节点的前驱节点)。
  • 若该节点没有左孩子,则该节点的前驱有以下两种可能:
    (1)若该节点是“一个左孩子”,则查找“x的最低的父节点,并且该父节点要具有右子树”,找到的这个"最低的父结点"就是"x的后继结点"。
    其中,要求“该父节点要具有右子树“,因为这是跳出while循环体的条件;
    注意:这里的”最低父节点“是根据循环体不断变化的

    (2)若该节点是”一个右孩子“,则”x的前驱节点“为”它的父节点“
节点的后继:是该节点的右子树中最小的节点(对一棵二叉树进行中序遍历,遍历后的顺序,当前节点的后一个节点为该节点的后继节点)。
  • 若该节点没有左孩子,则该节点的前驱有以下两种可能:
    (1)若该节点是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。
    (2)若该节点是"一个左孩子",则"x的后继结点"为 “它的父结点”。
//前驱
Node* bstree_predecessor(Node *x)
{
    // 如果x存在左孩子,则"x的前驱结点"为 "以其左孩子为根的子树的最大结点"。
    if (x->left != NULL)
        return bstree_maximum(x->left);

    // 如果x没有左孩子。则x有以下两种可能:
    // (01) x是"一个右孩子",则"x的前驱结点"为 "它的父结点"。
    // (01) x是"一个左孩子",则查找"x的最低的父结点,并且该父结点要具有右孩子",找到的这个"最低的父结点"就是"x的前驱结点"。
    Node* y = x->parent;
    while ((y!=NULL) && (x==y->left))
    {
        x = y;
        y = y->parent;
    }

    return y;
}
//后继
Node* bstree_successor(Node *x)
{
    // 如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。
    if (x->right != NULL)
        return bstree_minimum(x->right);

    // 如果x没有右孩子。则x有以下两种可能:
    // (01) x是"一个左孩子",则"x的后继结点"为 "它的父结点"。
    // (02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。
    Node* y = x->parent;
    while ((y!=NULL) && (x==y->right))
    {
        x = y;
        y = y->parent;
    }

    return y;
}

图例讲解:
在这里插入图片描述
中序遍历结果: 1 2 3 4 5 6
例如:要寻找节点1的前驱。

  1. 判断节点1(x)是否有左孩子,没有!
  2. 判断循环体条件,节点3(y)满足条件(x是y的左孩子,且y不为空),进入循环体,y指向y的父节点(NULL);
  3. 判断循环条件,此时y=NULL,不满足,返回y。

此时,节点1的前驱为NULL。

例如:寻找节点4的前驱。

  1. 判断节点4(x)是否有左孩子,没有!
  2. 判断循环体条件,节点5(y)满足条件(x是y的左孩子且y不为空),进入循环体,y指向y的父节点,更新y为节点3,x为节点5;
  3. 判断循环条件,节点3(y)不满足条件(x是y的右孩子!),返回节点3(y)。

此时,节点4的前驱为节点3。

6.插入

其实插入的过程,就是查找的过程,找到要插入的位置后,比较要插入的key和此位置的key,若大于此位置的key,将此位置节点的右孩子指向插入节点,否则,其左孩子指向插入节点。
  1. bstree_insert(BSTree tree, Node *z)直接传入节点。内部函数。
  2. insert_bstree(BSTree tree, Type key)先传入key值,然后函数内部新建结点,最后将建好的结点传入bstree_insert(BSTree tree, Node *z)。只是个对外接口。
static Node* bstree_insert(BSTree tree, Node *z)
{
    Node *y = NULL;
    Node *x = tree;

    // 查找z的插入位置
    while (x != NULL)
    {
        y = x;
        if (z->key < x->key)
            x = x->left;
        else
            x = x->right;
    }

    z->parent = y;
    if (y==NULL)
        tree = z;
    else if (z->key < y->key)
        y->left = z;
    else
        y->right = z;

    return tree;
}

Node* insert_bstree(BSTree tree, Type key)
{
    Node *z;    // 新建结点

    // 如果新建结点失败,则返回。
    if ((z=create_bstree_node(key, NULL, NULL, NULL)) == NULL)
        return tree;

    return bstree_insert(tree, z);
}

本文实现的二叉查找树是允许插入相同键值的节点的!

7.删除

删除有三种情况分析:
a.叶子结点:直接删除。

在这里插入图片描述

b.仅有左或右子树的结点:上移子树。

在这里插入图片描述

c.左右子树都有:用删除节点的直接前驱或者直接后继来替换当前节点,调整直接前驱或者直接后继的位置

在这里插入图片描述

static Node* bstree_delete(BSTree tree, Node *z)
{
    Node *x=NULL;
    Node *y=NULL;


    if ((z->left == NULL) || (z->right == NULL) )	//情况a:后面判断条件不考虑,直接到最后的if条件。情况b:进入下一个if条件
        y = z;								//y指向要删除的结点
    else									//情况c
        y = bstree_successor(z);			//y指向要删除结点的后继,为填充做准备

/*找到需要调整指针的子树或结点*/
    if (y->left != NULL)					//b:情况x指向子树;c情况x指向后继节点子树
        x = y->left;
    else
        x = y->right;
/*找到需要调整指针的子树或结点*/

/*调整指针*/
    if (x != NULL)							//b或c:子树不为空
        x->parent = y->parent;				//上移子树调整指针

    if (y->parent == NULL)					//上移子树调整指针
        tree = x;
    else if (y == y->parent->left)			//上移子树调整指针
        y->parent->left = x;
    else									//上移子树调整指针
        y->parent->right = x;

    if (y != z) 							//确认为情况c;
        z->key = y->key;					//后继节点y进行填充删除节点z;此时y没用了
/*调整指针*/

/*删除节点*/
    if (y!=NULL)							//删除y
        free(y);
/*删除节点*/

    return tree;
}


Node* delete_bstree(BSTree tree, Type key)
{
    Node *z, *node; 

    if ((z = bstree_search(tree, key)) != NULL)
        tree = bstree_delete(tree, z);

    return tree;
}

bstree_delete(tree, z)是内部函数,它的作用是:删除二叉树(tree)中的节点(z),并返回删除节点后的根节点。
delete_bstree(tree, key)是对外接口,它的作用是:在树中查找键值为key的节点,找到的话就删除该节点;并返回删除节点后的根节点。

8.打印

void print_bstree(BSTree tree, Type key, int direction)
{
    if(tree != NULL)
    {
        if(direction==0)    // tree是根节点
            printf("%2d is root\n", tree->key);
        else                // tree是分支节点
            printf("%2d is %2d's %6s child\n", tree->key, key, direction==1?"right" : "left");

        print_bstree(tree->left, tree->key, -1);
        print_bstree(tree->right,tree->key,  1);
    }
}

9.销毁二叉树

void destroy_bstree(BSTree tree)
{
    if (tree==NULL)
        return ;

    if (tree->left != NULL)
        destroy_bstree(tree->left);
    if (tree->right != NULL)
        destroy_bstree(tree->right);

    free(tree);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Strive_LiJiaLe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值