一.查找及其相關概念
查找,就是根據給定的某個值,在查找表中確定一個關鍵字等於數據值的數據元素的過程
查找表按操作方式可分爲兩種:
靜態查找表:只做查找操作的查找表
動態查找表:在查找過程中插入新的數據元素或刪除原有的數據元素
二.靜態查找的三種具體方式
1.順序查找算法
順序查找,也稱線性查找,是從第一個元素開始,將後面的每個元素與給定元素進行比對,若相同,則返回該元素;若沒有與之相同的元素,返回空
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define MAXSIZE 100
typedef int Status;
typedef int ElemType;
/*線性查找*/
Status SeqSearch(ElemType *a,int num,ElemType key){
int i=0;
for(i;i<num;i++){
if(a[i]==key)
return i+1;
}
return ERROR;
}
int main(void){
ElemType arr[11]={0,1,16,24,35,47,59,62,73,88,99};
srand(time(0));
int i=rand()%11;
printf("查找值爲%d的元素,位序爲%d\n",arr[i],SeqSearch(arr,11,arr[i]));
}
代碼很簡單,調用rand函數是用來隨機生成數組索引的
線性查找是最簡單的查找方式,但一般來說越簡單的東西時間效率往往不會太高,例如簡單的冒泡排序。那麼我們如何對這個算法進行優化呢?我們注意到在代碼中每次for循環中都會進行i是否越界的比較,那麼我們可以這樣修改
/*優化後的線性查找*/
Status SeqSearch2(ElemType *a,int num,ElemType key){
int i=num-1;/*從最後一位開始查找*/
a[0]=key;/*設置a[0]爲關鍵字位,也稱爲“哨兵”*/
while(a[i]!=key){
i--;
}
return i;/*這個算法相當於浪費a[0]位,所以查到的位序比第一種算法少1*/
}
int main(void){
ElemType arr[11]={0,1,16,24,35,47,59,62,73,88,99};
srand(time(0));
int j=rand()%11+1;
int Status=SeqSearch2(arr,11,arr[j]);
if(Status)
printf("查找值爲%d的元素,位序爲%d\n",arr[j],Status);
else
printf("查找失敗\n");
}
這種方法通過在盡頭設置哨兵,免去了每次都要判斷是否越界的過程,在數據量較大時,這個算法與前一種算法的差異將非常大
順序查找最後的情況是第一次查找就找到,時間複雜度爲O(1),最差爲O(n),平均來說,時間複雜度爲O(n)
2.二分查找
二分查找要求查找表中的數據的關鍵碼必須有序(通常要按從小到大排列),必須使用線性結構存儲;具體思路是,將關鍵值與中間元素作比較,若大於中間元素,則在右半區中繼續查找;若小於中間元素,則在左半區繼續查找,直到找到或失敗爲止
Status BinarySearch(ElemType *a,int num,ElemType key){
int low=0,high=num-1,mid=0;
while(low<=high){
mid=(low+high)/2;/*折半*/
if(key<a[mid])/*在左半區*/
high=mid-1;
else if(key>a[mid])
low=mid+1;
else
return mid+1;
}
return ERROR;
}
int main(void){
ElemType arr[11]={0,1,16,24,35,47,59,62,73,88,99};
srand(time(0));
int i=rand()%11;
int j=rand()%11+1;Status= BinarySearch(arr,11,arr[j]);
if(Status)
printf("二分查找值爲%d的元素,位序爲%d\n",arr[j],Status);
else
printf("查找失敗\n");
}
上圖清晰地分析了整個算法的執行過程,圖片來自《大話數據結構》
3.插值查找
想象這樣一個,我們要在英文詞典中查詢apple這個單詞,我們很自然的會從前幾頁開始翻,而查zoo這個單詞時,我們會從後幾頁開始翻。但如果使用二分查找算法呢?只會機械地從中間開始翻,然後逐步縮小範圍,如果要查詢第一個數據或最後一個數據這種極端情況,二分查找就顯得不再合適了。
鑑於此種情況,偉大的數學家們開始思考如何改進這個算法,之後,發現了這樣的情況:
如果將mid=(low+high)/2稍作變換,就可以得到mid=low+(high-low)/2
換句話說,二分查找中的mid等於 最低下表low加上high和low差值的一半,數學家們從這個”1/2”入手,做出瞭如下的改進:
mid=low+ (high-low)*(key-a[low])/(a[high]-a[low])
單純從時間複雜度上看,它和二分查找相同,均爲對數級算法,但是對於表長大且關鍵詞分佈均勻的情況,它的平均性能優於二分查找,但遇到一些極端數據的情況,插值查找也不一定是好的選擇。
/*插值查找算法*/
Status Interpolation(ElemType *a,int num,ElemType key){
int low=1,high=num-1,mid=0;
while(low<=high){
mid=low+ (high-low)*(key-a[low])/(a[high]-a[low]); /* 插值 */
if(key<a[mid])/*在左半區*/
high=mid-1;
else if(key>a[mid])
low=mid+1;
else
return mid+1;
}
return ERROR;
}
int main(void){
ElemType arr[11]={0,1,16,24,35,47,59,62,73,88,99};
srand(time(0));
int i=rand()%11;
int j=rand()%11+1;
Status=Interpolation(arr,11,arr[j]);
if(Status)
printf("插值查找值爲%d的元素,位序爲%d\n",arr[j],Status);
else
printf("查找失敗\n");
}
4.Fabbonaci查找
1.查找思想::
假設查找表中的某個數據比某個Fabbonaci數小1,用low,high,mid分別表示查找表下界,上界和分割位置,當:
key<a[mid]時,調整到左半區
key>a[mid]時,調整到左半區
key==a[mid]時,返回mid,查找成功
代碼如下:
Status FabbonaciSearch(ElemType *a,int num,ElemType key){
int high=num,low=1,mid=0,k=0,i=0;
while(num>Fab[k]-1)//第一步先計算num在Fabbonaci數列中的位置
k++;
for(i=num;i<Fab[k];i++)/*將數組a中未初始化的數據補全*/
a[i]=a[num];
while(low<=high){
mid=low+Fab[k-1]-1;
/*調整到右半區時,k減小2*/
if(key>a[mid]){
low=mid+1;
k=k-2;
}
/*調整到左半區時,k的值減小1*/
else if(key<a[mid]){
high=mid-1;
k=k-1;
}
/*如果k==mid,說明查找成功*/
else{
if(mid<=num)
return mid;
else
return num;/*若mid>num,說明是補全的數據,返回n*/
}
}
return 0;
}
int main(void){
ElemType arr[11]={0,1,16,24,35,47,59,62,73,88,99};
for(int i=0;i<20;i++){
Fab[i]=Fabbonaci(i);
}
srand(time(0));
int i=rand()%11;
int j=rand()%11+1;
Status=FabbonaciSearch(arr,10,arr[j]);
if(Status)
printf("Fabbnaci查找值爲%d的元素,位序爲%d\n",arr[j],Status);
else
printf("查找失敗\n");
}
這兩張圖片清晰地顯示了算法運行的過程
注意到在確定num在Fabbonaci數列中的位置的循環之後,有一個補全數據的循環,這個循環的作用在於,當key值爲99時,第一步的mid=8,a[8]小於99,需要調整到右半區,則low=mid+1,即low=9,所以mid的新值爲9+Fab[4]-1=11。但a[11]並沒有初始化,其內部是一個隨機值,這個時候用key與a[11]作比較就會產生不可預知的後果,所以我們要加上一個補全數據的循環,這是這個算法中一個令人難以理解的部分,需要在紙上一步步演繹才能理解其內部的巧妙
Fabbonaci查找的時間複雜度爲O(logn),看似與二分,插值查找相同,但由於Fabbonaci中的mid值只需要進行簡單的加減運算,當數據量巨大的時候,這個差異就會被成倍放大,三種查找各有優劣,使用前應加以考慮