查找(順序表&有序表)


【1】查找概論

查找表是由同一類型是數據元素(或記錄)構成的集合。

關鍵字是數據元素中某個數據項的值,又稱為鍵值。

若此關鍵字可以唯一標識一個記錄,則稱此關鍵字為主關鍵字。

查找就是根據給定的某個值,在查找表中確定一個其關鍵字等於給定值的數據元素(或記錄)。

查找分為兩類:靜態查找表和動態查找表。

靜態查找表:只作查找操作的查找表。主要操作:

(1)查詢某個“特定的”數據元素是否在查找表中。

(2)檢索某個“特定的”數據元素和各種屬性。

動態查找表:在查找過程中同時插入查找表中不存在的數據元素,或者從查找表中刪除已經已經存在的某個數據元素。 主要操作:

(1)查找時插入數據元素。

(2)查找時刪除數據元素。

好吧!兩者的區別: 靜態查找表只負責查找任務,返回查找結果。

而動態查找表不僅僅負責查找,而且當它發現查找不存在時會在表中插入元素(那也就意味着第二次肯定可以查找成功)

【2】順序表查找

順序表查找又稱為線性查找,是最基本的查找技術。 它的查找思路是:

逐個遍歷記錄,用記錄的關鍵字和給定的值比較:

若相等,則查找成功,找到所查記錄; 反之,則查找不成功。

順序表查找算法代碼如下:

對於這種查找算法,查找成功最好就是第一個位置找到,時間復雜度為O(1)。

最壞情況是最后一個位置才找到,需要n次比較,時間復雜度為O(n) 顯然,n越大,效率越低下。

【3】有序表查找

所謂有序表,是指線性表的數據有序排列。

(1)折半查找

關於這個算法不做贅述,代碼如下:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 // 折半查找算法(二分查找) 
 5 int Binary_Search(int* a,int n,int key)
 6 {
 7     int low = 1, high = n, mid = 0;  // 初始化
 8     while (low <= high)    // 注意理解這里還有等於條件
 9     {
10         mid = (low + high)/2; // 折半
11         if (key < a[mid])
12             high = mid -1;    // 最高小標調整到中位小一位
13         else if (key > a[mid])
14             low = mid + 1;    // 最低下標調整到中位大一位
15         else
16             return mid;         // 相等說明即是
17     }
18     return 0;
19 }
20 
21 void  main ()
22 {
23     int a[11] = {0,9,23,45,65,88,90,96,100,124,210};
24     int n = Binary_Search(a,10, 9);
25     if (n != 0)
26         cout << "Yes:" << n << endl;
27     else
28         cout << "No:" << endl;
29 }
View Code

折半查找算法的時間復雜度為O(logn)。

(2)插值查找

考慮一個問題:為什么是折半?而不是折四分之一或者更多呢? 好吧,且看分解:

(3)斐波那契查找

斐波那契查找利用了黃金分割原理來實現。 如何利用斐波那契數列作為分割呢?

為了理清這個查找算法,首先需要一個斐波那契數列,如下圖所示:

查找算法如下描述:

注意閱讀以下詳解之前,請先編譯並運行第四部分的實例代碼,結合代碼再理解算法。

首先要明確一點:

如果一個有序表的元素個數為n,並且n正好是某個斐波那契數-1,即n == F[k]-1時,才能用斐波那契查找法。

1. 如果有序表的元素個數n不等於某個斐波那契數-1,即n != F[k]-1,如何處理呢?

 這時必須要將有序表的元素個數擴展到比n大的第一個斐波那契數-1的個數才符合算法的查找條件。

 通俗點講,也就是為了使用斐波那契查找法,那么要求所查找順序表的元素個數n必須滿足n == F[k]-1這樣的條件才可以。

 因為查找表為從小到大的順序表,所以如果數據元素個數不滿足要求,只有在表末用順序表的最大值補滿。

 代碼中第9-10行的作用恰是如此。

2. 對於二分查找,分割點是從mid= (low+high)/2開始。

 而對於斐波那契查找,分割是從mid = low + F[k-1] - 1開始的。 為什么如此計算?

 用實例驗證,比如本例中: 第一次進入查找循環時,數組元素個數准確說應該是12(包括隨后補滿的元素)

 而黃金分割點比例為0.618,那么12*0.618=7.416,此值對應12個元素應該為a[8]

 觀察程序運行第一次mid=1+F[7-1]-1=8,正是此原理所體現。

 key=59,a[8]=73,顯然key<a[8],可知low=1,high=7,k=7-1=6

 注意此條件意思即為7個數據元素,正好滿足F[6]-1=7的再次查找客觀要求

 而同理,黃金分割點比例為0.618,那么7*0.618=4.326,此值對應7個元素應該為a[5]

 再看第二次進入循環mid=1+F[6-1]-1=5,正是此原理所體現。

 key=59,a[5]=47,顯然key>a[5],可知low=6,high=7,k=6-2=4

 注意此條件意思即為2個數據元素,正好滿足F[4]-1=2的再次查找客觀要求

 而同理黃金分割點比例為0.618,那么2*0.618=1.236,此值對應2個元素中的第二個即為a[7]

 key=59,a[7]=62,顯然key<a[7],可知low=6,high=6,k=4-1=3

 同理mid=6+F[3-1]-1=6。此時a[6]=59=key。 即查找成功。

