查找表介紹
在日常生活中,幾乎每天都要進行一些查找的工作,在電話簿中查閱某個人的電話號碼;在電腦的文件夾中查找某個具體的文件等等。本節主要介紹用於查找操作的數據結構——查找表。
查找表是由同一類型的數據元素構成的集合。例如電話號碼簿和字典都可以看作是一張查找表。
一般對於查找表有以下幾種操作:
- 在查找表中查找某個具體的數據元素;
- 在查找表中插入數據元素;
- 從查找表中刪除數據元素;
靜態查找表和動態查找表
在查找表中只做查找操作,而不改動表中數據元素,稱此類查找表為靜態查找表;反之,在查找表中做查找操作的同時進行插入數據或者刪除數據的操作,稱此類表為動態查找表。
關鍵字
在查找表查找某個特定元素時,前提是需要知道這個元素的一些屬性。例如,每個人上學的時候都會有自己唯一的學號,因為你的姓名、年齡都有可能和其他人是重復的,唯獨學號不會重復。而學生具有的這些屬性(學號、姓名、年齡等)都可以稱為關鍵字。
關鍵字又細分為主關鍵字和次關鍵字。若某個關鍵字可以唯一地識別一個數據元素時,稱這個關鍵字為主關鍵字,例如學生的學號就具有唯一性;反之,像學生姓名、年齡這類的關鍵字,由於不具有唯一性,稱為次關鍵字。
如何進行查找?
不同的查找表,其使用的查找方法是不同的。例如每個人都有屬於自己的朋友圈,都有自己的電話簿,電話簿中數據的排序方式是多種多樣的,有的是按照姓名的首字母進行排序,這種情況在查找時,就可以根據被查找元素的首字母進行順序查找;有的是按照類別(親朋好友)進行排序。在查找時,就需要根據被查找元素本身的類別關鍵字進行排序。
具體的查找方法需要根據實際應用中具體情況而定。
順序查找算法(C++)
靜態查找表既可以使用順序表表示,也可以使用鏈表結構表示。雖然一個是數組、一個鏈表,但兩者在做查找操作時,基本上大同小異。
順序查找的實現
靜態查找表用順序存儲結構表示時,順序查找的查找過程為:從表中的最后一個數據元素開始,逐個同記錄的關鍵字做比較,如果匹配成功,則查找成功;反之,如果直到表中第一個關鍵字查找完也沒有成功匹配,則查找失敗。
#include<bits/stdc++.h>
using namespace std;
typedef struct {
int key;//查找表中數據的值
//按需求添加其他屬性
}ElemType;
typedef struct {
ElemType *elem;//存放查找表中數據元素的數組
int length;//記錄查找表中的數據總數量
}SSTable;
//創建查找表
void Creat(SSTable **st,int length) {
(*st) =(SSTable*) new SSTable;
(*st)->length = length;
(*st)->elem = (ElemType *) new ElemType[length + 1];
//根據查找表中數據元素的總長度,在存儲時,從數組下標為 1 的空間開始存儲數據
cout << "輸入表中元素:\n";
for (int i = 1; i <= length; ++i) {
cin >> ((*st)->elem[i].key);
}
}
//查找表查找的功能函數,其中key為關鍵字
int Search_seq(SSTable *st, int key) {
st->elem[0].key = key;//將關鍵字作為一個數據元素存放在查找表的第一個位置,監視哨作用
int i = st->length;
//從查找表的最后一個元素依次遍歷,直到下標為0
while (st->elem[i].key != key)
i--;
//如果 i=0,說明查找失敗;反之,返回的是含有關鍵字key的數據元素在查找表中的位置
return i;
}
int main() {
SSTable *st;
Creat(&st, 6);
getchar();
printf("請輸入查找數據的關鍵字:\n");
int key;
cin >> key;
int location = Search_seq(st, key);
if (location == 0) {
printf("查找失敗\n");
}
else {
printf("數據在查找表中的位置為:%d", location);
}
return 0;
}
輸入表中的數據元素:
1 2 3 4 5 6
請輸入查找數據的關鍵字:
2
數據在查找表中的位置為:2
使用監視哨對普通的順序表的遍歷算法做了改進,在數據量大的情況下,能夠有效提高算法的運行效率。
二分查找(折半查找)算法
折半查找,也稱二分查找,在某些情況下相比於順序查找,使用折半查找算法的效率更高。但是該算法的使用的前提是靜態查找表中的數據必須是有序的。
例如,在{5,21,13,19,37,75,56,64,88 ,80,92}這個查找表使用折半查找算法查找數據之前,需要首先對該表中的數據按照所查的關鍵字進行排序:{5,13,19,21,37,56,64,75,80,88,92}。
在折半查找之前對查找表按照所查的關鍵字進行排序的意思是:若查找表中存儲的數據元素含有多個關鍵字時,使用哪種關鍵字做折半查找,就需要提前以該關鍵字對所有數據進行排序。
#include<bits/stdc++.h>
using namespace std;
#define MAX_INT 20
//int a[MAX_INT];//存儲元素
int n;//數組大小
int BinarySearch(int a[], int value) {
int left = 0;
int right = n - 1;
while (left <= right) {
//最好改用位運算符而不是 /2
//https://www.cnblogs.com/Kanna/p/12371164.html (位運算詳解)
if (value > a[(left + right) / 2])
left = (right + left) / 2;
else if (value < a[(left + right) / 2])
right = (right + left) / 2;
else
{
return (left + right) / 2;
}
}
return -1;
}
int main() {
int a[] = { 1,2,3,4,5 };//sorted arrays
n = 5;
int ans = BinarySearch(a, 3);
if (ans == -1)cout << " Not find " << endl;
else cout << ans + 1 << endl;
return 0;
}
注意:在做查找的過程中,如果 left 和 right 的中間位置在計算時位於兩個關鍵字中間,即求得 mid 的位置不是整數,需要統一做取整操作。
通過比較折半查找的平均查找長度,同前面介紹的順序查找相對比,明顯折半查找的效率要高。但是折半查找算法只適用於有序表,同時僅限於查找表用順序存儲結構表示。
當查找表使用鏈式存儲結構表示時,折半查找算法無法有效地進行比較操作(排序和查找操作的實現都異常繁瑣)。
分塊查找(索引順序查找)算法
分塊查找,也叫索引順序查找,算法實現除了需要查找表本身之外,還需要根據查找表建立一個索引表。例如圖 1,給定一個查找表,其對應的索引表如圖所示:

