一.查找/搜索html
- 咱們如今把注意力轉向計算中常常出現的一些問題,即搜索或查找的問題。搜索是在元素集合中查找特定元素的算法過程。搜索一般對於元素是否存在返回 True 或 False。有時它可能返回元素被找到的地方。咱們在這裏將僅關注成員是否存在這個問題。算法
- 在 Python 中,有一個很是簡單的方法來詢問一個元素是否在一個元素列表中。咱們使用 in 運算符。數據結構
>>> 15 in [3,5,2,4,1] False >>> 3 in [3,5,2,4,1] True >>>
- 這很容易寫,一個底層的操做替咱們完成這個工做。事實證實,有不少不一樣的方法來搜索。咱們在這裏感興趣的是這些算法如何工做以及它們如何相互比較。函數
二.順序查找spa
- 當數據存儲在諸如列表的集合中時,咱們說這些數據具備線性或順序關係。 每一個數據元素都存儲在相對於其餘數據元素的位置。 在 Python 列表中,這些相對位置是單個元素的索引值。因爲這些索引值是有序的,咱們能夠按順序訪問它們。 這個過程產實現的搜索即爲順序查找
。設計
- 順序查找原理剖析:從列表中的第一個元素開始,咱們按照基本的順序排序,簡單地從一個元素移動到另外一個元素,直到找到咱們正在尋找的元素或遍歷完整個列表。若是咱們遍歷完整個列表,則說明正在搜索的元素不存在。code
- 代碼實現:該函數須要一個列表和咱們正在尋找的元素做爲參數,並返回一個是否存在的布爾值。found
布爾變量初始化爲 False,若是咱們發現列表中的元素,則賦值爲 True。htm
def sequentialSearch(alist, item): pos = 0 found = False while pos < len(alist) and not found: if alist[pos] == item: found = True else: pos = pos+1 return found testlist = [1, 2, 32, 8, 17, 19, 42, 13, 0] print(sequentialSearch(testlist, 3)) print(sequentialSearch(testlist, 13))
- 順序查找分析:爲了分析搜索算法,咱們能夠分析一下上述案例中搜索算法的時間複雜度,即統計爲了找到搜索目標耗費的運算步驟。實際上有三種不一樣的狀況可能發生。在最好的狀況下,咱們在列表的開頭找到所需的項,只須要一個比較。在最壞的狀況下,咱們直到最後的比較才找到項,第 n 個比較。平均狀況怎麼樣?平均來講,咱們會在列表的一半找到該項; 也就是說,咱們將比較 n/2 項。然而,回想一下,當 n 變大時,係數,不管它們是什麼,在咱們的近似中變得不重要,所以順序查找的複雜度是 O(n)
blog
- 有序列表:以前咱們列表中的元素是隨機放置的,所以在元素之間沒有相對順序。若是元素以某種方式排序,順序查找會發生什麼?咱們可以在搜索技術中取得更好的效率嗎?
排序
- 設計:假設元素的列表按升序排列。若是咱們正在尋找的元素存在此列表中,則目標元素在列表的 n 個位置中存在的機率是相同。咱們仍然會有相同數量的比較來找到該元素。然而,若是該元素不存在,則有一些優勢。下圖展現了這個過程,在列表中尋找元素 50。注意,元素仍然按順序進行比較直到 54,由於列表是有序的。在這種狀況下,算法沒必要繼續查看全部項。它能夠當即中止。
def orderedSequentialSearch(alist, item): pos = 0 found = False stop = False while pos < len(alist) and not found and not stop: if alist[pos] == item: found = True else: if alist[pos] > item: stop = True else: pos = pos+1 return found testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42,] print(orderedSequentialSearch(testlist, 3)) print(orderedSequentialSearch(testlist, 13))
該排序模式在最好的狀況下,咱們經過只查看一項會發現該項不在列表中。 平均來講,咱們將只瞭解 n/2 項就知道。然而,這種複雜度仍然是 O(n)。 可是在咱們沒有找到目標元素的狀況下,才經過對列表排序來改進順序查找。
三. 二分查找:
- 有序列表對於咱們的實現搜索是頗有用的。在順序查找中,當咱們與第一個元素進行比較時,若是第一個元素不是咱們要查找的,則最多還有 n-1
個元素須要進行比較。 二分查找則是從中間元素開始,而不是按順序查找列表。 若是該元素是咱們正在尋找的元素,咱們就完成了查找。 若是它不是,咱們可使用列表的有序性質來消除剩餘元素的一半。若是咱們正在查找的元素大於中間元素,就能夠消除中間元素以及比中間元素小的一半元素。若是該元素在列表中,確定在大的那半部分。而後咱們能夠用大的半部分重複該過程,繼續從中間元素開始,將其與咱們正在尋找的內容進行比較。下圖展現了該算法如何快速找到值 54 。
first = 0 last = len(alist)-1 found = False while first<=last and not found: midpoint = (first + last)//2 if alist[midpoint] == item: found = True else: if item < alist[midpoint]: last = midpoint-1 else: first = midpoint+1 return found testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42,] print(binarySearch(testlist, 3)) print(binarySearch(testlist, 13))
- 二分查找分析:爲了分析二分查找算法,咱們須要記住,每一個比較消除了大約一半的剩餘元素。該算法檢查整個列表的最大比較數是多少?若是咱們從 n 項開始,大約 n/2 項將在第一次比較後留下。第二次比較後,會有約 n/4。 而後 n/8,n/16,等等。 咱們能夠拆分列表多少次?
當咱們切分列表足夠屢次時,咱們最終獲得只有一個元素的列表。 要麼是咱們正在尋找的元素,要麼不是。達到這一點所需的比較數是 i,當 n / i^2=1時。 求解 i 得出 i = logn。 最大比較數相對於列表中的項是對數的。 所以,二分查找是 O(log n)。
Hash 查找
。None
。下圖展現了大小 m = 11 的哈希表。換句話說,在表中有 m 個槽,命名爲 0 到 10。54,26,93,17,77
和31
的集合,hash 函數將接收集合中的任何元素,並在槽名範圍內(0和 m-1之間)返回一個整數。λ=項數/表大小
, 在這個例子中,λ = 6/11
。- 結論:當咱們要搜索一個元素時,咱們只需使用哈希函數來計算該元素的槽名稱,而後檢查哈希表以查看它是否存在。該搜索操做是 O(1)。
- 注意:只有每一個元素映射到哈希表中的位置是惟一的,這種技術纔會起做用。 例如,元素77是咱們集合中的某一個元素,則它的哈希值爲
0(77%11 == 0)
。 那麼若是集合中還有一個元素是44,則44的hash值也是 0,咱們會有一個問題。根據hash函數,兩個或更多元素將須要在同一槽中。這種現象被稱爲碰撞(它也能夠被稱爲「衝突」)。顯然,衝突使散列技術產生了問題。咱們將在後面詳細討論。- 其餘計算hash值的方法:
- 分組求和法:若是咱們的元素是電話號碼
436-555-4601
,咱們將取出數字,並將它們分紅2位數(43,65,55,46,01)
。43 + 65 + 55 + 46 + 01
,咱們獲得 210。咱們假設哈希表有 11 個槽,那麼咱們須要除以 11 。在這種狀況下,210%11
爲 1,所以電話號碼436-555-4601
放置到槽 1 。- 平方取中法:咱們首先對該元素進行平方,而後提取一部分數字結果。例如,若是元素是 44,咱們將首先計算
44^2 = 1,936
。經過提取中間兩個數字93
,咱們獲得93%11=5,所以元素44放置到槽5.
- 注意:還能夠思考一些其餘方法來計算集合中元素的哈希值。重要的是要記住,哈希函數必須是高效的,以便它不會成爲存儲和搜索過程的主要部分。若是哈希函數太複雜,則計算槽名稱的程序要比以前所述的簡單地進行基本的順序或二分搜索更耗時。 這將打破哈希的目的。
- 衝突解決:若是有兩個元素經過調用hash函數返回兩個一樣的槽名,咱們就必須有一種方法可使得這兩個元素能夠散落在hash表的不一樣槽中!
- 解決方案:解決衝突的一種方法是查找哈希表,嘗試查找到另外一個空槽以保存致使衝突的元素。一個簡單的方法是從原始哈希值位置開始,而後以順序方式移動槽,直到遇到第一個空槽。這種衝突解決過程被稱爲開放尋址,由於它試圖在哈希表中找到下一個空槽或地址。經過系統的依次訪問每一個槽。當咱們嘗試將
44
放入槽 0 時,發生衝突。在線性探測下,咱們逐個順序觀察,直到找到位置。在這種狀況下,咱們找到槽 1。再次,55
應該在槽 0 中,可是必須放置在槽 2 中,由於它是下一個開放位置。值 20 散列到槽 9 。因爲槽 9 已滿,咱們進行線性探測。咱們訪問槽10,0,1
和2
,最後在位置 3 找到一個空槽。一旦咱們使用開放尋址創建了哈希表,咱們就必須使用相同的方法來搜索項。假設咱們想查找項
93
。當咱們計算哈希值時,咱們獲得5
。查看槽 5 獲得93
,返回 True。若是咱們正在尋找20
, 如今哈希值爲9
,而槽9
當前項爲31
。咱們不能簡單地返回 False,由於咱們知道可能存在衝突。咱們如今被迫作一個順序搜索,從位置10
開始尋找,直到咱們找到項20
或咱們找到一個空槽。- 代碼實現
- 實現 map 抽象數據類型:最有用的 Python 集合之一是字典。回想一下,字典是一種關聯數據類型,你能夠在其中存儲鍵-值對。該鍵用於查找關聯的值。咱們常常將這個想法稱爲
map
。map 抽象數據類型定義以下:del map[key]
形式的語句從 map 中刪除鍵值對。key in map
語句,若是給定的鍵在 map 中,不然爲False。- 咱們使用兩個列表來建立一個實現 Map 抽象數據類型的HashTable 類。一個名爲
slots
的列表將保存鍵項,一個稱data
的並行列表將保存數據值。當咱們查找一個鍵時,data
列表中的相應位置將保存相關的數據值。咱們將使用前面提出的想法將鍵列表視爲哈希表。注意,哈希表的初始大小已經被選擇爲 11。儘管這是任意的,可是重要的是,大小是質數,使得衝突解決算法能夠儘量高效。- hash 函數實現簡單的餘數方法。衝突解決技術是
加1
rehash 函數的線性探測。 put 函數假定最終將有一個空槽,除非 key 已經存在於self.slots
中。 它計算原始哈希值,若是該槽不爲空,則迭代 rehash 函數,直到出現空槽。若是非空槽已經包含 key,則舊數據值將替換爲新數據值。- 一樣,get 函數從計算初始哈希值開始。若是值不在初始槽中,則 rehash 用於定位下一個可能的位置。注意,第 15 行保證搜索將經過檢查以確保咱們沒有返回到初始槽來終止。若是發生這種狀況,咱們已用盡全部可能的槽,而且項不存在。HashTable 類提供了附加的字典功能。咱們重載
__getitem__
和__setitem__
方法以容許使用[]
訪問。 這意味着一旦建立了HashTable,索引操做符將可用。- 下面展現了 HashTable 類的操做。首先,咱們將建立一個哈希表並存儲一些帶有整數鍵和字符串數據值的項。
- 接下來,咱們將訪問和修改哈希表中的一些項。注意,正替換鍵 20 的值。