目录
思维导图(其中部分图片来自网络,侵删)
以下图源均来自《大话数据结构》
查找(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指向一样,那么直接更改删除结点的左孩子指针即可。