数据结构:查询

目录

1顺序表查询

2有序查询

2.1折半查询

3二叉排序树

3.1查询 

3.2查询(未查询到返回最后达到的结点) 

3.3插入数据 

3.4删除


思维导图(其中部分图片来自网络,侵删)

以下图源均来自《大话数据结构》 

查找(Searching)就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)

查找表按照操作方式分有两种:静态查找表动态查找表

之后要介绍的顺序表查询就是典型的静态查找表,而二叉树查询就是动态查找表 

 


 

1顺序表查询

顺序查询(Sequential Search)又叫线性查询,是最基本的查询技术,它的查过过程:

从表中第一个(或最后一个)记录开始,逐个进行记录的关键字和给定值比较,若某个记录的关键字和给定值相等,则查找成功,找到所查的记录,如果直到最后一个(或第一个)记录,其关键字和给定值比较都不等时,则表中没有所查的记录,查找不成功。

 顺序查找接口如下:

输出0,则查找失败,查找成功则返回的是该元素在数组中出现的位置(并不是单纯的该元素的下标)

int SequentialSearch(const int Matrix[],const int length,const int key)
{
    if(nullptr == Matrix||length<=0)
        return 0;

    for(int i = 0;i<length;++i)
    {
        if(key == Matrix[i])
        {
            return i+1;
            break;
        }
    }
    return 0;
}

具体操作:

    int TestSearch[10] = {1,3,6,8,7,2,6,9,10,6};
    qDebug()<<"所查找元素的位置"<<SequentialSearch(TestSearch,sizeof(TestSearch)/sizeof(int),3);
    qDebug()<<"所查找元素的位置"<<SequentialSearch(TestSearch,sizeof(TestSearch)/sizeof(int),6);
    qDebug()<<"所查找元素的位置"<<SequentialSearch(TestSearch,sizeof(TestSearch)/sizeof(int),66);

输出结果如下:

该查询方式的问题:

只能查找到该元素出现的第一个位置

 

                   

 

2有序查询

2.1折半查询

               

                   

                     

                  

                     

                  

折半查询的关键是:

需要两个游标,来控制长度,而这两个游标最终会会和,会和的同时也是找到该元素的同时。

所以跳出循环的关键是low游标大于了high游标,也就是说,两个游标最终相遇都没有找到,那就是找不到了。

折半查找要求:从小到大已经排列好了

接口程序如下:和大话数据结构有些出入,low游标是从下标是0开始的,整个逻辑都是一样的。

int Binary_Search(const int Matrix[],const int length,int key)
{
    if(nullptr == Matrix||length<=0)
        return 0;
    int low,high,mid;
    low = 0;
    high = length - 1;
    while (low<=high)
    {
        mid = (low + high)/2;
        qDebug()<<mid<<Matrix[mid];
        if(key > Matrix[mid])
            low = mid + 1;
        else if(key < Matrix[mid])
            high = mid - 1;
        else if(key == Matrix[mid])
            return mid+1;

    }
    return 0;
}

具体操作

    //折半查询
    qDebug()<<"折半查询";
    int Binart_TestSearch[10] = {1,10,22,30,36,41,46,50,60,90};
    qDebug()<<"所查找元素的位置"<<Binary_Search(Binart_TestSearch,sizeof(Binart_TestSearch)/sizeof(int),10);
    qDebug()<<"所查找元素的位置"<<Binary_Search(Binart_TestSearch,sizeof(Binart_TestSearch)/sizeof(int),30);
    qDebug()<<"所查找元素的位置"<<Binary_Search(Binart_TestSearch,sizeof(Binart_TestSearch)/sizeof(int),60);
    qDebug()<<"所查找元素的位置"<<Binary_Search(Binart_TestSearch,sizeof(Binart_TestSearch)/sizeof(int),600);

输出:

                  

 

 

3二叉排序树

二叉排列数最大的有点是:中序遍历时,所有的数值都是从小到大排列的 

3.1查询 

 

 

二叉树的结构如下:

//二叉树
struct SearchTree
{
    int data;
    SearchTree * lchild,*rchild;
};
//
SearchTree * SearchTreeFunction(SearchTree * root,const int key);
//二叉树遍历
void ForeachTree(SearchTree * root);

最基本的先序遍历接口: 

void ForeachTree(SearchTree * root)
{
    if(nullptr == root)
        return;
    qDebug()<<root->data;
    ForeachTree(root->lchild);
    ForeachTree(root->rchild);

}

 

