1. 相关概念
1.1 静态查找和动态查找
静态查找:数据集合稳定,不需要添加,删除元素的查找操作。
动态查找:数据集合在查找的过程中需要同时添加或删除元素的查找操作。
1.2 查找结构
对于静态查找来说,我们不妨可以用线性表结构组织数据,这样可以使用顺序查找算法,如果我们再对关键字进行排序,则可以使用折半查找算法或斐波那契查找算法等来提高查找的效率。
对于动态查找来说,我们则可以考虑使用二叉排序树的查找技术,另外我们还可以使用散列表结构来解决一些查找问题,这些技术我们都将在这部分教程里边介绍给大家。
2. 顺序查找
2.1 思路
顺序查找又叫线性查找,是最基本的查找技术,它的查找过程是:从第一个(或者最后一个)记录开始,逐个进行记录的关键字和给定值进行比较,若某个记录的关键字和给定值相等,则查找成功。如果查找了所有的记录仍然找不到与给定值相等的关键字,则查找不成功。
2.2 代码
首先是一种最直觉的实现方法
// 顺序查找,a为要查找的数组,n为要查找的数组的长度,key为要查找的关键字
int Sq_Search(int *a, int n, int key) //其中的 *a 的含义是数组的头指针,传入头指针相当于传入了这个数组
{
int i;
for( i=1; i <= n; i++ )
{
if( a[i] == key )
{
return i;
}
}
return 0;
}
但是上面这段代码的复杂度是 2n ,那有每一种复杂度为 n 的查找代码呢?有的,如下优化方案
// 顺序查找优化方案,a为要查找的数组,n为要查找的数组的长度,key为要查找的关键字
int Sq_Search(int *a, int n, int key)
{
int i = n;
a[0] = key
while( a[i] != key )
{
i--;
}
return i;
}
这个算法虽然复杂度只有 n ,但是同样存在着一个问题,就是它对于里面具有多个相同的数的情况是没有考虑在内的。
2.3 相关思考题
假设以下有一个结构体存放的是学生的记录,每条记录包括:学号、姓名、成绩,请编写一个程序,要求输出1024编号同学的具体信息。
2.3.1 代码
#include <stdio.h>
typedef struct student
{
int id;
char name[10];
float score;
}Student;
float search(Student stu[], int n, int key)
{
int i;
for( i=0; i < n; i++ )
{
if( stu[i].id == key )
{
return i.score;
}
}
return -1;
}
int main()
{
Student stu[4] = {
{1024, "小甲鱼", 100},
{1026, "秋舞斜阳", 60},
{1028, "黑夜", 100},
{1030, "迷途", 60}
};
float score;
score = search(stu, 4, 1024);
printf("1024号童鞋的成绩是:%f", score);
return 0;
}
3. 折半查找
如果从文件中读取的数据记录的关键字是有序排列的,则可以用一种效率比较高的查找方法来查找文件的记录,这就是折半查找法,又称为二分法搜索。
3.1 基本思想
减小查找序列的长度,分而治之地进行关键字的查找。先确定待查找记录的所在范围,然后逐渐缩小这个范围,直到找到该记录或查找失败(查无该记录)为止。
例如有序列:1 1 2 3 5 8 13 21 34 55 89(该序列包含 11 个元素,而且关键字单调递增。),现要求查找关键字 key 为 55 的记录。我们可以设指针 low 和 high 分别指向关键字序列的上界和下界,指针 mid 指向序列的中间位置,即 mid = (low+high)/2。如下图所示
首先将 mid 所指向的元素与 key 进行比较,因为我们这里 key = 55,大于 8,这就说明待查找的关键字一定位于 mid 和 high 之间。于是我们执行 low = mid+1; mid = (low+high)/2;如下图所示
然后再将 mid 所指的 34 与 key 进行比较,仍然 mid < key,所以继续执行 low = mid+1; mid = (low+high)/2;如下图所示
接下来仍然将 mid 所指的元素与 key 进行比较,结果相等,查找成功。返回 mid 的指针值,程序结束。假设我们要查找的关键字 key = 88,那么上述的查找还要继续进行下去 low = mid+1; mid = (low+high)/2;如下图所示
3.2 迭代实现
#include <stdio.h>
int bin_search( int str[], int n, int key )
{
int low, high, mid;
low = 0;
high = n-1;
while( low <= high )
{
mid = (low+high)/2;
if( str[mid] == key )
{
return mid; // 查找成功
}
if( str[mid] < key )
{
low = mid + 1; // 在后半序列中查找
}
if( str[mid] > key )
{
high = mid - 1; // 在前半序列中查找
}
}
return -1; // 查找失败
}
int main()
{
int str[11] = {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
int n, addr;
printf("请输入待查找的关键字: ");
scanf("%d", &n);
addr = bin_search(str, 11, n);
if( -1 != addr )
{
printf("查找成功, 关键字 %d 所在的位置是: %d\n", n, addr);
}
else
{
printf("查找失败!\n");
}
return 0;
}
3.3 递归实现
#include<stdio.h>
#define SIZE 10
typedef int ElemType;
int refind(ElemType *data,int begin,int end,ElemType num);
int main(void){
ElemType data[SIZE]={10,20,30,40,50,60,70,80,90,100};
ElemType num;
for(int i = 0;i<SIZE;i++)
printf("%d ",data[i]);
printf("\n请输入要查找的数据:\n");
scanf("%d",&num);
int flag = refind(data,0,SIZE,num);
printf("位置为:%d\n",flag);
return 0;
}
/
//递归
int refind(ElemType *data,int begin,int end,ElemType num)
{
if(begin > end)
{
printf("没找到\n");
return -1;
}
int mid = (begin+end)/2;
if(data[mid] == num)
{
return mid;
}else if(data[mid] <= num)
return refind(data,mid+1,end,num);
else
return refind(data,begin,mid-1,num);
}
4. 插值查找(按比例查找)
在上面的折半查找法中,每次都是对折之后再查找,但是实际上是可以不进行对折查找。比如说,我们在英语字典中查找某一个英语单词,首先会翻到这个单词的首字母的位置再进行查找,这种查找方式就叫做按比例查找。
4.1 基本思想
它的基本思想就是,根据关键之在数组中的位置来确定 mid 的位置。
4.2 代码
#include <stdio.h>
int bin_search( int str[], int n, int key )
{
int low, high, mid;
low = 0;
high = n-1;
while( low <= high )
{
mid = low + (key-a[low]/a[high]-a[low])*(high-low); // 插值查找的唯一不同点
if( str[mid] == key )
{
return mid;
}
if( str[mid] < key )
{
low = mid + 1;
}
if( str[mid] > key )
{
high = mid - 1;
}
}
return -1;
}
int main()
{
int str[11] = {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89};
int n, addr;
printf("请输入待查找的关键字: ");
scanf("%d", &n);
addr = bin_search(str, 11, n);
if( -1 != addr )
{
printf("查找成功,可喜可贺,可口可乐! 关键字 %d 所在的位置是: %d\n", n, addr);
}
else
{
printf("查找失败!\n");
}
return 0;
}
5. 斐波那契查找(黄金分割法查找)
我们知道黄金分割比是0.618,但是这个比例实际上和斐波那契数列也是有关系的,比如如下的斐波那契数列
在这个数列中可以看到,随着斐波那契数列数值的逐渐增大,两个数值之间的比例越来越接近与 0.618,这也就是斐波那契数列与黄金分割之间的关系。
5.1 基本思想
斐波那契查找的主要思想如下图所示
在这里面我们将数组分为前后两个部分,假设整个数组中元素的个数是 F[k]+1,那么将它分为前后两个部分,前面是 F[k-1],后面是 F[k+1],其中的 k 是指斐波那契数列中的位置,但是在实现的过程中数组往往是从 0 开始的,所以实际上是下面的这种形式
5.2 代码
#include <stdio.h>
#define MAXSIZE 20
void fibonacci(int *f) //使用递推关系生成斐波那契数列
{
int i;
f[0] = 1;
f[1] = 1;
for(i=2; i < MAXSIZE; ++i)
{
f[i] = f[i-2] + f[i-1];
}
}
// *a 是待查找数组的头指针,key 待查找的数字,n 是待查找数组中元素的个数
int fibonacci_search(int *a,int key,int n)
{
int low = 0;
int high = n - 1;
int mid = 0;
int k = 0;
int F[MAXSIZE];
int i;
fibonacci(F);
//找到与数组长度相对应的数列中的元素
while( n > F[k]-1 )
{
++k;
}
//如果数组的长度小于对应的斐波那契数列中元素的值,那么将他用最大值拓展
for( i=n; i < F[k]-1; ++i)
{
a[i] = a[high];
}
while( low <= high )
{
mid = low + F[k-1] - 1;
if( a[mid] > key )
{
high = mid - 1;
k = k - 1;
}
else if( a[mid] < key )
{
low = mid + 1;
k = k - 2;
}
else
{
if( mid <= high )
{
return mid;
}
else
{
return high;
}
}
}
return -1;
}
int main()
{
int a[MAXSIZE] = {1, 5, 15, 22, 25, 31, 39, 42, 47, 49, 59, 68, 88};
int key;
int pos;
printf("请输入要查找的数字:");
scanf("%d", &key);
pos = fibonacci_search(a, key, 13);
if( pos != -1 )
{
printf("\n查找成功,关键字 %d 所在的位置是: %d\n\n", key, pos);
}
else
{
printf("\未在数组中找到元素:%d\n\n", key);
}
return 0;
}