演算法入門,其實可以像讀小說一樣有趣

GitChat 精品課發表於2019-04-08

640?wx_fmt=jpeg


本文來源 | 《演算法圖解:像小說一樣有趣的演算法入門書》

作者 | Aditya Bhargava

責編 | 林瑟


我一個非程式設計師,買了這本《演算法圖解》的書在看,剛拆快遞後翻了一會,被我老闆給看到了,被她強行借走去翻了,說很有意思,翻了幾天才還我。。。如果你是下面中任何一種型別的同學,請繼續往下看:


  • 業餘程式設計師

  • 程式設計培訓班學員

  • 需要重溫演算法的計算機專業畢業生

  • 對程式設計感興趣的物理或數學等專業畢業生


演算法是一組完成任務的指令。任何程式碼片段都可視為演算法,但這裡我們只介紹比較有趣的部分,比如,二分查詢。


假設要在電話簿中找一個名字以 K 打頭的人,現在誰還用電話簿!可以從頭開始翻頁,直到進入以 K 打頭的部分。但你很可能不這樣做,而是從中間開始,因為你知道以 K 打頭的名字在電話簿中間。


現在假設你登入 Facebook。當你這樣做時,Facebook 必須核實你是否有其網站的賬戶,因此必須在其資料庫中查詢你的使用者名稱。如果你的使用者名稱為 karlmageddon,Facebook 可從以 A 打頭的部分開始查詢,但更合乎邏輯的做法是從中間開始查詢。


這是一個查詢問題,在前述所有情況下,都可使用同一種演算法來解決問題,這種演算法就是二分查詢。


二分查詢是一種演算法,其輸入是一個有序的元素列表(必須有序的原因稍後解釋)。如果要查詢的元素包含在列表中,二分查詢返回其位置;否則返回null。


下圖是一個例子。


640?wx_fmt=png


下面的示例說明了二分查詢的工作原理。我隨便想一個1~100的數字。


640?wx_fmt=png


你的目標是以最少的次數猜到這個數字。你每次猜測後,我會說小了、大了或對了。


假設你從1開始依次往上猜,猜測過程會是這樣。


640?wx_fmt=png


這是簡單查詢,更準確的說法是傻找。每次猜測都只能排除一個數字。如果我想的數字是 99,你得猜 99 次才能猜到!


01

更佳的查詢方式


下面是一種更佳的猜法。從 50 開始。


640?wx_fmt=png


小了,但排除了一半的數字!至此,你知道 1~50 都小了。接下來,你猜75。


640?wx_fmt=png


大了,那餘下的數字又排除了一半!使用二分查詢時,你猜測的是中間的數字,從而每次都將餘下的數字排除一半。接下來,你猜 63(50 和 75 中間的數字)。


640?wx_fmt=png


這就是二分查詢,你學習了第一種演算法!每次猜測排除的數字個數如下。


640?wx_fmt=png


不管我心裡想的是哪個數字,你在 7 次之內都能猜到,因為每次猜測都將排除很多數字!


假設你要在字典中查詢一個單詞,而該字典包含 240000 個單詞,你認為每種查詢最多需要多少步?


640?wx_fmt=png 

如果要查詢的單詞位於字典末尾,使用簡單查詢將需要 240000 步。使用二分查詢時,每次排除一半單詞,直到最後只剩下一個單詞。


640?wx_fmt=png


因此,使用二分查詢只需 18 步——少多了!一般而言,對於包含 n 個元素的列表,用二分查詢最多需要 log2n 步,而簡單查詢最多需要 n 步。


02

對數


你可能不記得什麼是對數了,但很可能記得什麼是冪。log10100相當於問“將多少個10相乘的結果為100”。答案是兩個:10 × 10 = 100。因此,log10100 = 2。對數運算是冪運算的逆運算。

640?wx_fmt=png


對數是冪運算的逆運算


本文使用大O表示法(稍後介紹)討論執行時間時,log 指的都是 log2。使用簡單查詢法查詢元素時,在最糟情況下需要檢視每個元素。


而使用二分查詢時,最多需要檢查 log n個元素。如果列表包含 8 個元素,你最多需要檢查 3 個元素,因為 log 8 = 3(23 = 8)。如果列表包含1024個元素,你最多需要檢查10個元素,因為 log 1024 = 10(210 =1024)。


下面來看看如何編寫執行二分查詢的 Python 程式碼。你只需知道,可將一系列元素儲存在一系列相鄰的桶(bucket),即陣列中。這些桶從0開始編號:第一個桶的位置為#0,第二個桶為#1,第三個桶為#2,以此類推。


low = 0high = len(list) - 1

640?wx_fmt=png


你每次都檢查中間的元素。

mid = (low + high) / 2  ←---如果(low + high)不是偶數,Python自動將mid向下取整。guess = list[mid]


如果猜的數字小了,就相應地修改low

if guess < item:
 low = mid + 1

640?wx_fmt=png

如果猜的數字大了,就修改high。完整的程式碼如下。

def binary_search(list, item):
 low = 0    (以下2行)low和high用於跟蹤要在其中查詢的列表部分
 high = len(list)—1  

 while low <= high:  ←-------------只要範圍沒有縮小到只包含一個元素,
   mid = (low + high) / 2  ←-------------就檢查中間的元素
   guess = list[mid]    if guess == item:  ←-------------找到了元素      return mid    if guess > item:  ←-------------猜的數字大了
     high = mid - 1
   else:  ←---------------------------猜的數字小了
     low = mid + 1
 return None  ←--------------------沒有指定的元素

my_list = [1, 3, 5, 7, 9]  ←------------來測試一下!print binary_search(my_list, 3) # => 1  ←--------------------別忘了索引從0開始,第二個位置的索引為1print binary_search(my_list, -1) # => None  ←--------------------在Python中,None表示空,它意味著沒有找到指定的元素


03

電腦科學領域著名的旅行商問題


下面就是一個執行時間極長的演算法。這個演算法要解決的是旅行商問題,其計算時間增加得非常快,而有些非常聰明的人都認為沒有改進空間。


有一位旅行商,他需要前往5個城市。

640?wx_fmt=png

這位旅行商(姑且稱之為 Opus 吧)要前往這5個城市,同時要確保旅程最短,為此,可考慮前往這些城市的各種可能順序。

640?wx_fmt=png

若按旅程最短的路線來選,5 個城市有 120 種不同的排列方式。因此,解決這個問題需要執行 120 次操作。涉及 6 個城市時,需要執行 720 次操作,依次遞增!

640?wx_fmt=png


推而廣之,涉及 n 個城市時,需要執行 n!(n 的階乘)次操作才能計算出結果。因此執行時間為 O(n!),即階乘時間。


這種演算法很糟糕!這是電腦科學領域待解的問題之一。對於這個問題,目前還沒有找到更快的演算法,有些很聰明的人認為這個問題根本就沒有更巧妙的演算法。


面對這個問題,我們能做的只是去找出近似答案,高水平的讀者可以在《演算法圖解:像小說一樣有趣的演算法入門書》中研究一下二叉樹!!


掃描下方二維碼閱讀全書

640?wx_fmt=jpeg


點選閱讀原文,學習演算法也可以很有趣!!

點個在看

學演算法不禿頭

↓↓

相關文章