《演算法》筆記 7 符號表 順序查詢 二分查詢

2022-06-05 17:09:09 字數 3632 閱讀 8519

無序鍊錶中的順序查詢

有序陣列中的二分查詢

現代計算機和網路使人們能夠訪問海量的資訊,而且各種計算裝置正在源源不斷地生成新的資訊,高效檢索這些資訊的能力就成了處理它們的重要前提。接下來學習幾種經典的查詢演算法。

符號表指的是一張用於儲存資訊的抽象的**,主要目的就是將乙個鍵和乙個值聯絡起來,可以將乙個鍵值對插入到符號表,也可以從符號表的所有鍵值對中按照鍵直接找到對應的值,符號表也被稱為字典。

api符號表最基本的操作是:插入、查詢,此外還包括幾種方便演算法實現的操作。要實現符號表,首先要定義其背後的資料結構,並指明建立並操作這種資料結構以進行插入、查詢所需的演算法。

符號表的api如下:

public class st
設計的符號表將會遵循以下規則:符號表的實現使用了泛型;每個鍵只能對應乙個值,如果向表中存入的鍵值對已經在表中存在,則用新的值覆蓋舊值,所以表中不存在重複的鍵;也不允許存入空或空值,只有在表中找不到鍵對應的值時,get方法會返回null。

有序符號表

符號表根據其中鍵是否有序分為有序符號表和無序符號表,有序符號表中基於鍵的有序性可以實現更多有用的操作。

key floor(key key)  //獲取小於等於key的最大鍵

key ceiling(key key) //獲取大於等於key的最小鍵

int rank(key key) //獲取小於key的鍵的數量

key select(int k) //獲取排名為k的鍵

key max() //獲取最大的鍵

key min() //獲取最小的鍵

獲取最大、最小鍵的操作使得有序符號表具有了與優先佇列類似的功能,不同的是優先佇列中可以存在重複的鍵但符號表不行。

rank和select方法可以用來檢驗乙個新的鍵是否插入到合適的位置。對於0到size()-1中的所有i都有i=rank(select(i)),且key=select(rank(key))。floor和ceiling類似於對實數的向下取整、向上取整操作。

成本模型

符號表中插入、查詢都需要將乙個值與符號表中的鍵進行比較,在學習符號表的實現時,會統計比較的次數來分析一種實現的成本,如果一種實現的比較次數很少,便考慮其訪問資料結構的次數。

可以使用鍊錶作為符號表的簡單實現,每個結點儲存乙個鍵值對。get()方法會遍歷鍊錶,將要查詢的鍵依次與鍊錶中的結點比較,匹配成功就返回結點的值,否則返回null;put()方法也會遍歷鍊錶,如果找到匹配的結點,就用要插入的值覆蓋匹配結點的值,否則就在鍊錶頭部新增乙個新的結點。

實現

public class sequentialsearchst

}public value get(key key)

}return null;

}public void put(key key, value value)

}first = new node(key, value, first);

n++;

}...

}

效能

對於乙個含有n個鍵值對的基於無序鍊錶的符號表來說:

未命中的查詢,需要n次比較,因為要遍歷並比較鍊錶中的所有鍵;

插入操作,如果待插入的元素沒在符號表中,也需要n次比較;

對於命中的查詢,最壞情況為n次比較,但平均情況下,命中的查詢不需要這麼多次比較。可以通過計算查詢表中每個鍵的總次數,將其除以n來估算一次命中查詢的平均比較次數,這種方法假設對符號表中每個鍵進行查詢的可能性都相同,也稱為隨機命中。雖然實際應用中,不可能做到完全隨機,但也基本吻合。

在隨機命中模式下,查詢的總次數=第乙個鍵的比較次數+第二個鍵的比較次數+...+第n個鍵的比較次數=1+2+...+n=n(n+1)/2;平均比較次數=(n+1)/2。相當於與一半的元素進行比較。

向空表中插入n個不同的鍵時,每次插入都需與已經插入的所有鍵比較,所以也需要1+2+..+n=n(n+1)/2次比較。增長數量級為平方級別。

基於有序陣列的符號表,這裡使用的資料結構是一對平行的陣列,乙個儲存鍵,乙個儲存值;也可以用乙個由鍵值對構成的資料來實現。

