二叉排序树结点的插入与删除操作

独自空忆成欢 提交于 2020-02-28 14:33:36

二叉排序树结点的插入与删除操作

一 二叉排序树的性质

  二叉排序树,又称二叉搜索树,它最重要的性质就是:根结点左子树中所有结点的值均小于根结点值,右子树中所有结点的值都大于根结点的值,所以我们在中序遍历这棵二叉树时,将会得到一个升序序列,这也是我们验证二叉排序树的一个手段。
  对应的数据结构定义为:

typedef struct Node {  //一个数据域和左右两个指针域
    int data;
    struct Node *lchild, *rchild;
} Node;

二叉排序树

二 二叉树结点的插入

  1. 首先将待插入结点的值与根结点的值作比较,若val == root->data,此时我们直接返回当前root指针,因为我们规定,二叉排序树中不存在值相同的结点。
  2. 若待插入结点的值小于根结点的值,此时我们应该进入根结点的左子树
  3. 若待插入结点的值大于根结点的值,此时我们应该进入根结点的右子树
  4. 若指针为空,此时我们根据传入的值创建结点,并返回创建的结点指针
Node *getNewNode(int val) {
    Node *node = (Node *)malloc(sizeof(Node));
    node->data = val;
    node->lchild = node->rchild = NULL;
    return node;
}
//参数:二叉搜索树的根结点以及插入的值
//返回值:插入后二叉树的根结点
Node *insert(Node *root, int val) {
    if (!root) return getNewNode(val);
    if (val == root->data) return root;
    else if (val < root->data) {
        root->lchild = insert(root->lchild, val);
    } else {
        root->rchild = insert(root->rchild, val);
    }
    return root;
}

三 二叉树结点的删除

  其实排序二叉树结点的插入很容易,但是结点的删除却并不是这样的简单了,因为当我们删除结点之后,我们需要妥善的处理这个结点左右子树中的结点,使得整个二叉排序树的升序的性质不会被破坏,那我们应该怎么做呢?似乎很难!
  我们将问题进行分解,我们知道二叉树中结点可以根据出度进行划分为:度为0的结点 (也就是叶子结点)、度为1的结点、度为2的结点。

  1. 首先我们来讨论度为0和1结点
    对于度为0的结点,因为没有子结点,所以我们可以直接删除 (一个人在世上,走了就是走了,没有家眷需要安排)
    对于度为1的结点,我们假设是它是它父亲结点的左孩子,那么不论是它的左孩子还是他的右孩子,都要比它父亲结点的值要小,所以我们可以直接将它的左孩子 (或者是右孩子,注意这是度为1的结点,所以要么是左孩子要么是右孩子) 顶替自己的位置,成为它父亲结点的子孩子;同理若它是它父结点的右孩子,那么不论是它的左孩子还是他的右孩子,都要比它父亲结点的值要大,我们同样可以子结点二选一,顶替自己的位置。
  2. 度为2的结点
    讨论了删除度为0和1的结点,现在我们来想一想如何删除度为2的结点?
    我们来看这样一副图:
    删除度数为2的结点
      如上图所示:我们现在要删除的是值为20的结点,也就是整个树的根结点,此时我们的目光 不仅转移到值为19和28的这两个结点,因为这两个结点的值,是最靠近20的。我们可以用19或者是28结点来顶替20结点,之后再进行调整。
      其实值为19和28的结点,在二叉树的中序线索化中是20结点的前驱和后继结点(一个结点的前驱结点和后继结点的找寻大家需要知道,前驱结点是这个结点左子树右分支尽头的那个结点,后继结点是右子树左分支尽头的那个结点),现在假设我们使用前驱结点也就是19结点,我们可以找到交换19和20的值,也就是交换该结点和前驱结点俄数值域,之后,我们显然需要在左子树中删除原来的19结点,我们不难知道值为19的结点一定是度为0或者是度为1的结点,为什么?很简单,因为如果他还是度为2的结点,那这个结点一定不是左子树右分支的尽头的那个结点,与前驱结点的定义不满足。
      至此,我们将删除度为2的结点问题转换为删除度为0或1的结点的问题!
    代码实现:
Node *get_pre_node(Node *root) {  //返回root结点的前驱结点
    Node *ret = root->lchild;
    while (ret->rchild) ret = ret->rchild;
    return ret;
}

