1 基于二叉链表的有序二叉树
1 基于二叉链表的有序二叉树
1.1 问题
BST是Binary Search Tree的缩写,译为二叉搜索树,或有序二叉树,是二叉树的一种,它的定义如下:
1)或者是一棵空树;
2)或者是具有下列性质的二叉树:
I) 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
II) 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
III)左、右子树也分别为二叉排序树;
BST在查找一个结点或插入一个结点时,具有极大的优势,速度非常快。是一种基础性数据结构,广泛应用于更加抽象的集合、关联数组等数据结构。
1.2 方案
BST的基本操作包括:
-
创建结点,新生成一个结点用于向BST中添加。
-
增加结点,在BST中加入一个数据元素。
-
删除结点,在BST中删除一个数据元素。
-
查找结点,在BST中查找指定的数据是否存在,如果存在则得到其地址。
-
修改结点,对BST中指定的数据进行修改。
-
清除一棵树
1.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:定义BST结点结构
在C语言中:
1)定义一个变量data,用来模拟BST中的数据。
2)定义一个指针left,用于指向该结点的左孩子。
3)定义一个指针right,用于指向该结点的右孩子。
4)这三方面的信息共同描述一个BST结点,可将它们封装在一起。
代码如下:
typedef int DataType;
struct Node{
DataType data;
struct Node *left;
struct Node *right;
};
步骤二:创建结点
首先,向操作系统申请一块存储空间。
然后,存储数据域数据
最后,将左孩子和右孩子指针赋值为空。
代码如下所示:
#include <stdio.h>
#include <stdlib.h>
typedef int DataType;
struct Node{
DataType data;
struct Node *left;
struct Node right;
};
//创建一个节点
struct Node createNode(DataType d)
{
struct Node pn = (struct Node)malloc(sizeof(struct Node));
pn->data = d;
pn->left = pn->right = NULL;
return pn;
}
步骤三:增加结点
首先,判断要插入的结点是否存在。
然后,判断根结点是否为空。
1)若根结点不为空,则当要插入的结点数据大于根结点的数据时,插入右子树;否则插入左子树。
2)若根结点为空,则将新加入的结点添加到根结点处。
代码如下所示:
#include <stdio.h>
#include <stdlib.h>
typedef int DataType;
struct Node{
DataType data;
struct Node left;
struct Node right;
};
//创建一个节点
struct Node createNode(DataType d)
{
struct Node pn = (struct Node)malloc(sizeof(struct Node));
pn->data = d;
pn->left = pn->right = NULL;
return pn;
}
//讲一个节点插入到一个树中
void insert(struct Node* root, struct Node* pn) {
if (pn == NULL) return;
//树为空
if (*root == NULL) {*root = pn; return;}
//树不为空
if (pn->data > (*root)->data) {
insert(&(*root)->right, pn);
}else {
insert(&(*root)->left, pn);
}
}
步骤四:删除结点
首先,查找要删除的结点,若不存在,则直接返回。
然后,若要被删除的结点没有左子树,则作如下处理:
1)要被删除的结点的双亲结点不存在时,则表明要删除根结点,此时只需将要删除结点的右孩子结点变成根结点即可。
2)要被删除的结点是其双亲结点的左孩子时,将要删除结点的右子树变成双亲结点的左孩子即可。
- 要被删除的结点是其双亲结点的右孩子时,将要删除结点的右子树变成双亲结点的右孩子即可。
4)释放要被删除结点所占的存储空间。
最后,若要被删除的结点有左子树,则作如下处理:
1)在要删除结点的左子树中查找最右下的结点。
2)如果最右下结点的双亲结点与被删除结点是同一结点,则将最右下结点的双亲结点的左孩子变为最右下结点的左孩子。
3)如果最右下结点的双亲结点与被删除结点不是同一结点,则将最右下结点的双亲结点的右孩子变为最右下结点的左孩子。
4)将最右下结点的数据复制到要删除结点内。
5)释放最右下结点所占的存储空间。
代码如下:
#include <stdio.h>
#include <stdlib.h>
typedef int DataType;
struct Node{
DataType data;
struct Node left;
struct Node right;
};
//创建一个节点
struct Node createNode(DataType d)
{
struct Node pn = (struct Node)malloc(sizeof(struct Node));
pn->data = d;
pn->left = pn->right = NULL;
return pn;
}
//讲一个节点插入到一个树中
void insert(struct Node* root, struct Node* pn) {
if (pn == NULL) return;
//树为空
if (*root == NULL) {*root = pn; return;}
//树不为空
if (pn->data > (root)->data) {
insert(&(root)->right, pn);
}else {
insert(&(root)->left, pn);
}
}
//在二叉排序树t中删去关键字为k的结点
void delete(struct Node root, DataType k)
{
struct Node p, *f, *s, *q;
p=*root;
f=NULL;
while§//查找关键字为k的待删结点p
{
if(p->datak ) break; //找到则跳出循环
f=p; //f指向p结点的双亲结点
if(p->data>k)
p=p->left;
else
p=p->right;
}
if(pNULL) return; //若找不到,返回原来的二叉排序树
if(p->leftNULL) //p无左子树
{
if(fNULL)
*root=p->right; //p是原二叉排序树的根
else if(f->leftp) //p是f的左孩子
f->left=p->right; //将p的右子树链到f的左链上
else //p是f的右孩子
f->right=p->right; //将p的右子树链到f的右链上
free§;
}
else //p有左子树
{
q=p;
s=p->left;
while(s->right) //在p的左子树中查找最右下结点
{
q=s;
s=s->right;
}
if(qp)
q->left=s->left; //将s的左子树链到q上
else
q->right=s->left;
p->data=s->data; //将s的值赋给p
free(s);
}
}
上述代码中,以下代码:
while(p)//查找关键字为k的待删结点p
{
if(p->data==k ) break; //找到则跳出循环
f=p; //f指向p结点的双亲结点
if(p->data>k)
p=p->left;
else
p=p->right;
}
if(p==NULL) return; //若找不到,返回原来的二叉排序树
设置循环查找要删除的结点,若不存在,则直接返回。
上述代码中,以下代码:
if(p->left==NULL) //p无左子树
{
if(f==NULL)
*root=p->right; //p是原二叉排序树的根
else if(f->left==p) //p是f的左孩子
f->left=p->right; //将p的右子树链到f的左链上
else //p是f的右孩子
f->right=p->right; //将p的右子树链到f的右链上
free(p);
}
是若要被删除的结点没有左子树时的情形,其中指针p指向要被删除的结点,指针f指向被删除的结点的双亲结点。
if(f==NULL)
*root=p->right; //p是原二叉排序树的根
表示要被删除的结点的双亲结点不存在时,要删除根结点。
else if(f->left==p) //p是f的左孩子
f->left=p->right; //将p的右子树链到f的左链上
表示要被删除的结点是其双亲结点的左孩子时,将要删除结点的右子树变成双亲结点的左孩子即可。
else //p是f的右孩子
f->right=p->right; //将p的右子树链到f的右链上
表示要被删除的结点是其双亲结点的右孩子时,将要删除结点的右子树变成双亲结点的右孩子即可。
上述代码中,以下代码:
else //p有左子树
{
q=p;
s=p->left;
while(s->right) //在p的左子树中查找最右下结点
{
q=s;
s=s->right;
}
if(q==p)
q->left=s->left; //将s的左子树链到q上
else
q->right=s->left;
p->data=s->data; //将s的值赋给p
free(s);
}
是若要被删除的结点有左子树时的情形,其中指针p指向要被删除的结点,指针s指向最右下结点,指针q指向最右下结点的双亲结点。
s=p->left;
while(s->right) //在p的左子树中查找最右下结点
{
q=s;
s=s->right;
}
是在要删除结点的左子树中查找最右下的结点。
if(q==p)
q->left=s->left; //将s的左子树链到q上
表示如果最右下结点的双亲结点q与被删除结点p是同一结点,则将最右下结点的双亲结点q的左孩子q->left变为最右下结点s的左孩子s->left。
else
q->right=s->left;
表示如果最右下结点的双亲结点q与被删除结点p不是同一结点,则将最右下结点的双亲结点q的右孩子q->right变为最右下结点s的左孩子s->left。
步骤五:查找结点
首先,判断根结点是否为空,若是则直接返回NULL。
然后,在根结点不为空的情况下,若要查找的数据大于根结点数据,遍历右子树查找。
下一步,在根结点不为空的情况下,若要查找的数据小于根结点数据,遍历左子树查找。
最后,在根结点不为空的情况下,若要查找的数据等于根结点数据,则返回根结点。
代码如下:
#include <stdio.h>
#include <stdlib.h>
typedef int DataType;
struct Node{
DataType data;
struct Node left;
struct Node right;
};
//创建一个节点
struct Node createNode(DataType d)
{
struct Node pn = (struct Node)malloc(sizeof(struct Node));
pn->data = d;
pn->left = pn->right = NULL;
return pn;
}
//讲一个节点插入到一个树中
void insert(struct Node* root, struct Node* pn) {
if (pn == NULL) return;
//树为空
if (*root == NULL) {*root = pn; return;}
//树不为空
if (pn->data > (root)->data) {
insert(&(root)->right, pn);
}else {
insert(&(root)->left, pn);
}
}
//在二叉排序树t中删去关键字为k的结点
void delete(struct Node root, DataType k)
{
struct Node p, *f, *s, *q;
p=root;
f=NULL;
while§//查找关键字为k的待删结点p
{
if(p->datak ) break; //找到则跳出循环
f=p; //f指向p结点的双亲结点
if(p->data>k)
p=p->left;
else
p=p->right;
}
if(pNULL) return; //若找不到,返回原来的二叉排序树
if(p->leftNULL) //p无左子树
{
if(fNULL)
root=p->right; //p是原二叉排序树的根
else if(f->leftp) //p是f的左孩子
f->left=p->right; //将p的右子树链到f的左链上
else //p是f的右孩子
f->right=p->right; //将p的右子树链到f的右链上
free§;
}
else //p有左子树
{
q=p;
s=p->left;
while(s->right) //在p的左子树中查找最右下结点
{
q=s;
s=s->right;
}
if(qp)
q->left=s->left; //将s的左子树链到q上
else
q->right=s->left;
p->data=s->data; //将s的值赋给p
free(s);
}
}
//查找一个树
struct Node find(struct Node root, DataType d){
if(root == NULL)
return NULL;
if(d > root->data) //查找右子树
return find(root->right,d);
else if(d < root->data) //查找左子树
return find(root->left,d);
else
return root;
}
步骤六:修改结点
首先,使用步骤四的方法查找到要修改的结点。
然后,将要修改的结点数据修改为指定数据。
代码如下:
#include <stdio.h>
#include <stdlib.h>
typedef int DataType;
struct Node{
DataType data;
struct Node left;
struct Node right;
};
//创建一个节点
struct Node createNode(DataType d)
{
struct Node pn = (struct Node)malloc(sizeof(struct Node));
pn->data = d;
pn->left = pn->right = NULL;
return pn;
}
//讲一个节点插入到一个树中
void insert(struct Node* root, struct Node* pn) {
if (pn == NULL) return;
//树为空
if (*root == NULL) {*root = pn; return;}
//树不为空
if (pn->data > (root)->data) {
insert(&(root)->right, pn);
}else {
insert(&(root)->left, pn);
}
}
//在二叉排序树t中删去关键字为k的结点
void delete(struct Node root, DataType k)
{
struct Node p, *f, s, q;
p=root;
f=NULL;
while§//查找关键字为k的待删结点p
{
if(p->datak ) break; //找到则跳出循环
f=p; //f指向p结点的双亲结点
if(p->data>k)
p=p->left;
else
p=p->right;
}
if(pNULL) return; //若找不到,返回原来的二叉排序树
if(p->leftNULL) //p无左子树
{
if(fNULL)
root=p->right; //p是原二叉排序树的根
else if(f->leftp) //p是f的左孩子
f->left=p->right; //将p的右子树链到f的左链上
else //p是f的右孩子
f->right=p->right; //将p的右子树链到f的右链上
free§;
}
else //p有左子树
{
q=p;
s=p->left;
while(s->right) //在p的左子树中查找最右下结点
{
q=s;
s=s->right;
}
if(qp)
q->left=s->left; //将s的左子树链到q上
else
q->right=s->left;
p->data=s->data; //将s的值赋给p
free(s);
}
}
//查找一个树
struct Node find(struct Node root, DataType d){
if(root == NULL)
return NULL;
if(d > root->data) //查找右子树
return find(root->right,d);
else if(d < root->data) //查找左子树
return find(root->left,d);
else
return root;
}
//修改树中结点的值
void modify(struct Node root, DataType oldData, DataType newData)
{
struct Node p;
p = find(root, oldData);
p->data = newData;
}
1.4 完整代码
本案例的完整代码如下所示:
#include <stdio.h>
#include <stdlib.h>
typedef int DataType;
struct Node{
DataType data;
struct Node left;
struct Node right;
};
//创建一个节点
struct Node createNode(DataType d)
{
struct Node pn = (struct Node)malloc(sizeof(struct Node));
pn->data = d;
pn->left = pn->right = NULL;
return pn;
}
//讲一个节点插入到一个树中
void insert(struct Node* root, struct Node* pn) {
if (pn == NULL) return;
//树为空
if (*root == NULL) {*root = pn; return;}
//树不为空
if (pn->data > (root)->data) {
insert(&(root)->right, pn);
}else {
insert(&(root)->left, pn);
}
}
//在二叉排序树t中删去关键字为k的结点
void delete(struct Node root, DataType k)
{
struct Node p, *f, s, q;
p=root;
f=NULL;
while§//查找关键字为k的待删结点p
{
if(p->datak ) break; //找到则跳出循环
f=p; //f指向p结点的双亲结点
if(p->data>k)
p=p->left;
else
p=p->right;
}
if(pNULL) return; //若找不到,返回原来的二叉排序树
if(p->leftNULL) //p无左子树
{
if(fNULL)
root=p->right; //p是原二叉排序树的根
else if(f->leftp) //p是f的左孩子
f->left=p->right; //将p的右子树链到f的左链上
else //p是f的右孩子
f->right=p->right; //将p的右子树链到f的右链上
free§;
}
else //p有左子树
{
q=p;
s=p->left;
while(s->right) //在p的左子树中查找最右下结点
{
q=s;
s=s->right;
}
if(qp)
q->left=s->left; //将s的左子树链到q上
else
q->right=s->left;
p->data=s->data; //将s的值赋给p
free(s);
}
}
//查找一个树
struct Node find(struct Node root, DataType d){
if(root == NULL)
return NULL;
if(d > root->data) //查找右子树
return find(root->right,d);
else if(d < root->data) //查找左子树
return find(root->left,d);
else
return root;
}
//修改树中结点的值
void modify(struct Node root, DataType oldData, DataType newData)
{
struct Node p;
p = find(root, oldData);
p->data = newData;
}
//清除一颗树
void clears(struct Node **root) {
if (*root == NULL) return;
clears(&(*root)->left);
clears(&(*root)->right);
free(*root);
root = NULL;
}
//打印一颗树
void print(struct Node root) {
if (root == NULL) return;
printf("%d ", root->data);
print(root->left);
print(root->right);
}
int main() {
struct Node* root = NULL;
insert(&root, createNode(10));
insert(&root, createNode(5));
insert(&root, createNode(3));
insert(&root, createNode(2));
insert(&root, createNode(1));
insert(&root, createNode(4));
insert(&root, createNode(7));
insert(&root, createNode(6));
insert(&root, createNode(9));
insert(&root, createNode(8));
insert(&root, createNode(15));
print(root);
printf("\n");
printf("%d\n", find(root, 5)->data);
delete(&root, 10);
print(root);
return 0;
}