實現

public class binarysearchst, value> 

public value get(key key) else

}public void put(key key, value val)

for (int j = n; j > i; j--)

keys[i] = key;

vals[i] = val;

n++;

}public int rank(key key)

public int rankrecursion(key key, int lo, int hi) else if (cmp > 0) else

}public int rankiteration(key key, int lo, int hi) else if (cmp > 0) else

}return lo;

}public iterablekeys()

public iterablekeys(key lo, key hi)

}

這份實現的核心是rank方法,它返回表中小於給定鍵的鍵的數量。對於get方法,只要給定的鍵存在於表中,根據rank方法就可以知道去哪兒找到它;對於put方法,如果給定的鍵存在於表中,根據rank方法就可以知道去哪兒更新鍵對應的值,如果鍵不在表中,根據rank方法也可以知道應該將新的鍵值插入什麼位置。插入的時候,會先將所有更大的鍵向後移動一格來騰出位置。

由於使用了有序陣列,rank方法可以通過二分查詢快速地找到鍵的位置。在查詢時,先將被查詢的鍵和子陣列的中間鍵比較,如果被查詢的鍵小於中間鍵,就在左子陣列中繼續查詢,如果大於中間鍵,就在右子陣列中繼續查詢,否則中間鍵就是被命中的鍵。

rankrecursion和rankiteration分別用遞迴和迭代實現了這種演算法。put方法在插入鍵值對時,由於使用了rank方法拿到待插入鍵的排序位置,可以保證符號表一直是有序的。

效能在n個鍵的有序陣列中進行二分查詢時,先找到1個中間元素,然後在剩下的(n-1)/2個元素中繼續二分查詢,由此可得比較次數的關係式:

c(n)<=c((n-1)/2)+1,其中1為與中間元素的1次比較;

於是c(n)<=c(n/2)+1;

而且有c(0)=0,c(1)=1;

假設n的個數剛好為2的冪,即n=2^n, n=lgn;

則:c(2n)<=c(2(n-1))+1;

繼續迭代直到n=0,可得:c(2n)<=c(20)+n;

將n=2^n, n=lgn代入上式可得:

c(n)<=1+lgn。

即n的個數剛好為2的冪時,最多需要lgn+1次比較;

推廣到一般的情況,可知查詢的增長數量級為對數級別。

查詢操作的效能在對數級別,是非常快的,但插入操作的效能如何呢?

在最壞的情況下,插入的位置在陣列的最開頭,所以插入前需要將所有的元素向後移動一格,鍵、值各乙個陣列,共需要2n次數組訪問,插入前呼叫rank方法進行了lgn次比較,lgn相比2n較小可忽略,所以插入操作需要訪問陣列~2n次;

向乙個空符號表中插入n個元素,在最壞的情況下,每次都要插入陣列的開頭,共需2n(n-1)/2,約為~n^2次數組訪問,所以插入操作的增長數量級為線性的,構建乙個有序符號表則需要平方級別的時間,線性、平方級別的演算法無法用於解決大規模的問題。

符號表的順序查詢和二分查詢

符號表表示一張抽象的 我們將資訊儲存到其中,然後按照指定的鍵來搜尋並獲取這些資訊。鍵和值的具體意義取決於不同的應用。符號表中可能會儲存很多鍵和很多資訊,因此實現一張高效的符號表也是一項很有挑戰性的任務。符號表也稱為字典。符號表最主要的目的就是將乙個鍵和乙個值聯絡起來。用例能夠將乙個鍵值對插入符號表中...

二分查詢實現符號表

使用keys和values兩個陣列分別儲存鍵和值 實現的核心是rank 方法,它返回表中小於給定鍵的數量 有序陣列的二分查詢 public class arraybinarysearchst comparable value implements iorderedsymboltable public...

順序表查詢之二分查詢

二分查詢使用前提 1 線性表中的記錄必須是關鍵碼有序 一般由大到小 2 線性表必須採用順序儲存 3 有序儲存的順序表是靜態查詢表,即不需要頻繁的執行插入或者刪除操作 時間複雜度 o logn include include using namespace std int binary search ...