数据结构总结系列(二)——二叉排序树

什么是二叉排序树?二叉排序树和二叉树有什么关系呢?结合图像,让我们一点点来学习~

首先要明确的是二叉排序树是查找中运用很多的动态查找表,关于动态查找和静态查找,我们这次只是点明一点点,具体的部分下次再讲~

所谓动态查找,就是表在查找过程中动态生成我们所需要的表啦:

那么二叉排序树又是动态查找表中比较厉害(雾)的一种。。。

那么,二叉排序树有什么特点呢?

因此我们可以了解到,二叉排序树仍然具有树的特点,同时它的内部子树仍然保持高度有序呢(单个节点拎出来仍然是一颗二叉排序树)!

既然是树,最有效的存储方式当然是链表了~

按照链表定义,轻松写出结构体:

typedef struct BiNode
{
    int data;
    BiNode *lchild=NULL;
    BiNode *rchild=NULL;
}BiNode,*BiTree;//声明一棵树,以后可以使用

这里的结构体对左右孩子的声明还有一种写法:

struct BiNode *lchild=NULL;
struct BiNode *rchild=NULL;

既然是一棵树,接下来自然需要插入来生成了,但是我们需要注意,二叉排序树由于高度有序,插入的方式自然不同,我们认为最好是需要是对叶节点进行操作是最理想的状态,为了达到我们的目的,我们需要写一个遍历函数:

bool SearchTree(BiTree T,int kval,BiTree f,BiTree &tmp)//树的查找,这里思考了很久tmp的作用,最后理解为之后有时会需要这节点来干一些神奇的事情
{
    if(!T)//如果是空树,那么自然查不到了
    {
        tmp=f;
        return false;
    }
    else
    {
        if(T->data==kval)//如果此节点对应数据恰好吻合,bingo
        {
            tmp=T;
            return true;
        }
        else if(kval< (T->data)) SearchTree(T->lchild,kval,T,tmp);//这里的遍历查找自然不必多说
        else SearchTree(T->rchild,kval,T,tmp);
    }
}

我们能够对叶节点进行操作的话,自然可以用递归的方式进行新节点的插入操作,由于二叉排序树需要左边小于当前节点而右边大于当前节点,我们需要仔细考虑判断条件:

话不多说,上代码:

void CreatTree(BiTree &T,int data)//树的建立,非常简单,不必过多注释
{
    BiTree tmp;
    if(!SearchTree(T,data,NULL,tmp))//这里是一定需要遍历到最下面的叶节点的,不然插入节点是一件非常麻烦的事情
    {
        BiNode *t=new BiNode;
        t->data=data;
        if(!tmp) T=t;
        else if(data< (tmp->data)) tmp->lchild=t;//若当前数据小于节点,自然插入左边啦
        else tmp->rchild=t;
        cout << "Insert successful" << endl;
    }
    else
        cout << "Insert failed" << endl;
}

我们成功的插入了新的叶节点,是否意味着我们的工作完成了2/3呢,不幸的是,节点的删除工作十分繁琐,需要考虑的情况非常多,对于叶节点的删除是一件简单的事情,而对于中间节点:删除之后依然保留二叉排序树的高度有序是一件非常麻烦的事情,好在前人已经帮我们整理好了我们需要考虑的情况:

 

那么,上代码:

void Delete(BiTree &p)
{
    BiTree q;
    BiTree s;
    //从树中删除节点p
    //重新连接左右子树
    if(!p->rchild)//如果右兄弟那棵树是空的
    {
        q = p->lchild;//这里跳过了当前节点,走向了下一个左子女
        p->data = q->data;//这里把下一个节点的数据存到了当前节点

        p->rchild = q->rchild;//这里把下一个节点的左右子女兄弟均转移到了当前节点上,
        p->lchild = q->lchild;//即把下面的那一棵树变成了当前的节点

        free(q);//转移完成,删除"中转站"
    }
    else if(!p->lchild)//如果左子女那棵树是空的,这里的操作和上面没有区别,只是对左右的树的操作反过来了
    {
        q = p->rchild;
        p->data = q->data;
        p->rchild = q->rchild;
        p->lchild = q->lchild;
        free(q);
    }
    else if(p->lchild&&p->rchild)//如果左右都有树的情况下,这种情况比较复杂
    {
        q = p;//复制p到q,这样会得到两个一模一样的树(当前节点的下面整体)
        s = p->lchild;//s作为“中转站”,得到p左子女的那棵树
        while (s->rchild)//遍历p左子女的右边兄弟(较大的数字)当然也可以说是第一个后继
        {
            q = s;//把q更新为p左子女的后继的前一个节点
            s = s->rchild;//持续循环的条件
        }
        p->data = s->data;//把p的关键字更新为它的左子女的第一个后继
        if (q != p)//这里代表成功更新,也就是p的左子女有了后继
        {
            q->rchild = p->lchild;//这里的操作比较复杂:首先由于现在的大节点的数据更新为原先的左子女的第一个后继,那么他就可以访问此后继的左子女
        }//由于是第一个后继,那么一定没有右兄弟了!
        else//更新失败,那么回到第二种右兄弟为空树的情况(好麻烦==||)
        {
            q->lchild = s->lchild;
        }
        free(s);//最后依然是需要删除新建的树呢~
    }
}

void DeleteTree(BiTree &T,int data)
{
    if(!T) cout << "can't delete this data!" << endl;
    else
    {
        if(data==T->data)//如果此(当前找到的节点是要删除的节点)
        {
            Delete(T);
            cout << "delete this data successful!" << endl;
        }
        else if(data< (T->data)) DeleteTree(T->lchild,data);
        else DeleteTree(T->rchild,data);//这里用的遍历的查找方式
    }
}

