二叉排序树结点的插入与删除操作
一 二叉排序树的性质
二叉排序树,又称二叉搜索树,它最重要的性质就是:根结点左子树中所有结点的值均小于根结点值,右子树中所有结点的值都大于根结点的值,所以我们在中序遍历这棵二叉树时,将会得到一个升序序列,这也是我们验证二叉排序树的一个手段。
对应的数据结构定义为:
typedef struct Node { //一个数据域和左右两个指针域
int data;
struct Node *lchild, *rchild;
} Node;
二 二叉树结点的插入
- 首先将待插入结点的值与根结点的值作比较,若val == root->data,此时我们直接返回当前root指针,因为我们规定,二叉排序树中不存在值相同的结点。
- 若待插入结点的值小于根结点的值,此时我们应该进入根结点的左子树
- 若待插入结点的值大于根结点的值,此时我们应该进入根结点的右子树
- 若指针为空,此时我们根据传入的值创建结点,并返回创建的结点指针
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的结点。
- 首先我们来讨论度为0和1结点
对于度为0的结点,因为没有子结点,所以我们可以直接删除 (一个人在世上,走了就是走了,没有家眷需要安排)
对于度为1的结点,我们假设是它是它父亲结点的左孩子,那么不论是它的左孩子还是他的右孩子,都要比它父亲结点的值要小,所以我们可以直接将它的左孩子 (或者是右孩子,注意这是度为1的结点,所以要么是左孩子要么是右孩子) 顶替自己的位置,成为它父亲结点的子孩子;同理若它是它父结点的右孩子,那么不论是它的左孩子还是他的右孩子,都要比它父亲结点的值要大,我们同样可以子结点二选一,顶替自己的位置。 - 度为2的结点
讨论了删除度为0和1的结点,现在我们来想一想如何删除度为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
*/
五 题外话
码字不易,如果看官觉得写的还不错,可以点个赞吗?您的赞许,是对小编最大的鼓励!
加油,路漫漫其修远兮,吾将天天敲代码,与君共勉。
武汉加油!中国加油!
来源:CSDN
作者:qq_1927157164
链接:https://blog.csdn.net/DRZ_2000/article/details/104540436