3. 注意緊接着下面一句代碼可以改寫為:

 return  (mid <= n) ? mid : n;

 當然這樣寫也沒有功能錯誤,但是細細琢磨還是有邏輯問題:

 mid == n時,返回為n; mid > n時返回也是n。

 那么到底n屬於那種情況下的返回值呢?是否有違背if的本質!

 竊以為寫成if(mid < n)會合理些。

 另外,許多資料對於這步判斷描述如下:

 return  (mid <= high) ? mid : n;

 其實分析至此,我認為這種寫法從代碼邏輯而言更為合理。

4. 通過上面知道:數組a現在的元素個數為F[k]-1個,即數組長為F[k]-1。

 mid把數組分成了左右兩部分,左邊的長度為:F[k-1]-1

 那么右邊的長度就為(數組長-左邊的長度-1): (F[k]-1)-(F[k-1]-1)= F[k]-F[k-1]-1 = F[k-2] - 1

5. 斐波那契查找的核心是:

a: 當key == a[mid]時,查找成功;

b: 當key<a[mid]時,新的查找范圍是第low個到第mid-1個,此時范圍個數為F[k-1] - 1個,

 即數組左邊的長度,所以要在[low, F[k - 1] - 1]范圍內查找;

c: 當key>a[mid]時,新的查找范圍是第mid+1個到第high個,此時范圍個數為F[k-2] - 1個,

 即數組右邊的長度,所以要在[F[k - 2] - 1]范圍內查找。

關於斐波那契查找, 如果要查找的記錄在右側,則左側的數據都不用再判斷了,不斷反復進行下去。

對處於中間的大部分數據,其工作效率要高一些。

所以盡管斐波那契查找的時間復雜度也為O(logn),但就平均性能來說,斐波那契查找要優於折半查找。

可惜如果是最壞的情況,比如這里key=1,那么始終都處於左側在查找,則查找效率低於折半查找。   

還有關鍵一點:折半查找是進行加法與除法運算的(mid=(low+high)/2)

插值查找則進行更復雜的四則運算(mid = low + (high - low) * ((key - a[low]) / (a[high] - a[low])))

而斐波那契查找只進行最簡單的加減法運算(mid = low + F[k-1]-1)

在海量數據的查找過程中,這種細微的差別可能會影響最終的效率。

【4】斐波那契算法代碼實現

實例算法代碼如下:

 1 #include <iostream>
 2 #include <assert.h>
 3 using namespace std;
 4 
 5 #define  MAXSIZE  11
 6 
 7 // 斐波那契非遞歸
 8 void Fibonacci(int *f)
 9 {
10     f[0] = 0;
11     f[1] = 1;
12      
13     for (int i = 2; i < MAXSIZE; ++i)
14     {
15         f[i] = f[i-1] + f[i-2];
16     }
17 }
18 // 斐波那契數列
19 /*---------------------------------------------------------------------------------
20   |  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |  10  |  11  |  12  |
21   ----------------------------------------------------------------------------------
22   |     0  |  1  |  1  |  2  |  3  |  5  |  8  |  13 |  21 |  34 |  55  |  89  |  144 |
23  -----------------------------------------------------------------------------------*/
24 // 斐波那契數列查找
25 int Fibonacci_Search(int *a, int n, int key)
26 {
27     int low = 1;  // 定義最低下標為記錄首位
28     int high = n; // 定義最高下標為記錄末位(一般輸入的參數n必須是數組的個數減一)
29 
30     int F[MAXSIZE];
31     Fibonacci(F); // 確定斐波那契數列
32 
33     int k = 0, mid = 0;
34     // 查找n在斐波那契數列中的位置,為什么是F[k]-1,而不是F[k]?
35     while (n > F[k]-1)
36     {
37         k++;
38     }
39     // 將不滿的數值補全
40     for (int i = n; i < F[k]-1; ++i)
41     {
42         a[i] = a[high];
43     }
44     // 查找過程
45     while (low <= high)
46     {
47         mid = low + F[k-1] - 1; // 為什么是當前分割的下標?
48         if (key < a[mid])  // 查找記錄小於當前分割記錄
49         {
50             high = mid - 1;
51             k = k - 1;     // 注意:思考這里為什么減一位?
52         }
53         else if (key > a[mid]) // 查找記錄大於當前分割記錄
54         {
55             low = mid + 1;
56             k = k - 2;  // 注意:思考這里為什么減兩位?
57         }
58         else
59         {
60             return (mid <= high) ? mid : n;  // 若相等則說明mid即為查找到的位置; 若mid > n 說明是補全數值,返回n
61         }
62     }
63     return -1;
64 }
65 void main()
66 {
67     int a[MAXSIZE] = {0,1,16,24,35,47,59,62,73,88,99};  
68     int k = 0;  
69     cout << "請輸入要查找的數字:" << endl;  
70     cin >> k;
71     int pos = Fibonacci_Search(a, MAXSIZE-1, k);  
72     if (pos != -1)  
73         cout << "在數組的第"<< pos+1 <<"個位置找到元素:" << k; 
74     else  
75         cout << "未在數組中找到元素:" << k; 
76 }
View Code

若結合以上相關分析深入理解代碼。

 

Good  Good  Study, Day   Day  Up.

順序  選擇  循環  總結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM