數據結構(六)查找---有序表查找(三種查找方式:折半,插值,斐波拉契查找)


前提

有序表查找要求我們的數據是有序的,是排序好的,我們只需要進行查找即可

我們下面將介紹折半查找(二分查找),插值查找,斐波那契查找

一:折半查找

(一)定義

二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須采用順序存儲結構,而且表中元素按關鍵字有序排列。

(二)查找過程

首先,假設表中元素是按升序排列,將表中間位置記錄的關鍵字與查找關鍵字比較,如果兩者相等,則查找成功;
否則利用中間位置記錄將表分成前、后兩個子表,如果中間位置記錄的關鍵字大於查找關鍵字,則進一步查找前一子表,否則進一步查找后一子表。
重復以上過程,直到找到滿足條件的記錄,使查找成功,或直到子表不存在為止,此時查找不成功。

(三)代碼實現

int Binary_Search(int *a, int n, int key)
{
    int low, high, mid;
    low = 0;
    high = n - 1;
    while (low<=high)
    {
        mid = (low + high) / 2;
        if (a[mid] < key)
            low = mid + 1;
        else if (a[mid]>key)
            high = mid - 1;
        else
            return mid;
    }
    return -1;
}

int main()
{
    int a[10] = { 1, 6, 12, 21, 30, 31, 32, 42, 49, 52 };
    int index;
    index=Binary_Search(a, 10, 49);
    if (index != -1)
        printf("find key in %d\n", index);
    else
        printf("not find key\n");
    system("pause");
    return 0;
}

(四)性能分析

其時間復雜度是O(logn),不過由於折半查找的前提是需要有序表順序存儲,對於靜態查找表,一次排序后不再變化,這樣的算法是比較好的。但是對於需要頻繁插入或刪除操作的數據集來說,維護有序的排序會帶來不小的工作量,不建議使用。

二:插值查找(按比例查找法)

(一)算法分析:

對於前面的折半查找,為啥一定要折一般,而不是1/4或者其他?
比如:我們查字典Apple,我們會先從中間查找,還是有一定目的的向前找。
或者在0
-1000個數之間有200個元素從小到大均勻分布排序,我們若是需要查找到15,我們會去中間查找500,還是直接去前面開始查找。 以上都說明我們上面的折半查找可以再進行改進

首先我們對折半公式進行改寫:

 

折半查找這種查找方式,不是自適應的(也就是說是傻瓜式的),其前面的查找系數(比例參數)始終是1/2

通過類比,我們可以將查找的點改進為如下:

也就是將上述的比例參數1/2改進為自適應的,根據關鍵字在整個有序表中所處的位置,讓mid值的變化更靠近關鍵字key,這樣也就間接地減少了比較次數。

(二)基本思想:

基於二分查找算法,將查找點的選擇改進為自適應選擇,可以提高查找效率。當然,插值查找也屬於有序查找。

(三)代碼實現:

int Insert_Search(int *a, 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 (a[mid] < key)
            low = mid + 1;
        else if (a[mid]>key)
            high = mid - 1;
        else
            return mid;
    }
    return -1;
}
實現方法和折半一樣,只是修改了mid

(四)性能分析:

而插值查找則比較靈活,並不是簡單的從中間進行的,它是根據我們需要查詢的值的漸進進行搜索的.

插值查找的不同點在於每一次並不是從中間切分,而是根據離所求值的距離進行搜索的.
對於表長較大,而關鍵字分布又比較均勻的查找表來說,插值查找算法的平均性能比折半查找要好的多。反之,數組中如果分布非常不均勻,那么插值查找未必是很合適的選擇。
時間復雜度:平均情況O(loglog(n)),最壞O(log(n))

三:斐波那契查找(僅使用加法減法實現二分查找)

除了插值查找之外,我們再介紹一種有序查找,可以對折半進行優化,那就是斐波那契查找,利用了黃金分割原理來實現的
斐波那契查找與折半查找很相似,他是根據斐波那契序列的特點對有序表進行分割的。他要求開始表中記錄的個數為某個斐波那契數小1,即n=F(k)-1;

