前提
有序表查找要求我們的數據是有序的,是排序好的,我們只需要進行查找即可
我們下面將介紹折半查找(二分查找),插值查找,斐波那契查找
一:折半查找
(一)定義
二分查找也稱折半查找(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,始終處於左側長半區查找,則效率低於折半查找。
四:總結