查找概念
查找表(Search Table)是由同一類型的數據元素(或記錄)構成的集合。
關鍵字(Key)是數據元素中某個數據的值。又稱爲鍵值。可以標識一個記錄的某個數據項(字段),我們稱爲關鍵碼。
若此關鍵字可以唯一地標識一個記錄,則稱此關鍵字爲主關鍵字(Primary Key)。這說明對不同的記錄,其主關鍵字均不相同。主關鍵字所在的數據項稱爲主關鍵碼。
對於那些可能識別多個數據元素(或記錄)的關鍵字,我們稱爲次關鍵字(Sceondary Key)。
查找就是根據給定的某個值,在查找表中確定一個其關鍵字等於給定值的數據元素(或記錄)。
靜態查找表(Static Search Table):只作查找操作的查找表。
1. 查詢某個“特定的”數據元素是否在查找表中
2. 檢索某個“特定的”數據元素和各種屬性。
就是在在已經有的數據中找到我們需要的。但是往往有的時候我們是想在查找結果後去對結果進行一些操作。
動態查找表(Dynamic Search Table):在查找過程中同時插入查找表中不存在的數據元素,或者從查找表中刪除已經存在的某個數據元素。顯然動態查找表的操作就兩個:
1. 查找時插入數據元素
2. 查找時刪除數據元素
爲了提高查找的效率,我們需要專門爲查找操作設置數據結構,這種面向查找操作的數據結構稱爲查找結構。
從邏輯上來說,查找所基於的數據結構是集合,集合中的記錄之間沒有本質關係。可是要想獲得較高的查找性能,我們就不能不改變數據元素之間的關係,在存儲時(內存裏)可以將查找集合組織成表、樹等結構。
順序表查找
比如對散落的一堆書進行查找,散落的圖書可以理解爲一個集合,而將它們排列整齊,就如同是將此集合構造成一個線性表。我們要針對這一線性表進行查找操作,因此它就是靜態查找表。
順序查找(Sequential Search)又叫線性查找,是最基本的查找技術,它的查找過程是:從表中第一個(或最後一個)記錄開始,逐個進行記錄的關鍵字和給定值比較,若某個記錄的關鍵字和給定的值相等,則查找成功,找到所查的刻錄;如果直至最後一個(或第一個)刻錄,其關鍵字和給定值都不等時,則表中沒有所查的記錄查找不成功。
順序表查找算法
/*順序查找,a爲數組,n爲要查找的數組長度,key爲要查找的關鍵字*/
int Sequential_Search(int *a,int n,int key)
{
int i;
for(i=1;i<=n;i++)
{
if(a[i]==key)
return i;
}
return 0;
}
順序表查找優化
int Sequential_Search2(int *a,int n,int key)
{
int i;
a[0]=key; //設置a[0]爲關鍵字值,我們稱之爲“哨兵”
i=n; //循環從數組尾部開始
while(a[i]!=key)
{
i--;
}
return i; //返回0則說明查找失敗。
}
如果有key則返回i值,否則一定在最終a[0]處等於key,返回0。說明a[1]~a[n]中沒有關鍵字key,查找失敗。
這種在查找方向的盡頭放置“哨兵”免去了在查找過程中每一次比較後都要判斷查找位置是否越界的小技巧,看假與原先差別不大,但在總數據較多時,效率提高很大,是非常好的編碼技巧。
有序表查找
對於一個線性表有序時,對於查找總是很有幫助
折半查找
折半查找(Binary Search)技術,又稱爲二分查找。它的前提是線性表中的記錄必須是關鍵碼有序(通常從小到大有序),線性表必須採用順序存儲。取中間記錄作爲比較對象,若給定值與中間記錄的關鍵字相等,查找成功,若小於中間記錄的關鍵字,則在中間記錄的左半區繼續查找;若給定值大於中間記錄的關鍵字,則在中間記錄的右半區繼續查找。不斷重複上述過程,直至查找成功,或所有查找區域無記錄,查找失敗爲止。
{0,1,16,24,35,47,59,62,73,88,99},除0下標外共10個數字
對它進行查找是否存在62這個數。我們來看折半查找的算法是如何工作的
int Binary_Search(in *a,int n,int key)
{
int low,high,mid;
low=1;
hight=n;
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;
}
return 0;
}
插值查找
我們將折半查找的計算略微變成一下如下:
改進爲下面的計算方案:
將1/2改成了
實際上就是將查找代碼中的第8行改爲
插值查找(Interpolation Search),是根據要查找的關鍵字key與查找表中最大最小記錄的關鍵字比較後的查找方法,其核心就在於插值的計算公式
斐波那契查找
它是用黃金分割原理來實現的。爲了能夠介紹清楚這個查找算法,我們先需要有一個斐婆那契數列的數組。如圖
我們來看一下代碼如下:
int Fibonacci_Search(int *a ,int n,int key)
{
int low,higth,mid,i,k;
low=1;
high=n;
k=0;
while(n>F[k]-1) //計算n位於斐波那契數列的位置
k++;
for(i=n;i<F[k]-1;i++) //將不滿的數值補全
a[i]=a[n];
while(low<=high)
{
mid = low+F[k-1]-1; //計算當前分隔的下標
if(key<a[mid]) //若查找記錄小於當前分隔記錄
{
high = mid-1; //最高下標調整到分隔下標的mid-1處
k =k-1;//斐婆那契數列下標減一位。
}
else if(key>a[mid]) //查找記錄大於分隔記錄
{
low=mid+1;
k=k-2;
}
else
{
if(mid<=n)
return mid; //若相等則說明mid即爲查找到的位置
else
return n; //mid>n說明是補全數值,返回n
}
}
}
模擬一下
1. 程序開始運行,參數a={0,1,16,24,35,47,59,62,73,88,99} n=10,要查找的關鍵字key=59。注意此時我們已經有了事先計算好的全局變量數組F的具體數據,它是斐波那契數列F={0,1,1,2,3,5,8,13,21,…}
2. 第6~8行是計算當前的n處於斐波那契數列的位置。現在n=10,F[6]<n<F[7],所以計算得出k=7
3. 第9~10行,由於k=7,計算時,是以F[7]=13爲基礎,而a中最大的僅是a[10],後面的a[11],a[12]均未賦值,這不能構成有序數列,因此將它們賦值爲最大數組值,所以此時,a[11]=a[12]=a[10]=99
4. 11~31行查找正式開始
5. 第13行,mid=1+F[7-1]-1=8,也就是說,我們第一個要對比的數值是從下標爲8開始的。
6. 由於此時key=59而a[8]=73,因此執行第16~17行,得到high=7,k=6
7. 再次循環,mid=1+F[6-1]-1=5,此時a[5]=47<key,因此執行21~22行,得到low=6,k=6-2=4。注意此時k下調2個單位。
8. 再次循環,mid=6+F[4-1]-1=7。此時a[7]=62>key,因此執行16~17行,得到high=6,k=4-1=3;
9. 再次循環,mid=6+F[3-1]-1=6。此時a[6]=59=key。因此執行執行第26~27行,得到返回值6。程序運行結構。
如果key=99,此時查找循環第一次時,mid=8與上例是相同的,第二次循環時,mid=11,如果a[11]沒有值就會使得與key的比較失敗,爲了避免這樣的情況出現,第9~10行的代碼就起到這樣的作用。
斐波那契查找算法的核心在於:
1. 當key=a[mid]時,查找就成功
2. 當key<a[mid]時,新範圍是第low個到第mid-1個,此時範圍個數爲F[k-1]-1個
3. 當key>a[mid]時,新範圍是第mid+1個到第hight個,此時範圍個數爲F[k-2]-1個
折半查找是進行的加法與除法運算(mid=(low+high)/2),插值查找進行的是複雜的四則運算(mid=low+(high-low)*(key-a[low])/(a[high]-a[low])),而斐波那契查找只是最簡單的加減法運算。對於海量數據會有效率上的影響
本質上有序表的查找本質上是分隔點選擇不同,各有優劣。