本篇博客会介绍到数据结构中常用到的查找算法,合理的使用查找算法可以让我们很轻松找到自己想要的答案。本小节必须掌握的是顺序查找、二分查找、二叉排序树的建立,有剩余时间可以实现二叉排序树的节点删除,以及2011年真题实战42题的代码。
算法思想如下:
- 顺序查找:思想非常简单,直接遍历一遍列表即可!
- 二分查找:针对有序数组非常实用,搜索范围每次直接折半,搜索效率大大提升。
- 二叉排序树:Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。最大查询次数为树的深度。
- 真题实战:二分查找的升级版,实用二分查找的思想实现查找有序列表的中位数。
目前还没有对各种算法的时间复杂度空间复杂度进行分析!后面数据结构会详细整理!
顺序查找又称线性查找,它对于顺序表和链表都是适用的。对于顺序表,可通过数组下标递增来顺序扫描每个元素;对于链表,则通过指针next来依次扫描每个元素
//顺序查找
bool search(int *a,int len,int e){
for(int i=0;i<len;i++){
if(a[i]==e){
return true;
}
}
return false;
}
折半查找又称二分查找
,它仅适用于有序的顺序表。
折半查找的基本思想: 首先将给定值 key 与表中中间位置的元素比较,若相等,则查找成功,返回该元素的存储位置;若不等,则所需查找的元素只能在中间元素以外的前半部分或后半部分(例如,在查找表升序排列时,若给定值 key
大于中间元素,则所L找刻扮到为止.或确定表中没有所需要查找的兀素,进行同样的查找,如此重复,直到找到为止,或确定表中没有所需要查找的元素,则查找不成功,返回查找失败的信息。
这里需要注意的一点就是,中间的值如果不是我们想要的,我们的midde指针要移向他的前一个或者后一个!
//二分查找
bool Bin_Search(int *a,int len,int e){
int l,r,middle;
l=0;
r=len-1;
while (l<=r){
middle=(l+r)/2;
if(a[middle]==e){
return true;
} else if(a[middle]>e){
// 这里之所以这么确定,是因为middle指向的地方不是我们所要的值
r=middle-1;
}else{
l=middle+1;
}
}
return false;
}
二叉排序树(也称二叉查找树)或者是一棵空树,或者是具有下列特性的二叉树:
我们在建树的时候可以使用递归方法也可以使用循环方法,将要插入的元素与当前节点比较,大的话向右走,小的话向左走,直到找到合适的位置插入,寻找对应元素依旧如此!
代码实战中有二叉排序树的建立,有删除所找节点两个功能;
建树算法思想是:
遍历所要建树节点,每次取出一个节点,与根节点进行比较,如果比根节点小就向根节点左子树移动,否则向右子树,然后以根节点子树做新的根节点,重复上述过程!直到找到合适的位置进行插入;
删除节点思想:
遍历二叉搜索树,找到要删除的节点之后,先记录下该节点与该节点前驱,如果该节点有一个子树或者没有子树那么直接将该节点删除,否则:先向左走一步,之后一直向右走,直到走到右子树为空的节点,将该节点的值填充到要删除的节点,然后将该节点删除(本质是找与要删除节点数值相邻最近的点将其位置顶替)。
注:
在二叉排序树中删除节点时删除只有一个子树的节点或者没有子树的节点比较好删除,只需将子树向上移动一下即可。
BitStruct.h文件内容如下:
//
// Created by 123 on 2023/2/20.
//
#ifndef MYTEST_BITSTRUCT_H
#define MYTEST_BITSTRUCT_H
//基础元素类型
#define ElementData int
#define ElementDataEnd -1
//二叉树的节点
struct BiTNode{
ElementData data;
struct BiTNode *leftNode;
struct BiTNode *rightNode;
};
typedef struct BiTNode BiTNode;
//辅助队列(通过这个队列层次建树)
struct auxiliary{
// 指向二叉树中的节点
struct BiTNode* p;
struct auxiliary *next;
};
typedef struct auxiliary tagQueue;
#endif //MYTEST_BITSTRUCT_H
真正的业务代码如下:
//
// Created by Zhu Shichong on 2023/1/9.
//
#include
#include
#include "BitStruct.h"
#define bool int
#define true 1
#define false 0
#define MaxSize 10
//初始化二叉排序树----------循环实现方式
BiTNode *Creat_BST(ElementData *a,int len){
BiTNode *tree=(BiTNode*)malloc(sizeof (BiTNode));
tree->data=a[0];
tree->rightNode=tree->leftNode=NULL;
for(int i=1;i<len;i++){
BiTNode *p=tree;
BiTNode *q=(BiTNode*) malloc(sizeof (BiTNode));
q->data=a[i];
q->leftNode=q->rightNode=NULL;
// 用于保留p指针的位置
BiTNode *prep=NULL;
while (p!=NULL){
prep=p;
if(p->data>q->data){
p=p->leftNode;
}else{
p=p->rightNode;
}
}
// 将值插入找出的位置;
prep->data > q->data?(prep->leftNode = q):(prep->rightNode = q);
// 下面这种写法是错误的
// prep->data > q->data?prep->leftNode = q:prep->rightNode = q;
}
return tree;
}
//查找元素--------查找次数为树的深度
bool BST(BiTNode* tree,ElementData e){
while(tree!=NULL){
if(tree->data>e){
tree=tree->leftNode;
}else if(tree->data<e){
tree=tree->rightNode;
}else{
return true;
}
}
return false;
}
//删除树中的节点
//实现思路:二叉搜索树有特点就是,比当前节点小的最大元素在左子树的最右侧,比当前节点大的最小元素在右子树最左侧
//想要删除叶子节点或者只有一个子树的节点,只需用子树取代原节点的位置,为了简化计算,我们可以先采用相近元素取代要删除
//的节点,然后删除那个只有一个子树或者没有子树的节点。
bool Delete_BST(BiTNode** tree,ElementData e){
BiTNode *head=NULL;
BiTNode *temp_tree=*tree;
// 这样写head,只要是找到了目标e,那么head就作为值为e节点的前驱节点
while(temp_tree!=NULL){
// 只有判断temp才会往下走,只有temp往下走,temp才记录他的前驱
//(如果分支不这样写,那么要删除的是最后一个元素时,没法操作前驱指针)
if(temp_tree->data>e){
head=temp_tree;
temp_tree=temp_tree->leftNode;
}else if(temp_tree->data<e){
head=temp_tree;
temp_tree=temp_tree->rightNode;
}else{
break;
}
}
// 如果一路走到头没有找到要删除的节点,那么返回false
if(NULL==temp_tree){
return false;
}
// 判断要删除的节点什么特征(左右子树是否为空,不为空需要根据二叉搜索树的特征向下找!)
if(temp_tree->leftNode==NULL){
if(head->leftNode&&head->leftNode->data==temp_tree->data){
head->leftNode=temp_tree->rightNode;
}else{
head->rightNode=temp_tree->rightNode;
}
}else if(temp_tree->rightNode==NULL){
if(head->leftNode&&head->leftNode->data==temp_tree->data){
head->leftNode=temp_tree->leftNode;
}else {
head->rightNode = temp_tree->leftNode;
}
}else{
BiTNode *p=temp_tree->leftNode;
BiTNode *prep=temp_tree;
// 向下找,直到找到左子树最右边的节点(中序遍历可以看出,该节点可以取代要删除节点的位置)
// 并且所找节点一定是只有一个子树或者没有子树
// prep为符合条件节点的前驱。
while(p->rightNode!=NULL){
prep=p;
p=p->rightNode;
}
// 真正的删除(将找到的节点的值填充到要删除的节点)然后释放找到的节点
// 也就是找一个替死鬼!!!或者说让符合条件的节点拆迁
// if分支是一个交接处,需要先向左走一步然后再一直向右走
// 所以在该点进行特殊处理!
if(prep->data==temp_tree->data){
temp_tree->data=p->data;
prep->leftNode=NULL;
free(p);
}else{
temp_tree->data=p->data;
if (p->leftNode == NULL) {
prep->rightNode=NULL;
} else {
prep->rightNode=p->leftNode;
}
free(p);
}
}
return true;
}
void PrintBST(BiTNode *tree){
if(NULL==tree){
return;
}
PrintBST(tree->leftNode);
printf("%d ",tree->data);
PrintBST(tree->rightNode);
}
int main() {
BiTNode *tree=NULL;
ElementData a[MaxSize]={22,1,33,13,45,31,65,221,857,3};
// 构建二叉排序树
tree=Creat_BST(a,MaxSize);
PrintBST(tree);
printf("\n");
// 利用二叉排序树查找元素
printf("%d \n",BST(tree,888));
printf("%d \n", BST(tree,857));
// 删除二叉排序树中的元素--------测试1
Delete_BST(&tree,33);
PrintBST(tree);
printf("\n");
// 删除二叉排序树中的元素--------测试2
Delete_BST(&tree,1);
PrintBST(tree);
printf("\n");
// 删除二叉排序树中的元素--------测试3
Delete_BST(&tree,857);
PrintBST(tree);
printf("\n");
return 0;
}
先看看题目:
这个题目所考察的内容是二分查找,但是有两个数组,是双数组的二分查找,是一道非常经典的题目。因为空间尽可能高效,因此我们不能够去再搞一个大数组,把两个数组合并到一起,这样得分会非常低。
(1)算法的基本设计思想如下:
分别求出序列A和B的中位数,设为a 和 b,求序列A和 B
的中位数过程如下:
- 1)若a=b,则a 或b 即为所求中位数,算法结束。
- 若a
- 若 a>b,则舍弃序列A中较大的一半,同时舍弃序列B中较小的一半,要求舍弃的长度相等;
在保留的两个升序序列中,重复过程1)、2)、3),直到两个序列中均只含一个元素时为止,较小者即为所求的中位数。
代码实战如下:
抓住问题的本质,分奇偶数讨论,分析数值与位次之间的关系!
//
// Created by Zhu Shichong on 2023/1/9.
//
#include
#include
#define MaxSize 7
#define bool int
#define true 1
#define false 0
//查找两个升序列表合并后的中位数
//求解中位数的时候,如果有偶数个元素那么中位数处在
bool Bin_Search(int *a,int *b,int len){
int ans=-1;
int al,ar,bl,br,am,bm;
al=bl=0;
ar=br=len-1;
while(al!=ar){
am=(al+ar)/2;
bm=(bl+br)/2;
if((al+ar)%2==0){
if(a[am]>b[bm]){
ar=am;
bl=bm;
}else if(a[am]<b[bm]){
br=bm;
al=am;
}else{
return a[am];
}
}else{
if(a[am]>b[bm]){
ar=am;
bl=bm+1;
}else if(a[am]<b[bm]){
br=bm;
al=am+1;
}else {
return a[am];
}
}
}
ans=a[al]>b[bl]?b[bl]:a[al];
return ans;
}
int main() {
// 22,1,33,13,45,31,65,221,857,3
int a[MaxSize]={0,1,2,3,4,4,9};
int b[MaxSize]={0,5,5,6,7,8,10};
printf("result: %d",Bin_Search(a,b,MaxSize));
return 0;
}
测试数据如下:
int a[MaxSize]={0,1,2,3,4,4,9};
int b[MaxSize]={0,5,5,6,7,8,10};
结果如下:
result: 4
进程已结束,退出代码为 0