只要打开电脑,就会涉及查找技术。如硬盘中文件搜索,浏览器中的搜索“关键字key”等,无论是数据库,还是普通的ERP系统,查找功能数据处理的一个基本,同时也是一个非常重要的功能。所有这些需要被查找数据的集合,统称为“查找表”。数据查找并不复杂,但是如何实现数据又快又好地查找呢?前人在实践中积累的一些方法,值得我们好好学些一下。我们假定查找的数据唯一存在,数组中没有重复的数据存在。
我们如何在查找表里面找到我们想要的那个数据。此时数据本身没有特征,所以我们需要的那个数据可能出现在数组的各个位置,可能在数据的开头位置,也可能在数据的结束位置。这种性质要求我们必须对数据进行遍历之后才能获取到对应的数据。
(1) 普通的顺序查找(无序查找)
顺序查找(Sequential Search)又叫线型查找,是基本的查找技术。其基本过程是:从查找表中的第一个(或者最后一个)记录开始,逐个进行给定值与关键字的比较,若相等则查找成功;如果直到最后一个(或者第一个)记录,关键字和给定值不等,则查找不成功.
[cpp] view plain copy
int find(int array[], int length, int value)
{
if(NULL == array || 0 == length)
return -1;
for(int index = 0; index < length; index++){
if(value == array[index])
return index;
}
return -1;
}
由于每次循环时都要对index是否越界,即是否小于等于length做判断,因此我们可以通过设置哨兵进行改进。
int find(int array[], int length, int value)
{
if(NULL == array || 0 == length)
return -1;
int i = length;
array[0] = value; //注意此时数组存储在array[1]到array[length]中
while(array[i]!= key)//循环从尾部开始
i--;
return i; //返回0则说明查找失败
}
此时代码从尾部开始查找,由于array[0] = value,也就是说如果在array[i]有key则返回i值,查找成功,否则一定在最终的array[0]处等于value,此时返回的是0,即说明array[1]-array[length]中没有关键字value,查找失败。
分析:对于顺序查找而言,最好的情况是要查找的值在第一个位置就找到了,算法时间复杂度为O(1),最坏的情况是遍历所有元素,需要n次,时间复杂度为O(n),由于关键字出现在任何一个位置上的概率是相等的,因此平均查找次数为(n+1)/2,所以时间复杂度是O(n)。当n很大时,查找效率极为低下,由于这个算法简单,对静态查找表的记录没有任何要求,在一些小型数据的查找时,可以使用。
(2)有序表查找
如果数据排列地非常整齐,那结果会是什么样的呢?就像在生活中,如果平时 不注意收拾整齐,那么找东西的时候非常麻烦,效率很低;但是一旦东西放的位置固定下来,所有东西都归类放好,那么结果就不一样了,我们就会形成思维定势,这样查找东西的效率就会非常高。下面我们介绍几种方法:
二分法查找:
int binary_searcht(int array[], int length, int value)
{
if(NULL == array || 0 == length)
return -1;
int start = 0;
int end = length -1;
while(start <= end){
int middle = start + ((end - start) >> 1);
if(value == array[middle])
return middle;
else if(value > array[middle]){
start = middle + 1;
}else{
end = middle -1;
}
}
return -1;
}
分析::查找最好情况是1次,最坏情况是需要log(n+1)/log(2),即时间复杂度为O(logn),理论证明见《算法导论》.
- 插值查找
其思想与折半查找较为相似,根据要查找的关键字value与查找表中最大最小记录的关键字比较后的查找方法,由于是在有序表中查找,例如是在升序的表中查找,对于一个较小的值我们自然想到在查找表前面部分进行查找,因此我们将上述middle语句更改为:
middle = start+(end - start)*(value-array[start])/(array[end] - array[start]);即可,这里不再赘述。 - 斐波那契额查找
相对于折半查找,一般将待比较的value值与第mid=(low+high)/2位置的元素比较,比较结果分三种情况
1)相等,mid位置的元素即为所求
2)> ,low=mid+1;
3) < ,high=mid-1;
斐波那契查找与折半查找很相似,他是根据斐波那契序列的特点对有序表进行分割的。他要求开始表中记录的个数为某个斐波那契数小1,即
n=F(m)-1;开始将k值与第F(k-1)位置的记录进行比较(mid=low+F(k-1)-1),比较结果也分为三种:
1)相等,mid位置的元素即为所求
2)> ,low=mid+1,k-=2;
(说明:low=mid+1说明待查找的元素在[mid+1,hign]范围内,k-=2 说明范围[mid+1,high]内的元素个数为n-(F(k-1)) = Fk-1-F(k-1) =Fk-F(k-1)-1=F(k-2)-1个,所以可以递归的应用斐波那契查找)
3)< ,high=mid-1,k-=1;
说明:low=mid+1说明待查找的元素在[low,mid-1]范围内,k-=1 说明范围[low,mid-1]内的元素个数为F(k-1)-1 个,所以可以递归 的应用斐波那契查找
斐波那契查找的核心是:
1)当key=a[mid]时,查找成功;
2)当key
// 斐波那契查找.cpp
#include "stdafx.h"
#include <memory>
#include <iostream>
using namespace std;
const int max_size=20;//斐波那契数组的长度
/*构造一个斐波那契数组*/
void Fibonacci(int * F)
{
F[0]=0;
F[1]=1;
for(int i=2;i<max_size;++i)
F[i]=F[i-1]+F[i-2];
}
/*定义斐波那契查找法*/
int Fibonacci_Search(int *a, int n, int key) //a为要查找的数组,n为要查找的数组长度,key为要查找的关键字
{
int low=0;
int high=n-1;
int F[max_size];
Fibonacci(F);//构造一个斐波那契数组F
int k=0;
while(n>F[k]-1)//计算n位于斐波那契数列的位置
++k;
int * temp;//将数组a扩展到F[k]-1的长度
temp=new int [F[k]-1];
memcpy(temp,a,n*sizeof(int));
for(int i=n;i<F[k]-1;++i)
temp[i]=a[n-1];
while(low<=high)
{
int mid=low+F[k-1]-1;
if(key<temp[mid])
{
high=mid-1;
k-=1;
}
else if(key>temp[mid])
{
low=mid+1;
k-=2;
}
else
{
if(mid<n)
return mid; //若相等则说明mid即为查找到的位置
else
return n-1; //若mid>=n则说明是扩展的数值,返回n-1
}
}
delete [] temp;
return -1;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a[] = {0,16,24,35,47,59,62,73,88,99};
int key=100;
int index=Fibonacci_Search(a,sizeof(a)/sizeof(int),key);
cout<<key<<" is located at:"<<index;
system("PAUSE");
return 0;
}
分析:关于斐波那契查找, 如果要查找的记录在右侧,则左侧的数据都不用再判断了,不断反复进行下去,对处于当众的大部分数据,其工作效率要高一些。所以尽管斐波那契查找的时间复杂度也为O(logn),但就平均性能来说,斐波那契查找要优于折半查找。可惜如果是最坏的情况,比如这里key=1,那么始终都处于左侧在查找,则查找效率低于折半查找。
还有关键一点,折半查找是进行加法与除法运算的(mid=(low+high)/2),插值查找则进行更复杂的四则运算(mid = low + (high - low) * ((key - a[low]) / (a[high] - a[low]))),而斐波那契查找只进行最简单的加减法运算(mid = low + F[k-1] - 1),在海量数据的查找过程中,这种细微的差别可能会影响最终的效率。
(3)二叉搜索树
二叉搜索树或者是一棵空树或者有下列性质:
1.若左子树不为空,则左子树上的所有结点的值均小于根节点的值
2.若右子树不为空,则右子树上的所有结点的值均大于根节点的值
3.它的左右子树也分别为二叉搜索树
二叉树定义:
typedef struct _NODE
{
int data;
struct _NODE* left;
struct _NODE* right;
}NODE;
查找过程如下:
const NODE* find_data(const NODE* pNode, int data){
if(NULL == pNode)
return NULL;
if(data == pNode->data)
return pNode;
else if(data < pNode->data)
return find_data(pNode->left, data);
else
return find_data(pNode->right, data);
}
分析:若果二叉树是比较平衡的,即其深度与完全二叉树相同,那么查找的时间复杂度为O(logn),近似于折半查找,最坏的情况下是斜树,查找时间复杂度是O(n),对于二叉搜索树的其它诸如插入(insert),删除(delete)操作,在其它博客中有,可自行查找。
另外还有AVL树,多路查找树(B树),以及散列表查找(哈希表)另有单独博客讲解。