《算法圖解》學習筆記習題和代碼(第一、二章 二分法 選擇排序)Python3

目錄

一、二分法

練習1

1.3 大O表示法

練習2

1.3.5 旅行商 問題O(n!)

二、選擇排序

2.1 內存

2.2 數組和鏈表  

2.2.2 數組的優勢

練習 1

2.2.4 在中間插入 

2.2.5 刪除 

練習 2

2.3 選擇排序 

2.4 小結 



 

一、二分法

僅當列表是有序的時候,二分查找才管用。時間複雜度:\log_2n。(\log_ax:log以a爲底,以x爲真數。)

e.g. 從100個數中猜一個數,不超過7次就可以猜出來。2^7=128 

函數binary_search接受一個有序數組和一個元素。如果指定的元素包含在數組中,這個函數將返回其位置。

def binary_search(list,item):
    low = 0
    high = len(list)-1
    while low<=high:
        mid = (low+high) //2
        guess = list[mid]
        if guess == item:
            return mid
        elif guess > item:
            high = mid-1
        elif guess < item:
            low = mid+1
    return None
my_list = [1,3,5,7,9,11]
print(binary_search(my_list,7))
print(binary_search(my_list,1))

Output:
3
0

練習1

1.1 假設有一個包含128個名字的有序列表,你要使用二分查找在其中查找一個名字,請 問最多需要幾步才能找到? 

2^7=128,7步
1.2 上面列表的長度翻倍後,最多需要幾步?

2^8=256,8步

最多需要猜測的次數與列表長度相同,這被稱爲線性時間(linear time)。 O(n)

如果列表包含100個元素,最多要猜7次;如果列表包含40億個數字,最多需猜32次。二分查找的運行時間爲對數時間(或log時間)。O(\log n)

1.3 大O表示法

大O表示法讓你能夠比較操作數,它指出了算法運行時間的增速。 

大 O 表示法指出了最糟情況下的運行時間。 查找數用遍歷的方法運行時間爲O(n),用二分法則是O(\log n)

除最糟情況下的運行時間外,還應考慮平均情況的運行時間。在第四章討論。

 

下面按從快到慢的順序列出了經常會遇到的5種大O運行時間。 

O(log n),也叫對數時間,這樣的算法包括二分查找。
O(n),也叫線性時間,這樣的算法包括簡單查找。
O(n * log n),這樣的算法包括第4章將介紹的快速排序——一種速度較快的排序算法。
O(n2),這樣的算法包括第2章將介紹的選擇排序——一種速度較慢的排序算法。
O(n!),這樣的算法包括接下來將介紹的旅行商問題的解決方案——一種非常慢的算法。

練習2

算法的速度指的並非時間,而是操作數的增速。算法的運行時間用大O表示法表示。

使用大O表示法給出下述各種情形的運行時間。 

1.3 在電話簿中根據名字查找電話號碼。

O(logn)(名字是有序的?)

1.4 在電話簿中根據電話號碼找人。(提示:你必須查找整個電話簿。)

O(n)

1.5 閱讀電話簿中每個人的電話號碼。

O(n)

1.6 閱讀電話簿中姓名以A打頭的人的電話號碼。這個問題比較棘手,它涉及第4章的概念。答案可能讓你感到驚訝!

O(n),大O表示法不考慮乘以、除以、加上或減去的數字

1.3.5 旅行商 問題O(n!)

 一個人前往這5個城市,同時要確保旅程最短。爲此,可考慮前往這些城市的各種可能順序。所以要計算5x4x3x2=120次,才能得出最短旅程是什麼。涉及n個城市時,需要執行n!(n的階乘)次操作才能計算出結果。因此運行時間爲O(n!)。目前計算機領域認爲沒有更巧妙的算法。

1.4 小結 
二分查找的速度比簡單查找快得多。
O(log n)比O(n)快。需要搜索的元素越多,前者比後者就快得越多。
算法運行時間並不以秒爲單位。
算法運行時間是從其增速的角度度量的。
算法運行時間用大O表示法表示。 

二、選擇排序

二分查找只能用於有序元素列表;選擇排序是下一章將介紹的快速排序的基石。

2.1 內存

要把數據存儲到內存時,需要向計算機請求內存空間,計算機會爲你分配一個存儲地址。需要存儲多項數據時,有兩種基本方式——數組和鏈表。下圖中:fe0ffeeb是一個內存單元的地址。

 

2.2 數組和鏈表  

數組:

在數組中添加新元素也可能很麻煩。如果沒有了空間,就得移到內存的其他地方,因此添加新元素的速度會很慢。 

鏈表:

而鏈表中的元素可存儲在內存的任何地方。

 鏈表的每個元素都存儲了下一個元素的地址,從而使一系列隨機的內存地址串在一起。 

 使用鏈表時,不需要移動元素。只要有足夠的內存空間,就能爲鏈表分配內存。 鏈表的優勢在於插入元素。

2.2.2 數組的優勢

數組也有優勢。

鏈表在按順序讀取元素時,一個接一個跳向下一個地址(即使存儲位置不挨着)效率很高。但是我想訪問最後一個元素的時候,它的地址是存在上一個元素裏的,上一個元素的地址。。。以此類推。需要跳躍的找到某個元素(不按順序),鏈表的效率真的很低。 

數組則不是。假設假設有一個數組,它包含五個元素,起始地址爲00,就很容易推斷出第五個元素的地址是什麼。

元素的位置稱爲索引。00,01,02,03,04成爲索引。 

需要隨機地讀取元素時,數組的效率很高,因爲可迅速找到數組的任何元素。在鏈表中,元素並非靠在一起的,你無法迅速計算出第五個元素的內存地址,而必須先訪問第一個元素以獲取第二個元素的地址,再訪問第二個元素以獲取第三個元素的地址,以此類推,直到訪問第五個元素。  

常見的數組和鏈表操作的運行時間。 

練習 1

2.1 假設你要編寫一個記賬的應用程序。 

你每天都將所有的支出記錄下來,並在月底統計支出,算算當月花了多少錢。因此,你執行的插入操作很多,但讀取操作很少。該使用數組還是鏈表呢? 

答:採用鏈表。因爲鏈表(O(1))的插入操作時間少於數組(O(n))。

2.2.4 在中間插入 

新增待辦事項要插入到原來的清單中間去,用鏈表比較合適。插入時只用修改這個元素之前的那個元素指向的地址即可。使用數組的話,則必須將後面的元素都向後移。,如果沒有足夠的存儲空間,整個數組還要移動到別的地方去。

 

2.2.5 刪除 

頻繁刪除元素,鏈表也具有優勢,只用修改前一個元素的指向地址即可。而數組刪除元素,必須把其後面的元素全部前移。

 數組和鏈表哪個更好用,要分情況,數組使用的更多。因爲數組支持順序訪問和隨機訪問,鏈表只能順序訪問。數組讀取的速度更快。鏈表擅長插入和刪除,而數組擅長隨機訪問。數組和鏈表還被用來實現其他數據結構,後面會講。

練習 2

2.2 假設你要爲飯店創建一個接受顧客點菜單的應用程序。這個應用程序存儲一系列點菜單。服務員添加點菜單,而廚師取出點菜單並製作菜餚。這是一個點菜單隊列:服務員在隊尾添加點菜單,廚師取出隊列開頭的點菜單並製作菜餚。 你使用數組還是鏈表來實現這個隊列呢?(提示:鏈表擅長插入和刪除,而數組擅長隨機訪問。在這個應用程序中,你要執行的是哪些操作呢?) 

答:使用鏈表,因爲整個過程頻繁執行插入操作,而且廚師是順序讀取,不需要隨機訪問。

2.3 我們來做一個思考實驗。假設Facebook記錄一系列用戶名,每當有用戶試圖登錄  Facebook時,都查找其用戶名,如果找到就允許用戶登錄。由於經常有用戶登錄Facebook,因此需要執行大量的用戶名查找操作。假設Facebook使用二分查找算法,而這種算法要求能夠隨機訪問——立即獲取中間的用戶名。考慮到這一點,應使用數組還是鏈表來存儲用戶名呢?

答:用數組存儲用戶名。1.因爲使用二分法要求列表是有序的 。2.數組支持隨機訪問。

2.4 經常有用戶在Facebook註冊。假設你已決定使用數組來存儲用戶名,在插入方面數組有何缺點呢?具體地說,在數組中添加新用戶將出現什麼情況? 

答:插入只能順序插入,不然需要後面的元素都後移。而且需要足夠的內存,順序存儲。在數組添加新用戶時,順序添加,如果內存不夠,也不能存到別的地方,必須整體移動到別處。如果是中間插入,就需要把後面的元素全部後移,比較費時。

2.5 實際上,Facebook存儲用戶信息時使用的既不是數組也不是鏈表。假設Facebook使用的是一種混合數據:鏈表數組。這個數組包含26個元素,每個元素都指向一個鏈表例如,該數組的第一個元素指向的鏈表包含所有以A打頭的用戶名,第二個元素指向的鏈表包含所有以B打頭的用戶名,以此類推。 

假設Adit B在Facebook註冊,而你需要將其加入前述數據結構中。因此,你訪問數組的第一個元素,再訪問該元素指向的鏈表,並將Adit B添加到這個鏈表末尾。現在假設你要查找Zakhir H。因此你訪問第26個元素,再在它指向的鏈表(該鏈表包含所有以z打頭的用戶名)中查找Zakhir H。 

請問,相比於數組和鏈表,這種混合數據結構的查找和插入速度更慢還是更快?你不必給出大O運行時間,只需指出這種新數據結構的查找和插入速度更快還是更慢。 

答:融合了數組和鏈表的特點。查找時比數組慢,比鏈表快。插入時比鏈表慢,比數組快。(網上很多都是說於鏈表相當,可能時是前面26個字母的數組查找時相比於龐大的存儲,相當於常數?)。

2.3 選擇排序 

爲喜歡的樂隊按播放次數排序。遍歷選擇,第一次看完n個選出次數最多的。第二次看剩下n-1個,依次類推。

 

要找出播放次數最多的樂隊,必須檢查列表中的每個元素。

 需要的總時間爲 O(n × n),即O(n^2)。 

選擇排序速度不快,快速排序是一種更快的排序算法,其運行時間爲O(n log n),下一章介紹。 

#用選擇排序,將數組元素按從小到大的順序排列。
#第一步找到最小的元素再返回索引
def findsmallest(arr):
    smallest = arr[0]
    for i in range(len(arr)):
        if arr[i]<=smallest:
            smallest = arr[i]
            small_index = i
        i+=1
    return small_index

a =[5,3,6,8,2,7,9] #待排序的數組
print(findsmallest(a))

def sortnumber(arr):
    new = []
    for i in range(len(arr)):
        index = findsmallest(arr)
        new.append(arr[index])
        del arr[index]
    return new
print(sortnumber(a))

Out:

4
[2, 3, 5, 6, 7, 8, 9]

2.4 小結 


計算機內存猶如一大堆抽屜。
需要存儲多個元素時,可使用數組或鏈表。
數組的元素都在一起。
鏈表的元素是分開的,其中每個元素都存儲了下一個元素的地址。
數組的讀取速度很快。
鏈表的插入和刪除速度很快。
在同一個數組中,所有元素的類型都必須相同(都爲int、double等)。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章