Node *erase(Node *root, int val) {
    if (!root) return root;
    if (val < root->data) root->lchild = erase(root->lchild, val);
    else if (val > root->data) root->rchild = erase(root->rchild, val);
    else {
        if (!root->lchild || !root->rchild) {		//若待删除结点是度为0或1的结点
            Node *tmp = root->lchild ? root->lchild : root->rchild;
            free(root);
            return tmp;
        } else {  //待删除的结点是度为2的结点
            Node *tmp = get_pre_node(root);  //找到前驱结点
            root->data = tmp->data;  //交换前驱结点和待删除结点的值
            root->lchild = erase(root->lchild, tmp->data);  //递归删除前驱结点
        }
    }
    return root;
}

四 总代码

程序中我们规定:输入:1 val 表示向二叉排序树中插入值为val的结点,2 val表示删除值为val的结点
为了验证二叉排序树是否正确,我们采用中序遍历的方式,若中序序列始终升序,说明没问题
本内容只包含二叉排序树的插入和删除结点的操作,若看官对二叉树的其他基本操作感兴趣的化,可以查看小编的另外一片博客 二叉树的构建、遍历、转广义表等基本操作

#include <iostream>
using namespace std;

typedef struct Node {
    int data;
    struct Node *lchild, *rchild;
} Node;

Node *getNewNode(int val) {
    Node *node = (Node *)malloc(sizeof(Node));
    node->data = val;
    node->lchild = node->rchild = NULL;
    return node;
}

Node *insert(Node *root, int val) {
    if (!root) return getNewNode(val);
    if (val == root->data) return root;
    else if (val < root->data) {
        root->lchild = insert(root->lchild, val);
    } else {
        root->rchild = insert(root->rchild, val);
    }
    return root;
}

Node *get_pre_node(Node *root) {
    Node *ret = root->lchild;
    while (ret->rchild) ret = ret->rchild;
    return ret;
}

Node *erase(Node *root, int val) {
    if (!root) return root;
    if (val < root->data) root->lchild = erase(root->lchild, val);
    else if (val > root->data) root->rchild = erase(root->rchild, val);
    else {
        if (!root->lchild || !root->rchild) {
            Node *tmp = root->lchild ? root->lchild : root->rchild;
            free(root);
            return tmp;
        } else {
            Node *tmp = get_pre_node(root);
            root->data = tmp->data;
            root->lchild = erase(root->lchild, tmp->data);
        }
    }
    return root;
}

void in_order_1(Node *root) {
    if (!root) return ;
    in_order_1(root->lchild);
    printf("%d ", root->data);
    in_order_1(root->rchild);
}
void in_order(Node *root) {
    if (!root) return ;
    printf("in_order: ");
    in_order_1(root);
    printf("\n");
}


void clear(Node *root) {
    if (!root) return ;
    clear(root->lchild);
    clear(root->rchild);
    free(root);
}


int main() {
    Node *root = NULL;
    int op, data;
    while (~scanf("%d %d", &op, &data)) {
        switch (op) {
            case 1: {
                printf("insert %d into binary_sort_tree\n", data);
                root = insert(root, data);
                break;
            }
            case 2: {
                printf("erase %d from binary_search_tree\n", data);
                root = erase(root, data);
                break;
            }
            default: printf("input wrong\n");
        }
        in_order(root);
    }
    clear(root);
    return 0;
}

/*运行结果如下:
1 20
insert 20 into binary_sort_tree
in_order: 20 
1 10
insert 10 into binary_sort_tree
in_order: 10 20 
1 30
insert 30 into binary_sort_tree
in_order: 10 20 30 
1 15
insert 15 into binary_sort_tree
in_order: 10 15 20 30 
1 5
insert 5 into binary_sort_tree
in_order: 5 10 15 20 30 
1 25
insert 25 into binary_sort_tree
in_order: 5 10 15 20 25 30 
1 35
insert 35 into binary_sort_tree
in_order: 5 10 15 20 25 30 35 
2 20
erase 20 from binary_search_tree
in_order: 5 10 15 25 30 35 
2 30
erase 30 from binary_search_tree
in_order: 5 10 15 25 35
*/

五 题外话

码字不易,如果看官觉得写的还不错,可以点个赞吗?您的赞许,是对小编最大的鼓励!
加油,路漫漫其修远兮,吾将天天敲代码,与君共勉。
武汉加油!中国加油!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!