(一)斐波那契數列

越向后,每兩個數之間相除越接近黃金比例

(二)斐波拉契查找實現

1.首先我們要創建一個斐波拉契數列

2.獲取我們的數組大小n(我們不考慮0下標,因為我們的斐波那契數列從0開始,無法構成黃金比例),在斐波拉契數列中的位置

例如上面的查找數組大小為n=10,F[6]<n<F[7],所以得出其位置為k=7

3.因為我們的k=7,F[7]=13,而a數組最大為10,我們需要對其按照F[7]=13補齊數組,使其長度為F[7]-1=12,將a[11]=a[10],a[12]=a[10]

4.開始正式查找,按照上圖mid=low+F[k-1]-1;

 

(三)思考:n=F(k)-1, 表中記錄的個數為某個斐波那契數小1。這是為什么呢?

 推文:斐波那契查找原理詳解與實現

是為了格式上的統一,以方便遞歸或者循環程序的編寫。
表中的數據是F(k)-1個,使用mid值進行分割又用掉一個,那么剩下F(k)-2個。
正好分給兩個子序列,每個子序列的個數分別是F(k-1)-1與F(k-2)-1個,格式上與之前是統一的。
不然的話,每個子序列的元素個數有可能是F(k-1),F(k-1)-1,F(k-2),F(k-2)-1個,寫程序會非常麻煩。

(四)算法實現

void Fibonacci(int **Fb, int n)
{
    int f1, f2,ft;
    int count=3;
    f1 = 1; f2 = 1;

    while (f2<n)
    {
        ft = f1 + f2;
        f1 = f2;
        f2 = ft;
        count++;
    }
    (*Fb) = (int *)malloc(count*sizeof(int));
    (*Fb)[0] = 0;
    (*Fb)[1] = 1;
    for (f1 = 2; f1 <= count - 1; f1++)
        (*Fb)[f1] = (*Fb)[f1 - 1] + (*Fb)[f1 - 2];
}
創建斐波那契數列
int Fibonacci_Search(int *a, int n, int key)
{
    int low, high, mid, i, j,k;
    int *Fb,*temp;
    //根據實際情況來初始化
    low = 1;
    high = n;

    Fibonacci(&Fb, n);
    k = 0;

    while (n > Fb[k])    //計算n位於斐波那契數列位置
        k++;

    temp = (int *)malloc((Fb[k] - 1 + 1)*sizeof(int));//后面加1是因為還有個下標0空間
    memcpy(temp, a, n*sizeof(n + 1));

    for (i = n + 1; i <= Fb[k] - 1; i++)    //我們要查找的是數組1到F[k]-1,而實際數組長要包括一個下標0空間
        temp[i] = a[n];

    while (low<=high)
    {
    mid = low + Fb[k - 1] - 1; if (key == temp[mid])
        {
            //分情況,是前半段,正常輸出,后半段判斷是不是在我們補充的數組里面,這時我們返回原數組最后一個下標
            if (mid <= n)
                return mid;
            else
                return n;
        }
        else if (key>temp[mid])
        {
            low = mid + 1;
            k = k - 2;
        }
        else
        {
            high = mid - 1;
            k = k - 1;
        }
    }
    return 0;    //0是未找到,0下標使無意義的
}
int main()
{
    int a[11] = { 0,1,16,24,35,47,59,62,73,88,99};
    int index;
    index = Fibonacci_Search(a, 10, 73);
    if (index != -1)
        printf("find key in %d\n", index);
    else
        printf("not find key\n");
    system("pause");
    return 0;
}

 (五)性能分析

其時間復雜度也是O(logn),就平均性能來說,斐波那契查找由於折半查找。但是若是最壞情況,比如key=1,始終處於左側長半區查找,則效率低於折半查找。

四:總結

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM