查找是数据处理经常进行的操作。这里我介绍常见的几种,主要有静态查找,动态查找,哈希查找。
静态查找有:二分查找,顺序查找,插值查找 ,斐波那契查找。
动态查找:主要针对二叉树。
哈希查找 :主要理解哈希查找的思想 。
第一种:二分查找 。
在一个查找区,确定中心位置后,用待查找的值与中心值比较,前者大,就把查找区锁定到后半段,反之,锁定在前半段。这样的过程一直进行,知道查找区边界结束 。
有序顺序表的二分查找程序如下 :
//二分查找
#include
int binsearch(int *ListSeq,int ListLength,int KeyData)
{
int low = 0;
int mid;
int high = ListLength - 1;
while(low <= high)
{
mid = (low + high)/2;
if(KeyData < ListSeq[mid])
high = mid -1;
else if(KeyData > ListSeq[mid])
low = mid + 1;
else
return mid;
}
return -1;
}
第二种:顺序查找
顺序查找:顾名思义就是,从第一个开始逐个把查找区的值与待查值比较,知道后边界结束。
程序如下:
//顺序查找
#include
int FindSeq(int *ListSeq,int ListLength,int KeyData)
{
int i;
for(i = 0;i < ListLength,i++)
{
if(ListSeq[i] == KeyData)
return i;
}
return 0;
}
int main()
{
int a[5] = {34,35,26,89,56};
int Data = FindSeq(a,5,89);
if(data != 0)
printf("查找成功,是第%d个元素",Data);
else
printf("查找失败");
}
第三种:插值查找
我们由二分查找知道,是折半查找,那么我们是不是可以折四分之一查找呢,或者更多,比如,你在英汉词典中查找“apply”,你一定是翻书的前面,而不是后面。如果查“zoo”,你会从书的最后面开始找,而不是,从中间去找 。
再例如,从数值范围为1~10000的100个元素从小到大均匀分布的数组中查找5,我们是不是只需要考虑从下标较小的开始查找,插值查找的中心思想。
程序如下:
//插值查找(二分查找加强版)
#include
int binsearch(int *ListSeq,int ListLength,int KeyData)
{
int low = 0;
int mid;
int high = ListLength - 1;
while(low <= high)
{
mid = low + (KeyData - ListSeq[low])/(ListSeq[high] - ListSeq[low])*(low + high);
if(KeyData < ListSeq[mid])
high = mid -1;
else if(KeyData > ListSeq[mid])
low = mid + 1;
else
return mid;
}
return -1;
}
斐波那契查找其实也是二分查找的一种提升算法,相对于二分查找,一般将待比较的key值与 第min = (low + high)/ 2 的位置比较,而,斐波那契查找比较结果分三种情况:
1,相等。
2,大于, low = mid +1, k -= 2 ;
3,小于, high = high -1,k -= 1 ;
其实,斐波那契查找就是利用斐波那契数列的性质,斐波那契数列的前后两个数的比值越来越接近黄金比例0.618.所以一个长度只要可以被黄金分割,那么分割以后的片段继续黄金分割,循环。
程序如下:
//斐波那契查找
#include
#include
#define MAx 20
void Fibonacci(int *f) //产生斐波那契数列
{
int i;
f[0] = 0;
f[1] = 1;
for(i = 2;i < MAX;++i)
{
f[i] = f[i-2] + f[i-1];
}
}
int Fibonacci_search(int *a,int key;,int n)
{
int i,j = 0;
int low = 0;
int mid;
int high = n -1;
int F[MAX];
Fibonacci(F);
while(n >F[j] -1) //计算出n在斐波那契中的数列
++j;
for(i = n;i key)
{
high = mid -1;
j = j - 1;
}
else if(a[mid]
第五种:希尔查找。
什么是哈希表(Hash)?
我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素"分类",然后将这个元素存储在相应"类"所对应的地方。但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了"冲突",换句话说,就是把不同的元素分在了相同的"类"之中。后面我们将看到一种解决"冲突"的简便做法。
总的来说,"直接定址"与"解决冲突"是哈希表的两大特点。
什么是哈希函数?
哈希函数的规则是:通过某种转换关系,使关键字适度的分散到指定大小的的顺序结构中,越分散,则以后查找的时间复杂度越小,空间复杂度越高。
算法思想:哈希的思路很简单,如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值。这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键。
算法流程:
1)用给定的哈希函数构造哈希表;
2)根据选择的冲突处理方法解决地址冲突;
常见的解决冲突的方法:拉链法和线性探测法。
3)在哈希表的基础上执行哈希查找。
哈希表是一个在时间和空间上做出权衡的经典例子。如果没有内存限制,那么可以直接将键作为数组的索引。那么所有的查找时间复杂度为O(1);如果没有时间限制,那么我们可以使用无序数组并进行顺序查找,这样只需要很少的内存。哈希表使用了适度的时间和空间来在这两个极端之间找到了平衡。只需要调整哈希函数算法即可在时间和空间上做出取舍。
复杂度分析:
单纯论查找复杂度:对于无冲突的Hash表而言,查找复杂度为O(1)(注意,在查找之前我们需要构建相应的Hash表)。
使用Hash,我们付出了什么?
我们在实际编程中存储一个大规模的数据,最先想到的存储结构可能就是map,也就是我们常说的KV pair,经常使用Python的博友可能更有这种体会。使用map的好处就是,我们在后续处理数据处理时,可以根据数据的key快速的查找到对应的value值。map的本质就是Hash表,那我们在获取了超高查找效率的基础上,我们付出了什么?
Hash是一种典型以空间换时间的算法,比如原来一个长度为100的数组,对其查找,只需要遍历且匹配相应记录即可,从空间复杂度上来看,假如数组存储的是byte类型数据,那么该数组占用100byte空间。现在我们采用Hash算法,我们前面说的Hash必须有一个规则,约束键与存储位置的关系,那么就需要一个固定长度的hash表,此时,仍然是100byte的数组,假设我们需要的100byte用来记录键与位置的关系,那么总的空间为200byte,而且用于记录规则的表大小会根据规则,大小可能是不定的。
以上就是对查找算法的相关介绍,至于动态查找,这里不做详解。