二叉排序树,又称为二叉查找树。它具有以下性质:1.若它的左子树不空,则左子树上所有结点的值均小于它根结点的值。2.若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。3.它的左、右子树也分别为二叉排序树。
也就是按照数组从左到右的顺序,第一个为根节点,然后小的为左子树,大的为右子树,然后按照二叉树的性质以此类推。
(1)二叉排序树的结点结构
typedef struct BiTNode {
int data;
struct BiTNode* lchild, * rchild;//左右孩子指针
}BiTNode,*BiTree;
(2)二叉排序树的查找
int SearchBST(BiTree T, int key, BiTree f, BiTree& p) {
if (!T) {
p = f;
return 0;
}
else if (key == T->data) { //查找成功
p = T;
return 1;
}
else if (key < T->data) {
return SearchBST(T->lchild, key, T, p);//遍历左子树
}
else {
return SearchBST(T->rchild, key, T, p);//遍历右子树
}
}
1.参数的解释:
参数T是一个二叉链表(*BiTree),key代表要查找的关键字,二叉树f一直指向T的双亲(当T为根节点时,f的初值就为NULL)。最后的参数p是为查找成功后得到的结点位置,如果查找失败,则p指向查找路径上访问的最后一个结点。
调用函数的格式:SearchBST(T,93,NULL,p)
2.查找不成功的情况:
if (!T) {
p = f;
return 0;
}
也就是T查找到叶子结点的下一层(T为NULL),说明查找失败,那么要把最后访问的结点赋给p,也就是T的双亲结点f,返回0。
3.查找成功的情况:
else if (key == T->data) {
p = T;
return 1;
}
当key==T->data的时候,说明查找成功,这时赋给p对应的结点T,返回1。
4.关键值小于当前结点值的情况:
else if (key < T->data) {
return SearchBST(T->lchild, key, T, p);//遍历左子树
}
说明找大了,要向数值减小的方向进行,也就是对结点的左子树进行递归调用——SearchBST(T->lchild,key,T,p)。其中T修改为自己的左子树,关键值不便,f修改为T->lchild的双亲结点,也就是T,p不变。
5.关键值大于当前结点值的情况:
else {
return SearchBST(T->rchild, key, T, p);//遍历右子树
}
与前一种情况刚好相反。找小了,要对右子树进行递归。
(3)二叉排序树的插入
把关键字放到树中合适的位置
int InsertBST(BiTree& T, int key) {
BiTree p, s;
if (!SearchBST(T, key, NULL, p)) { //根据查找函数,p指向最后经过的叶子结点
s = new BiTNode;
s->data = key;
s->lchild = s->rchild = NULL; //初始化新结点
if (!p) { //说明这是个空树
T = s;
}
else if (key < p->data) //数值小于p结点,放左边
p->lchild = s;
else
p->rchild = s; //数值大于p结点,放右边
return 1;
}
else
return 0;
}
整体思路:
1.首先就是调用SearchBST函数,查看整个排序二叉树中是否有key值,如果没有则插入,如果有,则返回0。
2.在没有key值的情况下,生成一个新结点s,把s的左右孩子指针都置空,然后把key值赋给s的data域。
s = new BiTNode;
s->data = key;
s->lchild = s->rchild = NULL;
3.判断是否为空树——如果调用完SearchBST函数后,p为空,说明最后一个访问的结点为空,也就是整个排序二叉树为空树。这时让s成为根节点即可。
if (!p) {
T = s;
}
4.根据key与p->data的关系进行插入。因为s结点一定是p结点的孩子——排序二叉树的定义(插入一定要插在已经有的结点后面,而不能插入到中间)。因此,如果key小于p->data,那么s成为p的左结点,如果key大于p->data,那么s成为p的右结点。
else if (key < p->data)
p->lchild = s;
else
p->rchild = s;
return 1;
示例:
1.生成一棵排序二叉树:
BiTree t=NULL;
int n;
cout << "输入元素个数:";
cin >> n;
for (int i = 0; i < n; i++)
{
int h;
cin >> h;
InsertBST(t, h);
}
2.查找示例:
int main()
{
BiTree t=NULL;
int n;
cout << "输入元素个数:";
cin >> n;
for (int i = 0; i < n; i++)
{
int h;
cin >> h;
InsertBST(t, h);
}
BiTree p=NULL,f=NULL;
int key;
cout << "输入查找的元素:";
cin >> key;
int j = SearchBST(t, key, f, p);
if (j == 1) {
cout << "查找成功!查找的元素为:" << p->data;
}
else {//j==0
cout << "查找失败!";
}
return 0;
}
注意p和f的初始化:BiTree p=NULL,f=NULL p可以初始化也可以不初始化,因为SearchBST函数全程没有用p的值,p一直是被赋的。但是f一定要初始化为NULL,因为它的值会赋给p。
(4)二叉排序树结点的删除
二叉排序树的删除共有三种情况:1.删除叶子结点。2.仅有左或右子树的结点。3.左右子树都有的结点。因此代码需要对这三种情况进行分类讨论。因此二叉排序树结点的删除不是简简单单的delete,需要额外定义一个函数——DeleteTree
int DeleteTree(BiTree& p) {
BiTree q, s;
if (p->rchild == NULL) {//结点的右子树为空
q = p; //保存结点
p = p->lchild; //修改p的双亲结点指向p的左子树(因为双亲结点的孩子是p指向的结点)
delete q;
}
else if (p->lchild == NULL) {
q = p;
p = p->rchild;
delete q;
}
else { //左右子树都不为空
q = p; //保存结点
s = p->lchild; //s左转
while (s->rchild) { //然后右转到尽头
q = s;
s = s->rchild;
} //找到中序遍历的直接前驱
p->data = s->data; //修改p
if (q != p) { //说明s左转后有右子树
q->rchild = s->lchild;
}
else { //说明s左转后没有右转(没有右子树)
q->lchild = s->lchild;
}
delete s;
}
return 1;
}
1.右子树为空的情况:
if (p->rchild == NULL) {//结点的右子树为空
q = p;
p = p->lchild;
delete q;
}
若右子树为空,则删除结点之后,把左子树嫁接到删除结点的双亲结点即可,也即p=p->lchild,这里对这句代码进行解释:插入结点时,双亲结点的左子树或右子树指针是指向p的,也就是不管p自身发生什么样的变化,指针一直指向p。因此要完成嫁接的操作,只需要p变成自己的左子树即可,这样双亲结点的指针就指向了p的左子树。 注意p已经发生变化,因此需要提前准备另外一个指针q指向原先的p结点,然后删除。
2.左子树为空的情况:
else if (p->lchild == NULL) {
q = p;
p = p->rchild;
delete q;
}
与右子树为空的情况基本相同,只不过p改变的位置换成了自己的右子树。
3.叶子结点的情况:
这里没有对叶子结点特别给出代码,这是因为叶子结点符合左子树为空同时也符合右子树为空,因此上面给出的代码对叶子结点同样有效,只不过是把NULL嫁接给了双亲结点。
4.左子树和右子树都不为空的情况:
else {
q = p;
s = p->lchild;
while (s->rchild) {
q = s;
s = s->rchild;
}
p->data = s->data;
if (q != p) {
q->rchild = s->lchild;
}
else {
q->lchild = s->lchild;
}
delete s;
}
这是最复杂情况,这时我们的方法是——找到需要删除的结点p的直接前驱(或直接后继)s,用s的数据来替换结点p的数据,然后删除结点s即可。所谓直接前驱(直接后继),其实就是二叉排序树经过中序遍历后得到的数组中,p数据的直接前驱(直接后继)。
这里的做法是使用直接前驱。
q = p;
s = p->lchild;
while (s->rchild) {
q = s;
s = s->rchild;
}
先将要删除的结点p赋值给临时变量q,然后s先指向p的左子树,然后一直转到右子树的尽头(先指向左子树是因为要小于p,然后转到右子树的尽头是因为要找到p的直接前驱)。q始终为s的双亲结点。
p->data = s->data;
if (q != p) {
q->rchild = s->lchild;
}
else {
q->lchild = s->lchild;
}
delete s;
找到直接前驱后,把p的数据修改为直接前驱s的数据。关于嫁接则有两种情况:q不等于p和q等于p。如果p等于q,也就是p的直接前驱s的双亲结点就是p,说明左转之后就没有右子树了,不能右转。这时要把s的左子树嫁接给q(也就是p)的左子树。如果p不等于q,说明已经右转过了,这时要把s的左子树嫁接给q的右子树。(s嫁接的都是左子树)
嫁接完毕后删除结点s即可。
(5)二叉排序树的查找删除函数
这个函数主要的作用是找到要删除的结点,然后调用结点删除函数。
int DeleteBST(BiTree& T, int key) {
if (!T) {
return 0;
}//找到了叶子结点的下一层还没有找到,查找失败,返回0
else {//还没有到叶子结点的下一层
if (key == T->data) {//查找到了
DeleteTree(T);//删除该结点
return 1;
}
else if (key < T->data) {//结点的数据较大
return DeleteBST(T->lchild, key);//需要往小的地方找(左子树)
}
else {//结点的数据较小
return DeleteBST(T->rchild, key);//需要往大的地方找(右子树)
}
}
}
与二叉排序树的查找函数几乎完全相同,唯一的区别就是调用了DeleteTree函数。 注意查找失败的条件是到了叶子结点的下一层还没有找到。
示例:
int main()
{
BiTree t=NULL;
int n;
cout << "输入元素个数:";
cin >> n;
for (int i = 0; i < n; i++)
{
int h;
cin >> h;
InsertBST(t, h);
}
BiTree p=NULL,f=NULL;
int key1,key2;
cout << "输入删除的元素:";
cin >> key1;
DeleteBST(t, key1);
cout << "输入查找的元素:";
cin >> key2;
int j = SearchBST(t, key2, f, p);
if (j == 1) {
cout << "查找成功!查找的元素为:" << p->data;
}
else {//j==0
cout << "查找失败!";
}
return 0;
}
int main()
{
BiTree t=NULL;
int n;
cout << "输入元素个数:";
cin >> n;
for (int i = 0; i < n; i++)
{
int h;
cin >> h;
InsertBST(t, h);
}
int key;
cout << "输入要删除的元素:";
cin >> key;
if (DeleteBST(t, key)) {
cout << "删除成功!";
}
else
cout << "删除失败!";
return 0;
}