二叉树查询接口如下:查找到则返回该元素,查找不到,返回nullptr

程序思路也是如上面所述,和根结点比较,小于根结点,则继续去左子树进行查询,如果大于根结点,则继续去右子树进行查询。形成了一个典型的递归思路。

接口输入:树的根结点,要查询的元素内容

接口输出:返回该元素所在结点的地址(未找到则返回的是nullptr

SearchTree * SearchTreeFunction(SearchTree * root,const int key)
{
    SearchTree * result;
    if(nullptr == root)
    {
        return nullptr;
    }
    else if(root->data == key)
    {
        result = root;
        return result;
    }
    else if(root->data < key)
    {
        return SearchTreeFunction(root->rchild,key);
    }
    else if(root->data > key)
    {
        return SearchTreeFunction(root->lchild,key);
    }
    return nullptr;
}

具体操作如下:

    qDebug()<<"二叉树查询";
    SearchTree b1 = {62,nullptr,nullptr};
    SearchTree b2 = {58,nullptr,nullptr};
    SearchTree b3 = {88,nullptr,nullptr};
    SearchTree b4 = {47,nullptr,nullptr};
    SearchTree b5 = {73,nullptr,nullptr};
    SearchTree b6 = {99,nullptr,nullptr};
    SearchTree b7 = {35,nullptr,nullptr};
    SearchTree b8 = {51,nullptr,nullptr};
    SearchTree b9 = {93,nullptr,nullptr};
    SearchTree b10 = {37,nullptr,nullptr};
    b1.lchild = &b2;// 62 58
    b1.rchild = &b3;//

    b2.lchild = &b4;

    b4.lchild = &b7;
    b4.rchild = &b8;

    b7.rchild = &b10;

    b3.lchild = &b5;
    b3.rchild = &b6;

    b6.lchild = &b9;

    ForeachTree(&b1);
    // 62 58 47 35 37 51 88 73 99 93
    SearchTree * result = SearchTreeFunction(&b1,93);
    if(result != nullptr)
    qDebug()<<"查询结果"<<result->data;

输出:

上述程序,首先建立树的结构,之后再遍历,查看是否建立成果 

二叉排列数最大的有点是:中序遍历时,所有的数值都是从小到大排列的 

使用中序遍历后的结果

中序遍历接口:

void ForeachTree_Middleorder(SearchTree * root)
{
    if(nullptr == root)
        return;
    ForeachTree_Middleorder(root->lchild);

    qDebug()<<root->data;

    ForeachTree_Middleorder(root->rchild);

}

输出:

 

这也是二叉树结构的特性。

3.2查询(未查询到返回最后达到的结点) 

修改:

接口输入:树的根结点,要查询的元素内容,该分支的根结点(如果root是左子树,则输入左子树的根结点)

接口输出:返回该元素所在结点的地址(未找到则返回的是最后达到的结点)

 和之前程序唯二不同的是,如果为空,返回的是ParentRoot,在左右子树递归的时候函数接口输入的是root根结点。

这样就做到了,即使未查询到元素,也会返回最后到达的结点

SearchTree * SearchTreeFunction(SearchTree * root,const int key,SearchTree * ParentRoot)
{
    SearchTree * result;
    if(nullptr == root)
    {
        return ParentRoot;//记录最后访问的结点
    }
    else if(root->data == key)
    {
        result = root;
        return result;
    }
    else if(root->data < key)
    {
        return SearchTreeFunction(root->rchild,key,root);
    }
    else if(root->data > key)
    {
        return SearchTreeFunction(root->lchild,key,root);
    }
    return nullptr;
}

具体操作:

    SearchTree * result = SearchTreeFunction(&b1,93,nullptr);//第三个参数:指向目前结点的双亲,初始化就是nullptr
    if(result != nullptr)
    qDebug()<<"查询结果"<<result->data;
    qDebug()<<"查询不存在的数据,返回达到最近的结点";
    qDebug()<<"查询91";
    SearchTree * result2 = SearchTreeFunction(&b1,91,nullptr);//第三个参数:指向目前结点的双亲,初始化就是nullptr
    if(result2 != nullptr)
    qDebug()<<"查询结果"<<result2->data;

    qDebug()<<"查询77";
    SearchTree * result3 = SearchTreeFunction(&b1,77,nullptr);//第三个参数:指向目前结点的双亲,初始化就是nullptr
    if(result3 != nullptr)
    qDebug()<<"查询结果"<<result3->data;

输出: 

3.3插入数据 

因为3.2查询的修改,使得插入非常简单,直接调用3.2查询函数,直接定位到距离插入元素值最近的结点,然后与结点比较大小,最终决定是放在左子树还是右子树即可。 

bool Insert_tree(SearchTree * root,SearchTree * InsertData)
{
    if(nullptr == root||nullptr == InsertData)
        return false;
    SearchTree * nearlyPoint = SearchTreeFunction(root,InsertData->data,nullptr);
    if(nearlyPoint->data != InsertData->data)//没有找到,可以擦入数据
    {
        if(nearlyPoint->data < InsertData->data)
        {
            nearlyPoint->rchild = InsertData;

        }
        else if(nearlyPoint->data > InsertData->data)
        {
            nearlyPoint->lchild = InsertData;

        }
        InsertData->lchild = nullptr;
        InsertData->rchild = nullptr;
        return true;

    }
    else
        return false;//找到相同的数据,不进行插入
}

具体操作:

    qDebug()<<"插入操作";
    SearchTree InsertData = {95,nullptr,nullptr};

    qDebug()<<"插入情况:"<<Insert_tree(&b1,&InsertData);
    ForeachTree(&b1);

输出: 

 

3.4删除

            

                       

                      

                         

                  

                         

              

              

            

                   

                     

                        

                        

                       

综上:最重要的举措:找到1删除点2删除点前驱3删除前前驱的根结点

1删除点 :更新数值

2删除点前驱:要作为新的结点,需要提供数值,提供左子树指针

3删除前前驱的根结点:更新自己的右结点/左结点

重写上述接口并进行测试:

能够如此删除,也是依托于二叉树的结构特性,中序遍历后,所有的内容都是从小到大排列的。

先查询,查询到了,删除,

删除版本的查询接口

SearchTree * Search_Delete_TreeFunction(SearchTree * root,const int key,SearchTree * ParentRoot)
{
    if(nullptr == root)
    {
        return ParentRoot;//记录最后访问的结点
    }
    else if(root->data == key)
    {
//        result = root;
        Delete_Data(root);
        return root;
    }
    else if(root->data < key)
    {
        return Search_Delete_TreeFunction(root->rchild,key,root);
    }
    else if(root->data > key)
    {
        return Search_Delete_TreeFunction(root->lchild,key,root);
    }
    return nullptr;

}

其中调用的删除接口如下:

SearchTree *  Delete_Data(SearchTree * deleteroot)
{
    if(nullptr == deleteroot)
        return nullptr;
    SearchTree * LastRoot,* RightPrior;
    if(deleteroot->lchild == nullptr)//没有左子树,右子树直接对接
    {
        deleteroot = deleteroot->rchild;
    }
    else if(deleteroot->rchild == nullptr)//没有右子树,左子树直接对接
    {
        deleteroot = deleteroot->lchild;
    }
    else //左右子树都不为空
    {
        //找到deleteroot的前驱
        LastRoot=deleteroot;
        RightPrior = deleteroot->lchild;
        while(RightPrior->rchild != nullptr)
        {
            LastRoot = RightPrior;//deleteroot的前驱的根
            RightPrior = RightPrior->rchild;//最右,deleteroot的前驱
        }

        deleteroot->data = RightPrior->data;//更新删除节点数值
        if(LastRoot!=deleteroot)
            //有一种情况,就是该根结点左子树,全部没有右叶子结点,因为LastRoot=deleteroot;初始化的赋值,导致二者相等,
            LastRoot->rchild = RightPrior->lchild;
        else
            LastRoot->lchild = RightPrior->lchild;

        RightPrior->lchild = nullptr;
        RightPrior->rchild = nullptr;

        return deleteroot;

    }

}

操作:

    Search_Delete_TreeFunction(&b1,47,nullptr);
    qDebug()<<"中序遍历";
    ForeachTree_Middleorder(&b1);

结果:

以下就是核心操作,整体分两组,第一组就是下面的情况,删除结点的左孩子,也有左右孩子

但是还有一种是没有展示的,删除结点的左孩子,只有左孩子,没有右孩子,那么就不进入程序的while循环,lastroot和deleteroot指向一样,那么直接更改删除结点的左孩子指针即可。

 

 

                      

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值