折半查找,也稱二分查找,在某些情況下相比於順序查找,使用折半查找算法的效率更高。但是該算法的使用的前提是靜態查找表中的數據必須是有序的。
例如,在
圖 1 折半查找的過程(a)
如上圖 1 所示,指針 low 和 high 分別指向查找表的第一個關鍵字和最后一個關鍵字,指針 mid 指向處於 low 和 high 指針中間位置的關鍵字。在查找的過程中每次都同 mid 指向的關鍵字進行比較,由於整個表中的數據是有序的,因此在比較之后就可以知道要查找的關鍵字的大致位置。
例如在查找關鍵字 21 時,首先同 56 作比較,由於
因此,再次遍歷時需要更新 high 指針和 mid 指針的位置,令 high 指針移動到 mid 指針的左側一個位置上,同時令 mid 重新指向 low 指針和 high 指針的中間位置。如圖 2 所示:
圖 2 折半查找的過程(b)
同樣,用 21 同 mid 指針指向的 19 作比較,
圖 3 折半查找的過程(3) 當第三次做判斷時,發現 mid 就是關鍵字 21 ,查找結束。
圖 4 折半查找對應的判定樹
在判定樹中可以看到,如果想在查找表中查找 21 的位置,只需要進行 3 次比較,依次和 56、19、21 進行比較,而比較的次數恰好是該關鍵字所在判定樹中的層次(關鍵字 21 在判定樹中的第 3 層)。
對於具有 n 個結點(查找表中含有 n 個關鍵字)的判定樹,它的層次數至多為:
同時,在查找表中各個關鍵字被查找概率相同的情況下,折半查找的平均查找長度為:
{5,21,13,19,37,75,56,64,88 ,80,92}
這個查找表使用折半查找算法查找數據之前,需要首先對該表中的數據按照所查的關鍵字進行排序:
{5,13,19,21,37,56,64,75,80,88,92}
。
在折半查找之前對查找表按照所查的關鍵字進行排序的意思是:若查找表中存儲的數據元素含有多個關鍵字時,使用哪種關鍵字做折半查找,就需要提前以該關鍵字對所有數據進行排序。
折半查找算法
對靜態查找表{5,13,19,21,37,56,64,75,80,88,92}
采用折半查找算法查找關鍵字為 21 的過程為:

圖 1 折半查找的過程(a)
如上圖 1 所示,指針 low 和 high 分別指向查找表的第一個關鍵字和最后一個關鍵字,指針 mid 指向處於 low 和 high 指針中間位置的關鍵字。在查找的過程中每次都同 mid 指向的關鍵字進行比較,由於整個表中的數據是有序的,因此在比較之后就可以知道要查找的關鍵字的大致位置。
例如在查找關鍵字 21 時,首先同 56 作比較,由於
21 < 56
,而且這個查找表是按照升序進行排序的,所以可以判定如果靜態查找表中有 21 這個關鍵字,就一定存在於 low 和 mid 指向的區域中間。
因此,再次遍歷時需要更新 high 指針和 mid 指針的位置,令 high 指針移動到 mid 指針的左側一個位置上,同時令 mid 重新指向 low 指針和 high 指針的中間位置。如圖 2 所示:

圖 2 折半查找的過程(b)
19 < 21
,所以可以判定 21 如果存在,肯定處於 mid 和 high 指向的區域中。所以令 low 指向 mid 右側一個位置上,同時更新 mid 的位置。

圖 3 折半查找的過程(3)
折半查找的實現代碼:注意:在做查找的過程中,如果 low 指針和 high 指針的中間位置在計算時位於兩個關鍵字中間,即求得 mid 的位置不是整數,需要統一做取整操作。
#include <stdio.h> #include <stdlib.h> #define keyType int
typedef struct
{ keyType key; // 查找表中每個數據元素的值 // 如果需要,還可以添加其他屬性 }ElemType; typedef struct
{ ElemType *elem; // 存放查找表中數據元素的數組 int length; // 記錄查找表中數據的總數量 }SSTable;
// 創建查找表 void Create(SSTable **st, int length)
{ (*st) = (SSTable*)malloc(sizeof(SSTable)); (*st)->length = length; printf("輸入表中的數據元素:\n"); // 根據查找表中數據元素的總長度,在存儲時,從數組下標為 1 的空間開始存儲數據 for (int i=1; i<=length; i++)
{ scanf("%d", &((*st)->elem[i].key)); } }
//折半查找算法 int Search_Bin(SSTable *ST, keyType key)
{ int low = 1; //初始狀態 low 指針指向第一個關鍵字 int high = ST->length; //high 指向最后一個關鍵字 int mid; while (low <= high)
{ mid = (low+high) / 2; // int 本身為整形,所以,mid 每次為取整的整數 if (ST->elem[mid].key == key) // 如果 mid 指向的同要查找的相等,返回 mid 所指向的位置 { return mid; }
else if(ST->elem[mid].key > key) // 如果mid指向的關鍵字較大,則更新 high 指針的位置 { high = mid-1; } // 反之,則更新 low 指針的位置 else
{ low = mid + 1; } }
return 0; } int main(int argc, const char * argv[])
{ SSTable *st; Create(&st, 11); getchar(); printf("請輸入查找數據的關鍵字:\n"); int key; scanf("%d", &key); int location = Search_Bin(st, key); //如果返回值為 0,則證明查找表中未查到 key 值, if (location == 0)
{ printf("查找表中無該元素"); }
else
{ printf("數據在查找表中的位置為:%d", location); } return 0; }
以圖 1 的查找表為例,運行結果為: 輸入表中的數據元素: 5 13 19 21 37 56 64 75 80 88 92 請輸入查找數據的關鍵字: 21 數據在查找表中的位置為:4
折半查找的性能分析
折半查找的運行過程可以用二叉樹來描述,這棵樹通常稱為“判定樹”。例如圖 1 中的靜態查找表中做折半查找的過程,對應的判定樹如圖 4:
圖 4 折半查找對應的判定樹
在判定樹中可以看到,如果想在查找表中查找 21 的位置,只需要進行 3 次比較,依次和 56、19、21 進行比較,而比較的次數恰好是該關鍵字所在判定樹中的層次(關鍵字 21 在判定樹中的第 3 層)。
對於具有 n 個結點(查找表中含有 n 個關鍵字)的判定樹,它的層次數至多為:
log2n + 1
(如果結果不是整數,則做取整操作,例如:
log211 +1 = 3 + 1 = 4
)。
同時,在查找表中各個關鍵字被查找概率相同的情況下,折半查找的平均查找長度為:
ASL = log2(n+1) – 1
。
總結
通過比較折半查找的平均查找長度,同前面介紹的順序查找相對比,明顯折半查找的效率要高。但是折半查找算法只適用於有序表,同時僅限於查找表用順序存儲結構表示。
當查找表使用鏈式存儲結構表示時,折半查找算法無法有效地進行比較操作(排序和查找操作的實現都異常繁瑣)。