三種靜態查找的思路及具體實現

一.查找及其相關概念

查找,就是根據給定的某個值,在查找表中確定一個關鍵字等於數據值的數據元素的過程

查找表按操作方式可分爲兩種:
靜態查找表:只做查找操作的查找表
動態查找表:在查找過程中插入新的數據元素或刪除原有的數據元素

二.靜態查找的三種具體方式

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值只需要進行簡單的加減運算,當數據量巨大的時候,這個差異就會被成倍放大,三種查找各有優劣,使用前應加以考慮

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章