最近開始學習王爭老師的《資料結構與演算法之美》,通過總結再加上自己的思考的形式記錄這門課程,文章主要作為學習歷程的記錄。
課前問題:假設有1000萬個整數資料,每個資料佔8個位元組,如何設計資料結構與演算法,快速判斷某個整數是否出現在這1000萬資料中,同時又希望這個功能記憶體不要超過100m?
這個問題就引入了二分查詢,舉個例子來理解二分查詢:假設乙個陣列為0-99,要查詢元素23在陣列中的位置
次數猜測範圍
中間數對比大小
10-99
4949>23
20-48
2424>23
30-23
1111<23
412-23
1717<23
518-23
2020<23
621-23
2222<23723
√利用二分思想,每次都與區間中間資料比對大小,縮小查詢區間的範圍,二分查詢針對的是乙個有序的資料集合,查詢思想有點類似分治思想,每次都通過跟區間的中間元素對比,將待查詢的區間縮小為原來的一半,直到找到要查詢的元素,或者區間被縮小為0。
二分查詢查詢效率非常高。假設資料大小為n,每次查詢後資料都會縮小為原來的一半,也就是會除以2,最壞的情況下,直到查詢區間被縮小為空才停止。
被查詢區間的大小變化:最簡單的二分查詢: n
nn , n/2
n/2n/
2 , n/4
n/4n/
4 , n/8
n/8n/
8 , … , n/2
kn/2^k
n/2k
當n /2
kn/2^k
n/2k
=1時,k的值就是縮小的次數。
時間複雜度為o(k) = o(log
2n
log_2
log2n
),故時間複雜度為o(logn)
最簡單的二分查詢就是有序陣列中不存在重複元素,**:
def binary_search(l,n):
low = 0
high = len(l)-1
while(low<=high):
mid = low+((high-low)>>1)
if n == l[mid]:
return mid
elif nl[mid]:
low = mid+1
下面有幾點需要注意:
1.迴圈退出條件:實際上,二分查詢除了可以用迴圈,還可以採用遞迴實現。 注意是low<=high,而不是low2.mid的取值:
mid = (low+high)/2的寫法是有問題的,low+high可能會發生溢位,改進的方法是將mid的計算方式寫出low+(high-low)/2.需要進一步優化的話,可將除以2的操作轉化為位運算,即low+((high-low)>>1),速度要快得多。
3.low和high的更新
low = mid+1, high = mid -1,如果直接寫成low = mid或者high = mid就會發生死迴圈。
def binary_search(l,n,low,high):
while(high>=low):
mid = ((high-low)>>1)+low
if l[mid]==n:
return mid
elif l[mid]>n:
return binary_search(l,n,low,mid-1)
elif l[mid]二分查詢應用場景的侷限性
一、二分查詢依賴的是順序表結構,即陣列
二分查詢不適合用於鍊錶,主要原因在於二分查詢演算法需要按照下標隨機訪問元素。陣列按照下標隨機訪問資料的時間複雜度為o(1),而鍊錶是o(n)。因此資料使用鍊錶儲存的話,二分查詢時間複雜度就會很高。
二、二分查詢針對的是有序陣列
二分查詢要求資料必須是有序的。排序的時間複雜度最低是o(nlogn)。因此,針對的是一組靜態的資料,沒有頻繁地插入、刪除,我們可以進行一次排序,多次二次查詢,這樣排序的成本可以被均攤,二次查詢的邊際成本就會比較低。
三、資料量太小不適合採用二分查詢
資料量很小,直接進行順序遍歷。只有資料量大時,二分查詢的優勢才比較明顯。但若是資料之間的比較操作非常耗時,不管資料量大小,都更推薦使用二分查詢。
四、資料量太大也不適合二分查詢
二分查詢的底層需要依賴陣列,而陣列為了支援隨機訪問的特性,要求記憶體空間連續,對記憶體的要求比較苛刻。因此太大的資料用陣列儲存就比較吃力,不能用二分查詢。
補充乙個思考題,程式設計實現「求乙個數的平方根,保留6位有效小數」
def sqrt(t):
low = 0
high = t
mid = t/2
while(abs(t-mid**2)>0.00001):
if mid**2>t:
high = mid
elif mid**2一、查詢第乙個值等於給定值的元素
這個問題的難度在於當查詢到對應的值時,不一定是第乙個值,需要進行判斷。若mid=0,則元素已經是第乙個值了,即我們查詢的。若mid不等於0,但s[mid]的前乙個元素s[mid-1]不等於t,那麼s[mid]就是我們要找的第乙個等於給定值的元素。如果發生s[mid-1]也等於t,則令high = mid - 1,因為要找的元素肯定在[low,mid-1]中。
def bin_find(s,t): #查詢第乙個等於給定值的元素
low = 0
high = len(l)-1
while(high>=low):
mid = low + ((high-low)>>1)
if s[mid]t:
high = mid-1
elif s[mid]==t:
if mid==0 or s[mid-1]!=t:
return mid
else:
high = mid-1
二、查詢最後乙個等於給定值的元素
與變體一類似
def bin_find(s,t): #查詢最後乙個等於給定值的元素
low = 0
high = len(l)-1
while(high>=low):
mid = low + ((high-low)>>1)
if s[mid]t:
high = mid-1
elif s[mid]==t:
if mid==0 or s[mid+1]!=t:
return mid
else:
low = mid+1
三、查詢第乙個大於等於給定值的元素
難度在於s[mid]>=t。我們要首先看一下這個s[mid]是不是我們要找的第乙個大於等於給定值的元素。如果s[mid]前面無元素或前乙個元素小於要查詢的t,那s[mid]即為要找的元素。如果s[mid-1]也大於等於要找的t,說明要查詢的元素在[low,mid-1]之間,所以要將high更新為mid-1.
def bin_find(s,t): #查詢第乙個大於等於給定值的元素
low = 0
high = len(l)-1
while(high>=low):
mid = low + ((high-low)>>1)
if s[mid]=t:
if mid==0 or s[mid-1]四、查詢最後乙個小於等於給定值的元素
與變體三類似
def bin_find(s,t): #查詢最後乙個小於等於給定值的元素
low = 0
high = len(l)-1
while(high>=low):
mid = low + ((high-low)>>1)
if s[mid]<=t:
if mid == 0 or s[mid+1]>t:
return mid
else:
low = mid+1
elif s[mid]>t:
high = mid-1
python資料結構 二分查詢
二分查詢 有序列表對於我們的比較是很有用的。在順序查詢中,當我們與第乙個項進行比較時,如果第乙個項不是我們要查詢的,則最多還有 n 1 個專案。二分查詢從中間項開始,而不是按順序查詢列表。如果該項是我們正在尋找的項,我們就完成了查詢。如果它不是,我們可以使用列表的有序性質來消除剩餘項的一半。如果我們...
資料結構 二分查詢
二分查詢演算法也稱為折半搜尋 二分搜尋,是一種在有序陣列中查詢某一特定元素的搜尋演算法。搜素過程從陣列的中間元素開始,如果中間元素正好是要查詢的元素,則搜素過程結束 如果某一特定元素大於或者小於中間元素,則在陣列大於或小於中間元素的那一半中查詢,而且跟開始一樣從中間元素開始比較。如果在某一步驟陣列為...
資料結構 二分查詢
總共有n個元素,漸漸跟下去就是n,n 2,n 4,n 2 k 接下來操作元素的剩餘個數 其中k就是迴圈的次數。由於你n 2 k取整後 1,即令n 2 k 1,可得k log2n,是以2為底,n的對數 所以時間複雜度可以表示o o logn public class binarysearch else...