注释已经把我想说的话说完啦~~

最后我们需要验证一下我们的数据结构是否能够经受考验:

#include <bits/stdc++.h>

using namespace std;

typedef struct BiNode
{
    int data;
    BiNode *lchild=NULL;
    BiNode *rchild=NULL;
}BiNode,*BiTree;//声明一棵树,以后可以使用

bool SearchTree(BiTree T,int kval,BiTree f,BiTree &tmp)//树的查找,这里思考了很久tmp的作用,最后理解为之后有时会需要这节点来干一些神奇的事情
{
    if(!T)//如果是空树,那么自然查不到了
    {
        tmp=f;
        return false;
    }
    else
    {
        if(T->data==kval)//如果此节点对应数据恰好吻合,bingo
        {
            tmp=T;
            return true;
        }
        else if(kval< (T->data)) SearchTree(T->lchild,kval,T,tmp);//这里的遍历查找自然不必多说
        else SearchTree(T->rchild,kval,T,tmp);
    }
}

void CreatTree(BiTree &T,int data)//树的建立,非常简单,不必过多注释
{
    BiTree tmp;
    if(!SearchTree(T,data,NULL,tmp))//这里是一定需要遍历到最下面的叶节点的,不然插入节点是一件非常麻烦的事情
    {
        BiNode *t=new BiNode;
        t->data=data;
        if(!tmp) T=t;
        else if(data< (tmp->data)) tmp->lchild=t;//若当前数据小于节点,自然插入左边啦
        else tmp->rchild=t;
        cout << "Insert successful" << endl;
    }
    else
        cout << "Insert failed" << endl;
}

void Display(BiTree T)//先序遍历展示是否插入成功!
{
    if(T)
    {
        cout << T->data << ' ' ;//递归show结果自然不必多说
        Display(T->lchild);
        Display(T->rchild);
    }
}

void Delete(BiTree &p)
{
    BiTree q;
    BiTree s;
    //从树中删除节点p
    //重新连接左右子树
    if(!p->rchild)//如果右兄弟那棵树是空的
    {
        q = p->lchild;//这里跳过了当前节点,走向了下一个左子女
        p->data = q->data;//这里把下一个节点的数据存到了当前节点

        p->rchild = q->rchild;//这里把下一个节点的左右子女兄弟均转移到了当前节点上,
        p->lchild = q->lchild;//即把下面的那一棵树变成了当前的节点

        free(q);//转移完成,删除"中转站"
    }
    else if(!p->lchild)//如果左子女那棵树是空的,这里的操作和上面没有区别,只是对左右的树的操作反过来了
    {
        q = p->rchild;
        p->data = q->data;
        p->rchild = q->rchild;
        p->lchild = q->lchild;
        free(q);
    }
    else if(p->lchild&&p->rchild)//如果左右都有树的情况下,这种情况比较复杂
    {
        q = p;//复制p到q,这样会得到两个一模一样的树(当前节点的下面整体)
        s = p->lchild;//s作为“中转站”,得到p左子女的那棵树
        while (s->rchild)//遍历p左子女的右边兄弟(较大的数字)当然也可以说是第一个后继
        {
            q = s;//把q更新为p左子女的后继的前一个节点
            s = s->rchild;//持续循环的条件
        }
        p->data = s->data;//把p的关键字更新为它的左子女的第一个后继
        if (q != p)//这里代表成功更新,也就是p的左子女有了后继
        {
            q->rchild = p->lchild;//这里的操作比较复杂:首先由于现在的大节点的数据更新为原先的左子女的第一个后继,那么他就可以访问此后继的左子女
        }//由于是第一个后继,那么一定没有右兄弟了!
        else//更新失败,那么回到第二种右兄弟为空树的情况(好麻烦==||)
        {
            q->lchild = s->lchild;
        }
        free(s);//最后依然是需要删除新建的树呢~
    }
}

void DeleteTree(BiTree &T,int data)
{
    if(!T) cout << "can't delete this data!" << endl;
    else
    {
        if(data==T->data)//如果此(当前找到的节点是要删除的节点)
        {
            Delete(T);
            cout << "delete this data successful!" << endl;
        }
        else if(data< (T->data)) DeleteTree(T->lchild,data);
        else DeleteTree(T->rchild,data);//这里用的遍历的查找方式
    }
}

int main()
{
    BiTree T=NULL;
    cout << "Please input the number n of the Bstree:" ;
    int n;
    cin>> n;
    int data;
    cout << "Please input the key of the Bstree" << endl;
    for(int i=0;i<n;i++)
    {
        cin>> data;
        CreatTree(T,data);
    }
    cout << "Please input a key to Search:" ;
    int kval;
    cin>> kval;
    BiTree tmp;
    if(SearchTree(T,kval,NULL,tmp)) cout << "the data is found!" << endl;
    else cout << "can't search this data!" << endl;
    Display(T);
    cout << endl;
    cout << "input the key data you want to delete:" ;
    int key;
    cin>> key;
    DeleteTree(T,key);
    Display(T);
    return 0;
}

以上!

二叉排序树的相关内容就是这些啦~~

希望大家能够多多指教,本弱鸡知道和大佬们差距很大,希望大家看到博主的错误能够和博主探讨一波,带带本弱鸡一起进步叭!

感谢大家!

猜你喜欢

转载自www.cnblogs.com/ever17/p/10920020.html