主要內容
基本概念
在介紹查找算法前我們先重溫幾個重要概念:
1)數據:客觀事物的符號表示,是所有能輸入到計算機中,並能被計算機程序處理的符號的總稱。如數學計算中用到的整數和實數,文本編輯中用到的字符串,多媒體程序處理的圖形、圖像、聲音及動畫等通過特殊編碼定義後的數據。
2)數據元素:數據的基本單位,在計算機中通常作爲一個整體進行考慮和處理。數據元素也稱記錄,用於描述一個完整的對象,該對象可以是一名學生的信息(記錄),某一次棋局,圖中的某一個頂點等。
3)數據項:組成數據元素的、有獨立含義的、不可分割的最小單位。如學生記錄中的學號、姓名、性別等都屬於數據項。
4)數據結構:相互之間存在一種或多種特定關係的數據元素的集合。數據結構包括邏輯結構(邏輯表示)和存儲結構(物理實現)。
5)查找表:由同一類型的數據元素(或記錄)構成的集合,可以由線性表、樹結構等多種數據結構來實現。
6)關鍵字:數據元素(或記錄)中的某個數據項的值,相當於“數據庫”知識中一行記錄的主碼(PRIMARY KEY)。
typedef struct /*定義數據元素結構體*/
{
KeyType key; /*關鍵字*/
OtherType Other; /*其他數據項*/
} Element;
typedef struct /*以順序表結構定義查詢表*/
{
/*順序表藉助數組存儲數據,因此需要空間基地址base(數組頭)和當前長度length(數組長度)*/
Element *base;
int length;
} SSTable; /*SSTable表示順序查詢表*/
7)查找:根據給定的某個值,在查找表中確定一個其關鍵字等於給定值的數據元素(或記錄)。同樣可以類比數據庫的數據查詢。針對不同的數據結構,查找算法也會有所不同。
8)平均查找長度:衡量查找算法性能的一個標準。
線性表的查找
在查找表的數據結構中,線性表是最簡單的一種。
順序查找(Sequential Search)
顧名思義,從查詢表的一頭順序往下查找,直至找到含有給定關鍵字的數據元素。
下面給出順序查找的兩種算法形式:
/*-------First-------*/
int Seq_Search(SSTable t, KeyType key)
{
for(int i = 0; i < length; i++)
{
if(t.base[i].key == key) return i;
}
return 0;
}
/*-------Second-------*/
int Seq_Search(SSTable t, KeyType Key)
{
t.base[0].key = key; /*將頭元素設爲監視哨*/
for(int i = t.length; t.base[i] != key; i--);
return i;
}
Tips:
1)base是數組頭,同時也作數組名,對數組和指針概念不清晰的同學請戳“這裏”。
2)在第一種算法形式中,base[]的空間也用來存放數據元素,所以循環是從0開始,到length前結束,共遍歷length個位置(但貌似一般習慣在1到length存放數據元素);
3)在第二種算法形式中,我們設置了“監視哨”,“監視哨”的含義可以先不去深入理解,我們先知道它的作用是什麼。
設置頭元素爲監視哨後,每一步循環都只需要判斷關鍵字是否爲給定值,而不需要先判斷i到哪了,再去判斷是否找到給定值。
與此同時,因爲設置了監視哨,存儲length個元素的位置變成了從1到length。
至於爲什麼第一種形式可以用從前往後查找,而第二種形式建議用從後往前查找,這就留給大家思考了。
4)雖然在第二種算法形式中減少了判斷步驟,但是兩種形式的時間複雜度是一樣的。不過實踐證明,在查詢數據很多很多的時候,第二種算法形式的處理時間幾乎是第一種的一半。
折半查找(Binary Search)
顧名思義,折半查找每一次查找比較都能使查找範圍縮小一半,它是一種效率較高的查找方法。
但是折半查找要求線性表必須採用順序存儲結構,而且表中元素必須按關鍵字有序排列(有點類似數據庫的索引)。
爲了標記出查找範圍,我們要設置三個變量low、high、middle分別表示查找範圍的下界、上界和中間位置(每次查找後更新賦值)。
提前路過的圈毛君:“總是一不留心就把“查找”寫成了“查詢”(數據庫留下的習慣),雖然這兩個詞意思一樣,但無奈博主是強迫症_(:з」∠)_喜歡維持前後文的用詞一致性。”
下面給出代碼:
int Bin_Search(SSTable t, KeyType key)
{
int low = 1, high = t.length;
while(low <= high)
{
int mid = (low + high) / 2;
if(t.base[mid] == key) return mid;
else if(t.base[mid] < key) low = mid+1;
else high = mid-1;
}
return 0;
}
在每次循環中,low、high、mid中的其中兩個值都要作更新,而且這個不斷縮小查找範圍的過程也很像一個不斷深入、細化的樹或圖的遍歷過程,因此我們很容易想到一個詞——遞歸。
下面給出折半查找的遞歸算法的實現:
int Bin_Search(SSTable t, KeyType key, int low, int high)
{
if(low <= high)
{
int mid = (low + high) / 2;
if(t.base[mid] == key) return mid;
else if(t.base[mid] < key) low = mid + 1;
else high = mid-1;
}
Bin_Search(t, key, low, high);
}
折半查找過程可用二叉樹來描述,由此得到的二叉樹稱爲判定樹。藉助判定樹可以很快求出折半查找的平均查找長度。
優點:
查找效率比順序查找高,時間複雜度爲O(log2n)。
缺點:
只適用於順序存儲結構的有序表,所以查找前必須先對錶進行排序,而排序本身就是一種費時的運算。而且,對於有序表並不方便進行插入、刪除操作,因此折半查找不適用於數據元素經常發生變動的查詢表。
分塊查找(Blocking Search)
分塊查找又稱索引順序查找,後一個名稱更能反映查找的特性,這是一種性能介於順序查找和折半查找之間的查找方法。
在該查找算法中,除了查詢表外,還需要爲查詢表建立一個索引表。
建立索引表的過程分三步:
1)將查詢表分塊,分成若干個子表,取出每個子表中的第一個數據元素的地址作爲索引表中第一個數據元素的其中一個數據項;
2)取出每個子表中的最大關鍵字作爲索引表每個數據元素的關鍵字;
3)根據關鍵字對每一塊進行排序。
分塊查找的過程分兩步:
1)通過折半查找確定待查記錄所在的塊(子表)。如給定關鍵字27,第一個塊的最大關鍵字爲22,第二個塊的最大關鍵字爲35,則待查數據位於第二個塊。
2)在塊中順序查找。從第二個塊中的第一個數據元素開始遍歷。
/*--------索引表--------*/
typedef struct
{
KeyType maxkey; /*每一塊的最大關鍵字*/
Element *first; /*每一塊的首元素地址*/
} IndexElement;
typedef struct
{
IndexElement *base;
int length;
} Index;
CreatIndex(Index &i);
/*--------分塊查詢--------*/
/*********分塊*********/
DivideBlock(SSTable t, Index &i, int blocknum) /*傳遞參數包括查詢表,索引表的引用,切分塊數*/
{
int avg_length = t.length / blocknum;
int j = k = 1;
while(j < t.length)
{
i.base[K].maxkey = Max(t, j, avg_length);
/*Max()函數可以求出查詢表t中 base[n](j <= n < j + avg_length)的最大關鍵字*/
/*若出現 n > t.length 的情況,給予相應處理*/
i.base[k].first = &t.base[j];
i.length++;
j = j + avg_length;
k++;
}
}
/*********查找**********/
int Blo_Search(SSTable t, Index i, Keytype key)
{
/*對索引表折半查找*/
/*對塊順序查找*/
}
優點:
進行插入、刪除操作時,只要確定該元素所在的塊,就能在塊內插入或刪除數據元素。由於塊內元素無序,插入和刪除時不需要大量移動數據元素。
缺點:
需要額外增加索引表的存儲空間,並對索引表排序。