圖中,查找表中共 18 個查找關鍵字,將其平均分為 3 個子表,對每個子表建立一個索引,索引中包含中兩部分內容:該子表部分中最大的關鍵字以及第一個關鍵字在總表中的位置,即該子表的起始位置。
建立的索引表要求按照關鍵字進行升序排序,查找表要么整體有序,要么分塊有序。
分塊有序指的是第二個子表中所有關鍵字都要大於第一個子表中的最大關鍵字,第三個子表的所有關鍵字都要大於第二個子表中的最大關鍵字,依次類推。
塊(子表)中各關鍵字的具體順序,根據各自可能會被查找到的概率而定。如果各關鍵字被查找到的概率是相等的,那么可以隨機存放;否則可按照被查找概率進行降序排序,以提高算法運行效率。
分塊查找的具體實現
所有前期准備工作完成后,開始在此基礎上進行分塊查找。分塊查找的過程分為兩步進行:
- 確定要查找的關鍵字可能存在的具體塊(子表);
- 在具體的塊中進行順序查找。
以圖中的查找表為例,假設要查找關鍵字 38 的具體位置。首先將 38 依次和索引表中各最大關鍵字進行比較,因為 22 < 38 < 48,所以可以確定 38 如果存在,肯定在第二個子表中。
由於索引表中顯示第二子表的起始位置在查找表的第 7 的位置上,所以從該位置開始進行順序查找,一直查找到該子表最后一個關鍵字(一般將查找表進行等分,具體子表個數根據實際情況而定)。結果在第 10 的位置上確定該關鍵字即為所找。
Tip:在第一步確定塊(子表)時,由於索引表中按照關鍵字有序,所有可以采用折半查找算法。而在第二步中,由於各子表中關鍵字沒有嚴格要求有序,所以只能采用順序查找的方式。
具體實現代碼:
#include<bits/stdc++.h>
using namespace std;
struct index { //定義塊的結構
int key;
int start;
} newIndex[3]; //定義結構體數組
int search(int key, int a[]);
int cmp(const void *a, const void* b) {
return (*(struct index*)a).key > (*(struct index*)b).key ? 1 : -1;
}
int main() {
int i, j = -1, k, key;
int a[] = { 33,42,44,38,24,48, 22,12,13,8,9,20, 60,58,74,49,86,53 };
//確認模塊的起始值和最大值
for (i = 0; i < 3; i++) {
newIndex[i].start = j + 1; //確定每個塊范圍的起始值
j += 6;
for (int k = newIndex[i].start; k <= j; k++) {
if (newIndex[i].key < a[k]) {
newIndex[i].key = a[k];
}
}
}
//對結構體按照 key 值進行排序
qsort(newIndex, 3, sizeof(newIndex[0]), cmp);
//輸入要查詢的數,並調用函數進行查找
printf("請輸入您想要查找的數:\n");
scanf("%d", &key);
k = search(key, a);
//輸出查找的結果
if (k > 0) {
printf("查找成功!您要找的數在數組中的位置是:%d\n", k + 1);
}
else {
printf("查找失敗!您要找的數不在數組中。\n");
}
return 0;
}
int search(int key, int a[]) {
int i, startValue;
i = 0;
while (i<3 && key>newIndex[i].key) { //確定在哪個塊中,遍歷每個塊,確定key在哪個塊中
i++;
}
if (i >= 3) { //大於分得的塊數,則返回0
return -1;
}
startValue = newIndex[i].start; //startValue等於塊范圍的起始值
while (startValue <= startValue + 5 && a[startValue] != key)
{
startValue++;
}
if (startValue > startValue + 5) { //如果大於塊范圍的結束值,則說明沒有要查找的數
return -1;
}
return startValue;
}
請輸入您想要查找的數:
22
查找成功!您要找的數在數組中的位置是:7
分塊查找的性能分析
分塊查找算法的運行效率受兩部分影響:
查找塊的操作和塊內查找的操作。查找塊的操作可以采用順序查找,也可以采用折半查找(更優);塊內查找的操作采用順序查找的方式。相比於折半查找,分塊查找時間效率上更低一些;相比於順序查找,由於在子表中進行,比較的子表個數會不同程度的減少,所有分塊查找算法會更優。
總體來說,分塊查找算法的效率介於順序查找和折半查找之間。
