算法图解笔记
- 前言知識(shí)
- 第一章,算法簡(jiǎn)介
- 1.2,二分法查找元素
- 1.2.1,特殊的二分查找
- 1.2,二分法查找元素
- 第二章,選擇排序
- 2.1,內(nèi)存工作原理
- 2.2.1,鏈表
- 2.2.2,數(shù)組
- 2.2.3,術(shù)語(yǔ)
- 2.3,選擇排序
- 2.4,小結(jié)
- 2.1,內(nèi)存工作原理
- 第三章,遞歸
- 3.2,基線(xiàn)條件和遞歸條件
- 3.3,棧
- 3.3.1,調(diào)用棧
- 3.3.2,遞歸調(diào)用棧
- 3.4,小結(jié)
- 第四章,快速排序
- 4.1,分而治之
- 4.2 快速排序
- 4.3 再談大O表示法
- 4.4,小結(jié)
- 第五章,散列表
- 5.3,沖突
- 5.4,性能
- 5.5,小結(jié)
- 第六章,廣度優(yōu)先搜索
- 6.1,圖是什么
- 6.2,廣度優(yōu)先搜索
- 6.3,棧與隊(duì)列
- 6.4,代碼實(shí)現(xiàn)圖結(jié)構(gòu)
- 6.4.1 運(yùn)行時(shí)間
- 第七章,迪克斯特拉算法
- 7.1,使用迪克斯特拉算法
- 7.2,術(shù)語(yǔ)
- 7.3,負(fù)權(quán)邊
- 7.4,編程實(shí)現(xiàn)狄克斯特拉算法
- 7.5,小結(jié)
- 第八章,貪婪(貪心)算法
- 8.1,教室調(diào)度問(wèn)題
- 8.2,背包問(wèn)題
- 8.3,集合覆蓋問(wèn)題
- 8.3.1,近似算法
- 8.4,NP完全問(wèn)題
- 8.4.1,如何識(shí)別NP完全問(wèn)題
- 8.5,小結(jié)
- 第九章,動(dòng)態(tài)規(guī)劃
- 9.1,概念
- 9.2,背包問(wèn)題
- 9.1,最長(zhǎng)公共子串
- 第十章,K最近鄰算法
- 第十一章 接下來(lái)如何做
前言知識(shí)
十大經(jīng)典排序算法動(dòng)畫(huà)與解析,看我就夠了!(配代碼完全版)
10 大排序算法時(shí)間復(fù)雜度及空間復(fù)雜度如下圖:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-A010KZLD-1664372500316)(…/data/images/排序算法時(shí)間復(fù)雜度.jpg)]
第一章,算法簡(jiǎn)介
1.2,二分法查找元素
二分查找是一種算法,其輸入是一個(gè)有序的元素列表(必須有序的原因稍后解釋)。如果要查找的元素包含在列表中,二分查找返回其位置;否則返回 null,使用二分查找時(shí),每次猜測(cè)的是中間的數(shù)字,從而將余下的數(shù)字排除一半。(僅僅當(dāng)列表是有序的時(shí)候,二分查找才管用)
一般而言,對(duì)于包含n個(gè)元素的列表查找某個(gè)元素,使用二分法最多需要 log2nlog_{2}nlog2?n 步(**時(shí)間復(fù)雜度為 log2nlog_{2}nlog2?n **),簡(jiǎn)單查找最多需要 n 步。大 O 表示法指出了算法最糟糕情況下的運(yùn)行時(shí)間。二分法實(shí)例代碼如下:
def binary_search(list, item):low = 0high = len(list)-1while low <= high:mid = (low + high) // 2if list[mid] == item:return midelif list[mid] > item:high = mid - 1else:low = mid + 1return Noneif __name__ == "__main__":print(binary_search([1,2,3,4,6,7], 3)) # 輸出 21.2.1,特殊的二分查找
有序數(shù)組中的目標(biāo)出現(xiàn)多次,利用二分查找返回在最左邊出現(xiàn)的目標(biāo)值或者是最右邊出現(xiàn)的目標(biāo)值,實(shí)例代碼如下:
def binary_search2(arr, target, flag="left"):if not arr:return Noneleft = 0right = len(arr) - 1while left <= right:mid = left + (right - left) // 2 # 防止數(shù)據(jù)過(guò)大溢出?if arr[mid] < target:left = mid + 1elif arr[mid] > target:right = mid -1else:if flag == "left":if mid > 0 and arr[mid-1] == target:right = mid -1 # 不斷向最左邊逼近else:return midelif flag == "right":if mid + 1 < len(arr) and arr[mid + 1] == target:left = mid + 1 # 不斷向最右邊逼近else:return midreturn Noneif __name__ == "__main__":print(binary_search2([1,1,1,3,3,3,4], 3, "left")) # 查找最左邊出現(xiàn)的目標(biāo)值, 輸出3print(binary_search2([1,1,1,3,3,3,4,4,4], 3, "right")) # 查找最右邊出現(xiàn)的目標(biāo)值, 輸出5第二章,選擇排序
2.1,內(nèi)存工作原理
在計(jì)算機(jī)中,存儲(chǔ)多項(xiàng)數(shù)據(jù)時(shí),有兩種基本方式-數(shù)組和鏈表。但它們并非適用于所有情形。
2.2.1,鏈表
鏈表中的元素可存儲(chǔ)在內(nèi)存的任何地方。
鏈表的每個(gè)元素都存儲(chǔ)了下一個(gè)元素的地址,從而使一系列隨機(jī)的內(nèi)存地址串在一起。鏈表結(jié)構(gòu)直觀顯示如下圖所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-G7hRcnjg-1664372500320)(…/data/images/2.1鏈表.png)]
鏈表的優(yōu)勢(shì)在插入元素方面,那數(shù)組的優(yōu)勢(shì)又是什么呢?
2.2.2,數(shù)組
需要隨機(jī)地讀取元素時(shí),數(shù)組的效率很高,因?yàn)榭裳杆僬业綌?shù)組的任何元素。在鏈表中,元素并非靠在一起的,你無(wú)法迅速計(jì)算出第五個(gè)元素的內(nèi)存 地址,而必須先訪問(wèn)第一個(gè)元素以獲取第二個(gè)元素的地址,再訪問(wèn)第二個(gè)元素以獲取第三個(gè)元素 的地址,以此類(lèi)推,直到訪問(wèn)第五個(gè)元素。
2.2.3,術(shù)語(yǔ)
數(shù)組的元素帶編號(hào),編號(hào)從 0 而不是 1 開(kāi)始,幾乎所有的編程語(yǔ)言都從0開(kāi)始對(duì)數(shù)組元素進(jìn)行編號(hào),比如C/C++的數(shù)組結(jié)構(gòu)和Python的列表結(jié)構(gòu)。
元素的位置稱(chēng)為索引。下面是常見(jiàn)數(shù)組和鏈表操作的運(yùn)行時(shí)間.
2.3,選擇排序
選擇排序時(shí)間復(fù)雜度O(n2)O(n^{2})O(n2)
def findSmallest(arr):smallest = arr[0] # 存儲(chǔ)最小的值smallest_index = 0 # 存儲(chǔ)最小元素的索引for i in range(1, len(arr)):if arr[i] < smallest:smallest_index = ismallest = arr[i]return smallest # 選擇排序法對(duì)數(shù)組進(jìn)行排序 def selectionSort(arr):newArr = []for i in range(len(arr)):smallest = findSmallest(arr)arr.remove(smallest)newArr.append(smallest)return newArr # 實(shí)例應(yīng)用 print(selectionSort([5, 3, 6, 100])) # [3, 5, 6, 100]2.4,小結(jié)
- 計(jì)算機(jī)內(nèi)存猶如一大堆抽屜。
- 需要存儲(chǔ)多個(gè)元素時(shí),可使用數(shù)組或鏈表。
- 數(shù)組的元素都在一起。
- 鏈表的元素是分開(kāi)的,其中每個(gè)元素都存儲(chǔ)了下一個(gè)元素的地址。
- 數(shù)組的讀取速度很快。
- 鏈表的插入和刪除速度很快.
- 在同一個(gè)數(shù)組中,所有元素的類(lèi)型都必須相同(都為int、 double等)。
第三章,遞歸
學(xué)習(xí)如何將問(wèn)題分成基線(xiàn)條件和遞歸條件,學(xué)習(xí)如何使用遞歸算法,遞歸算法直觀上更好理解,步驟簡(jiǎn)單。
3.2,基線(xiàn)條件和遞歸條件
編寫(xiě)遞歸函數(shù)時(shí),必須告訴它何時(shí)停止,因此,每個(gè)遞歸函數(shù)有兩個(gè)部分:基線(xiàn)條件(base case)和遞歸條件(recursive case)。遞歸條件指的是函數(shù)調(diào)用自己,而基線(xiàn)條件則 指的是函數(shù)不再調(diào)用自己,從而避免形成無(wú)限循環(huán)。
3.3,棧
棧的定義:棧是一種只能從表的一端存取數(shù)據(jù)且遵循 “先進(jìn)后出” 原則的線(xiàn)性存儲(chǔ)結(jié)構(gòu)。
調(diào)用棧(call stack)
3.3.1,調(diào)用棧
計(jì)算機(jī)在內(nèi)部使用被稱(chēng)為調(diào)用棧的棧。調(diào)用另一個(gè)函數(shù)時(shí),當(dāng)前函數(shù)暫停 并處于未完成狀態(tài)。該函數(shù)的所有變量的值都還在內(nèi)存中。棧頂?shù)姆娇蛑赋隽水?dāng)前執(zhí)行 到了什么地方。
3.3.2,遞歸調(diào)用棧
棧在遞歸中扮演著重要角色。使用棧雖然很方便,但是也要付出代價(jià):存儲(chǔ)詳盡的信息可能占用大量的內(nèi)存。每個(gè)函數(shù)調(diào) 用都要占用一定的內(nèi)存,如果棧很高,就意味著計(jì)算機(jī)存儲(chǔ)了大量函數(shù)調(diào)用的信息。在這種情況 下,你有兩種選擇。
- 重新編寫(xiě)代碼
- 使用尾遞歸
3.4,小結(jié)
- 遞歸值的是調(diào)用自己的函數(shù)
- 每個(gè)遞歸函數(shù)都有兩個(gè)條件:基線(xiàn)條件和遞歸條件
- 棧有兩種操作:壓如和彈出
- 所有函數(shù)調(diào)用都進(jìn)入調(diào)用棧
- 調(diào)用棧可能很長(zhǎng),這將占用大量?jī)?nèi)存
第四章,快速排序
快速排序使用分而治之的策略,分而治之是我們學(xué)習(xí)的第一種通用的問(wèn)題解決辦法。
分而治之(divide and conquer,D&C)-一種著名的遞歸式問(wèn)題解決辦法。
4.1,分而治之
D&C算法是遞歸的。使用D&C解決問(wèn)題的過(guò)程包括兩個(gè)步驟:
- 找出基線(xiàn)條件,這種條件必須盡可能簡(jiǎn)單。
- 不斷將問(wèn)題分解(或者說(shuō)縮小規(guī)模),直到符合基線(xiàn)條件。
D&C并非可直接用于解決問(wèn)題的算法,而是一種解決問(wèn)題的思路。
4.2 快速排序
C語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的函數(shù)qsort實(shí)現(xiàn)的就是快速排序。快速排序也是用了D&C思想。
對(duì)數(shù)組進(jìn)行快速排序,步驟如下:
在平均情況下,快速排序時(shí)間復(fù)雜度O(nlogn)O(nlogn)O(nlogn)。快速排序代碼如下:
def quicksort(array):if len(array) < 2: # 基線(xiàn)條件:為空或只包含一個(gè)元素的數(shù)組是“有序”的return arrayelse:# 遞歸條件pivot = array[0] less = [x for x in array[1:] if x <= pivot]greater = [x for x in array[1:] if x > pivot]return quicksort(less) + [pivot] + quicksort(greater) print(quicksort([4, 90, 0, 2, 17, 79, 12])) # [0, 2, 4, 12, 17, 79, 90]上面的代碼空間復(fù)雜度很大,真正的快排是原地排序,空間復(fù)雜度為O(1),代碼如下:
# _*_ coding:utf-8 _*_def quick_sort(L):return q_sort(L, 0, len(L)-1)def q_sort(L, left, right):if left < right:pivot = Partition(L, left, right)q_sort(L, left, pivot-1)q_sort(L, pivot+1, right)return Ldef Partition(L, left, right):pivotkey = L[left]while left < right:while left < right and L[right] >= pivotkey:right -= 1L[left] = L[right]while left < right and L[left] <= pivotkey:left += 1L[right] = L[left] # 遇到比基準(zhǔn)大的數(shù), 則覆蓋在之前尾部指針的位置L[left] = pivotkeyreturn leftif __name__ == "__main__":L = [5, 9, 1, 1, 11, 6, 7, 2, 4]print(quick_sort(L))4.3 再談大O表示法
快速排序的獨(dú)特之處在于,其速度取決于選擇的基準(zhǔn)值。在討論快速排序的運(yùn)行時(shí)間前,我 們?cè)賮?lái)看看最常見(jiàn)的大O運(yùn)行時(shí)間。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-JYpoxy7V-1664372500324)(…/data/images/4.3再談大O表示法.png)]
- 選擇排序,其運(yùn)行時(shí)間為 O(n2)O(n^2)O(n2),速度非常慢。
- 還有一種名為合并排序(merge sort)的排序算法,其運(yùn)行時(shí)間為 O(nlogn)O(nlogn)O(nlogn),比選擇排序快得多!
- 快速排序的情況比較棘手,在最糟情況下,其運(yùn)行時(shí)間為 O(n2)O(n^2)O(n2)。與選擇排序一樣慢!但這是最糟情況。在平均情況下,快速排序的運(yùn)行時(shí)間為 O(nlogn)O(nlogn)O(nlogn)。
由對(duì)數(shù)的換底公式,loganlog_a nloga?n 和 logbnlog_b nlogb?n 只有一個(gè)常數(shù)因子不同,這個(gè)因子在大O記法中被丟棄。因此記作O(logn)O(log n)O(logn),而不論對(duì)數(shù)的底是多少,是對(duì)數(shù)時(shí)間算法的標(biāo)準(zhǔn)記法。
4.4,小結(jié)
- D&C將問(wèn)題逐步分解。使用D&C處理列表時(shí),基線(xiàn)條件很可能是空數(shù)組或只包含一個(gè)元 素的數(shù)組。
- 實(shí)現(xiàn)快速排序時(shí),請(qǐng)隨機(jī)地選擇用作基準(zhǔn)值的元素。快速排序的平均運(yùn)行時(shí)間為O(n log n)。
- 大O表示法中的常量有時(shí)候事關(guān)重大,這就是快速排序比合并排序快的原因所在。
- 比較簡(jiǎn)單查找和二分查找時(shí),常量幾乎無(wú)關(guān)緊要,因?yàn)榱斜砗荛L(zhǎng)時(shí), O(log n)的速度比O(n) 快得多。
第五章,散列表
數(shù)組和鏈表結(jié)構(gòu)可以用以查找,棧不行。散列表也叫哈希表(Hash table),散列表有些類(lèi)似 Python 中的字典 dict 結(jié)構(gòu)。散列表可用以:
- 模擬映射關(guān)系;
- 防止重復(fù);
- 緩沖/記住數(shù)據(jù),以免服務(wù)器再通過(guò)處理來(lái)生成它們。
5.3,沖突
給兩個(gè)鍵分配的位置相同,這被稱(chēng)為沖突(collision)。處理沖突最簡(jiǎn)單的辦法就是:如果兩個(gè)鍵映射到了同一個(gè)位置,就在這個(gè)位置存儲(chǔ)一個(gè)鏈表。
5.4,性能
散列表,數(shù)組,鏈表的查找、插入刪除元素的時(shí)間復(fù)雜度,如下表所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-mGqZtFSo-1664372500326)(…/images/5.4性能.png)]
在平均情況下,散列表的查找(獲取給定索引處的值)速度與數(shù)組一樣快,而插入和刪除速 度與鏈表一樣快,因此它兼具兩者的優(yōu)點(diǎn)!但在最糟情況下,散列表的各種操作的速度都很慢。 因此,在使用散列表時(shí),避開(kāi)最糟情況至關(guān)重要。為此,需要避免沖突。而要避免沖突,需要有:
- 較低的填裝因子;
- 良好的散列函數(shù)。
5.5,小結(jié)
散列表是一種功能強(qiáng)大的數(shù)據(jù)結(jié)構(gòu),其操作速度快,還能讓你以不同的方式建立數(shù)據(jù)模型。 你可能很快會(huì)發(fā)現(xiàn)自己經(jīng)常在使用它。
- 你可以結(jié)合散列函數(shù)和數(shù)組來(lái)創(chuàng)建散列表。
- 沖突很糟糕,你應(yīng)使用可以最大限度減少?zèng)_突的散列函數(shù)。
- 散列表的查找、插入和刪除速度都非常快。
- 散列表適合用于模擬映射關(guān)系。
- 一旦填裝因子超過(guò)0.7,就該調(diào)整散列表的長(zhǎng)度。
- 散列表可用于緩存數(shù)據(jù)(例如,在Web服務(wù)器上)。
- 散列表非常適合用于防止重復(fù)。
第六章,廣度優(yōu)先搜索
圖算法:廣度優(yōu)先搜索(breadth-first search, BFS)算法
廣度優(yōu)先搜索讓你能夠找出兩樣?xùn)|西之間的最短距離,不過(guò)最短距離的含義有很多!使用廣度優(yōu)先搜索可以:
- 編寫(xiě)國(guó)際跳棋AI,計(jì)算最少走多少步就可獲勝;
- 編寫(xiě)拼寫(xiě)檢查器,計(jì)算最少編輯多少個(gè)地方就可將錯(cuò)拼的單詞改成正確的單詞,如將 READED改為READER需要編輯一個(gè)地方;
- 根據(jù)你的人際關(guān)系網(wǎng)絡(luò)找到關(guān)系最近的醫(yī)生。
解決最短路徑問(wèn)題的算法被稱(chēng)為廣度有限算法,一般步驟為:
6.1,圖是什么
圖由節(jié)點(diǎn)(node)和邊(edge)組成。
6.2,廣度優(yōu)先搜索
在廣度優(yōu)先搜索的執(zhí)行過(guò)程中,搜索范圍從起點(diǎn)開(kāi)始逐漸向外延伸,即先檢查一度關(guān)系,再檢查二度關(guān)系。
6.3,棧與隊(duì)列
- 棧:后進(jìn)先出(Last In First Out,LIFO)的數(shù)據(jù)結(jié)構(gòu)
- 隊(duì)列:先進(jìn)先出(First In First Out,FIFO)的數(shù)據(jù)結(jié)構(gòu),支持入隊(duì)和出對(duì)操作。
6.4,代碼實(shí)現(xiàn)圖結(jié)構(gòu)
圖中每個(gè)節(jié)點(diǎn)都與相鄰節(jié)點(diǎn)相連,散列表結(jié)構(gòu)可以表示這種關(guān)系。
圖分為有向圖(directed graph)和無(wú)向圖(undirected graph),有向圖關(guān)系是單向的,無(wú)向圖沒(méi)有箭頭,直接相連的節(jié)點(diǎn)互為鄰居。對(duì)從自己出發(fā)有指向他人的箭頭,則有鄰居。
6.4.1 運(yùn)行時(shí)間
如果你在你的整個(gè)人際關(guān)系網(wǎng)中搜索芒果銷(xiāo)售商,就意味著你將沿每條邊前行(記住,邊是從一個(gè)人到另一個(gè)人的箭頭或連接),因此運(yùn)行時(shí)間至少為 O(邊數(shù))O(邊數(shù))O(邊數(shù))。你還使用了一個(gè)隊(duì)列,其中包含要檢查的每個(gè)人。將一個(gè)人添加到隊(duì)列需要的時(shí)間是固定的,即為 O(1)O(1)O(1),因此對(duì)每個(gè)人都這樣做需要的總時(shí)間為 O(人數(shù))O(人數(shù))O(人數(shù))。所以,廣度優(yōu)先搜索的運(yùn)行時(shí)間為 O(人數(shù)+邊數(shù))O(人數(shù) + 邊數(shù))O(人數(shù)+邊數(shù)),這通常寫(xiě)作 O(V+E)O(V + E)O(V+E),其中 VVV 為頂點(diǎn)( vertice)數(shù), EEE 為邊數(shù)。
第七章,迪克斯特拉算法
7.1,使用迪克斯特拉算法
迪克斯特拉算法能夠找出加權(quán)圖中前往X的最短路徑。對(duì)于尋找耗時(shí)最少的路徑,迪克斯特拉算法包含4個(gè)步驟:
每個(gè)節(jié)點(diǎn)都有開(kāi)銷(xiāo)。開(kāi)銷(xiāo)指的是從起點(diǎn)前往該節(jié)點(diǎn)需要多長(zhǎng)時(shí)間。
7.2,術(shù)語(yǔ)
- 帶權(quán)重的圖稱(chēng)為加權(quán)圖( weighted graph),不帶權(quán)重的圖稱(chēng)為非加權(quán)圖( unweighted graph)。
- 要計(jì)算非加權(quán)圖中的最短路徑,可使用廣度優(yōu)先搜索。要計(jì)算加權(quán)圖中的最短路徑,可使用狄克斯特拉算法。
- 在無(wú)向圖中,每條邊都是一個(gè)環(huán)。狄克斯特拉算法只適用于有向無(wú)環(huán)圖( directed acyclic graph, DAG)。
圖可能有環(huán),所謂環(huán),是指由一個(gè)節(jié)點(diǎn)出發(fā),走一圈后可以又回到原節(jié)點(diǎn),如下圖所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-nvShzYxH-1664372500329)(…/data/images/7.2環(huán)示意圖.png)]
7.3,負(fù)權(quán)邊
因此, 不能將狄克斯特拉算法用于包含負(fù)權(quán)邊的圖。在包含負(fù)權(quán)邊的圖中,要找出最短路徑,可使用另一種算法——貝爾曼?福德算法( Bellman-Ford algorithm)。
7.4,編程實(shí)現(xiàn)狄克斯特拉算法
以下圖為例,編程實(shí)現(xiàn)耗時(shí)最短的路徑。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-IROpVHMd-1664372500334)(…/data/images/7.4圖算法問(wèn)題.png)]
代碼如下:
# 為了實(shí)現(xiàn)帶權(quán)圖,可使用散列表,散列表用Python字典實(shí)現(xiàn) graph = {} # 存儲(chǔ)起始節(jié)點(diǎn)鄰居和前往鄰居的開(kāi)銷(xiāo) graph['start'] = {} graph["start"]["a"] = 6 graph["start"]["b"] = 2 print(graph["start"].keys()) # 添加其他節(jié)點(diǎn)及其鄰居 graph["a"] = {} graph["a"]["fin"] = 1graph["b"] = {} graph["b"]["a"] = 3 graph["b"]["fin"] = 5# 終點(diǎn)沒(méi)有任何鄰居 graph['fin'] = {}# 創(chuàng)建存儲(chǔ)每個(gè)節(jié)點(diǎn)開(kāi)銷(xiāo)的開(kāi)銷(xiāo)表 infinity = float("inf") costs = {} costs["a"] = 6 costs["b"] = 2 costs["fin"] = infinity# 創(chuàng)建存儲(chǔ)父節(jié)點(diǎn)的散列表 parents = {} parents["a"] = "start" parents["b"] = "start" parents["fin"] = None # 創(chuàng)建一個(gè)數(shù)組,用于記錄處理過(guò)的節(jié)點(diǎn) processed = []# 找出開(kāi)銷(xiāo)最低的節(jié)點(diǎn) def find_lowest_cost_node(costs):lowest_cost = float("inf")lowest_cost_node = Nonefor node in costs:cost = costs[node]if cost < lowest_cost and node not in processed:lowest_cost = costlowest_cost_node = nodereturn lowest_cost_node# 在未處理的節(jié)點(diǎn)中找出開(kāi)銷(xiāo)最小的節(jié)點(diǎn) node = find_lowest_cost_node(costs) while node is not None:cost = costs[node]neighbors = graph[node]# 遍歷當(dāng)前節(jié)點(diǎn)的鄰居for n in neighbors.keys():new_cost = cost + neighbors[n]# 如果當(dāng)前節(jié)點(diǎn)前往該鄰居更近,就更新該鄰居的開(kāi)銷(xiāo), 同時(shí)將該鄰居的父節(jié)點(diǎn)設(shè)置為當(dāng)前節(jié)點(diǎn)if costs[n] > new_cost:costs[n] = new_costparents[n] = node# 將當(dāng)前節(jié)點(diǎn)標(biāo)記為處理過(guò)processed.append(node) # 找出接下來(lái)要處理的節(jié)點(diǎn),并循環(huán)node = find_lowest_cost_node(costs)print("Cost from the start to each node:") print(costs)7.5,小結(jié)
- 廣度優(yōu)先搜索用于在非加權(quán)圖中查找最短路徑。
- 狄克斯特拉算法用于在加權(quán)圖中查找最短路徑。
- 僅當(dāng)權(quán)重為正時(shí)狄克斯特拉算法才管用。
- 如果圖中包含負(fù)權(quán)邊,請(qǐng)使用貝爾曼?福德算法。
第八章,貪婪(貪心)算法
貪婪算法思想很簡(jiǎn)單:每步都采取最優(yōu)的做法,專(zhuān)業(yè)術(shù)語(yǔ)說(shuō),就是每步都選擇局部最優(yōu)解,最終得到的就是全局最優(yōu)解。
8.1,教室調(diào)度問(wèn)題
根據(jù)給定課表,盡可能將更多的課程安排在某間教室。解決辦法:貪婪算法可找到最優(yōu)解。
8.2,背包問(wèn)題
背包重量有限,根據(jù)策略使得裝入背包的物品價(jià)值最高。
在這里, 貪婪策略顯然不能獲得最優(yōu)解,但非常接近。在有些情況下,完美是優(yōu)秀的敵人。有時(shí)候,你只需找到一個(gè)能夠大致解決問(wèn)題的算法,此時(shí)貪婪算法正好可派上用場(chǎng),因?yàn)樗鼈儗?shí)現(xiàn)起來(lái)很容易,得到的結(jié)果又與正確結(jié)果相當(dāng)接近。
8.3,集合覆蓋問(wèn)題
每個(gè)廣播臺(tái)都覆蓋特定區(qū)域的州,找出覆蓋全美50個(gè)州的最小廣播集合。
貪婪算法解決這個(gè)問(wèn)題,當(dāng)廣播臺(tái)數(shù)量過(guò)多,算法所耗費(fèi)的時(shí)間將激增。
8.3.1,近似算法
集合覆蓋問(wèn)題舉例:每個(gè)廣播臺(tái)都覆蓋特定區(qū)域的州,找出覆蓋全美50個(gè)州的最小廣播集合。貪婪算法可以解決這個(gè)問(wèn)題,當(dāng)廣播臺(tái)數(shù)量過(guò)多,算法所耗費(fèi)的時(shí)間將激增。
這是一種近似算法( approximation algorithm) 。在獲得精確解需要的時(shí)間太長(zhǎng)時(shí),可使用近似算法。判斷近似算法優(yōu)劣的標(biāo)準(zhǔn)如下:
- 速度有多快;
- 得到的近似解與最優(yōu)解的接近程度。
代碼實(shí)例:
""" 準(zhǔn)備工作 """ # 創(chuàng)建一個(gè)列表,包含要覆蓋的州 states_needed = set(["mt", "wa", "or", "id", "nv", "ut", "ca", "az"]) # 廣播臺(tái)清單 stations = {} stations["kone"] = set(["id", "nv", "ut"]) stations["ktwo"] = set(["wa", "id", "mt"]) stations["kthree"] = set(["or", "nv", "ca"]) stations["kfour"] = set(["nv", "ut"]) stations["kfive"] = set(["ca", "az"]) # 定義一個(gè)集合存儲(chǔ)最終選擇的廣播臺(tái) final_stations = set() """ 計(jì)算答案 """ best_station = None while states_needed:best_station = Nonestates_covered = set()for station, states in stations.items():covered = states_needed & statesif len(covered) > len(states_covered):best_station = stationstates_covered = coveredstates_needed -= states_coveredfinal_stations.add(best_station) print(final_stations)程序輸出如下:
{‘kone’, ‘ktwo’, ‘kthree’, ‘kfive’}
貪心算法的實(shí)質(zhì)是每次選出當(dāng)前的最優(yōu)解,不管整體,是基于一定假設(shè)下的最優(yōu)解。
8.4,NP完全問(wèn)題
旅行商問(wèn)題和集合覆蓋問(wèn)題有一些共同之處:你需要計(jì)算所有的解,并從中選出最小/最短的那個(gè)。這兩個(gè)問(wèn)題都屬于NP完全問(wèn)題。NP完全問(wèn)題的簡(jiǎn)單定義是,以難解著稱(chēng)的問(wèn)題,如旅行商問(wèn)題和集合覆蓋問(wèn)題。很多非常聰明的人都認(rèn)為,根本不可能編寫(xiě)出可快速解決這些問(wèn)題的算法。
8.4.1,如何識(shí)別NP完全問(wèn)題
NP 完全問(wèn)題無(wú)處不在!如果能夠判斷出要解決的問(wèn)題屬于 NP 完全問(wèn)題就好了,這樣就不用 去尋找完美的解決方案,而是使用近似算法即可。但要判斷問(wèn)題是不是NP完全問(wèn)題很難,易于解決的問(wèn)題和 NP 完全問(wèn)題的差別通常很小。
但如果要找出經(jīng)由指定幾個(gè)點(diǎn)的的最短路徑,就是旅行商問(wèn)題——NP完全問(wèn)題。簡(jiǎn)言之,沒(méi)辦法判斷問(wèn)題是不是 NP 完全問(wèn)題,但還是有一些蛛絲馬跡可循的。
- 元素較少時(shí)算法的運(yùn)行速度非常快,但隨著元素?cái)?shù)量的增加,速度會(huì)變得非常慢。
- 涉及“所有組合”的問(wèn)題通常是NP完全問(wèn)題。
- 不能將問(wèn)題分成小問(wèn)題,必須考慮各種可能的情況。這可能是NP完全問(wèn)題。
- 如果問(wèn)題涉及序列(如旅行商問(wèn)題中的城市序列)且難以解決,它可能就是NP完全問(wèn)題。
- 如果問(wèn)題涉及集合(如廣播臺(tái)集合)且難以解決,它可能就是NP完全問(wèn)題。
- 如果問(wèn)題可轉(zhuǎn)換為集合覆蓋問(wèn)題或旅行商問(wèn)題,那它肯定是NP完全問(wèn)題。
8.5,小結(jié)
- 貪婪算法尋找局部最優(yōu)解,企圖以這種方式獲得全局最優(yōu)解。
- 對(duì)于NP完全問(wèn)題,還沒(méi)有找到快速解決方案。
- 面臨NP完全問(wèn)題時(shí),最佳的做法是使用近似算法。
- 貪婪算法易于實(shí)現(xiàn)、運(yùn)行速度快,是不錯(cuò)的近似算法。
第九章,動(dòng)態(tài)規(guī)劃
9.1,概念
動(dòng)態(tài)規(guī)劃算法是通過(guò)拆分問(wèn)題,定義問(wèn)題狀態(tài)和狀態(tài)之間的關(guān)系,使得問(wèn)題能夠以遞推(或者說(shuō)分治)的方式去解決。在學(xué)習(xí)動(dòng)態(tài)規(guī)劃之前需要明確掌握幾個(gè)重要概念,如下:
- 階段:對(duì)于一個(gè)完整的問(wèn)題過(guò)程,適當(dāng)?shù)那蟹譃槿舾蓚€(gè)相互聯(lián)系的子問(wèn)題,每次在求解一個(gè)子問(wèn)題,則對(duì)應(yīng)一個(gè)階段,整個(gè)問(wèn)題的求解轉(zhuǎn)化為按照階段次序去求解。
- 狀態(tài):狀態(tài)表示每個(gè)階段開(kāi)始時(shí)所處的客觀條件,即在求解子問(wèn)題時(shí)的已知條件。狀態(tài)描述了研究的問(wèn)題過(guò)程中的狀況。
- 決策:決策表示當(dāng)求解過(guò)程處于某一階段的某一狀態(tài)時(shí),可以根據(jù)當(dāng)前條件作出不同的選擇,從而確定下一個(gè)階段的狀態(tài),這種選擇稱(chēng)為決策。
- 策略:由所有階段的決策組成的決策序列稱(chēng)為全過(guò)程策略,簡(jiǎn)稱(chēng)策略。
- 最優(yōu)策略:在所有的策略中,找到代價(jià)最小,性能最優(yōu)的策略,此策略稱(chēng)為最優(yōu)策略。
- 狀態(tài)轉(zhuǎn)移方程:狀態(tài)轉(zhuǎn)移方程是確定兩個(gè)相鄰階段狀態(tài)的演變過(guò)程,描述了狀態(tài)之間是如何演變的。
9.2,背包問(wèn)題
學(xué)習(xí)動(dòng)態(tài)規(guī)劃,這是一種解決棘手問(wèn)題的方法,它將問(wèn)題分成小問(wèn)題,并先著手解決這些小問(wèn)題,每個(gè)動(dòng)態(tài)規(guī)劃問(wèn)題都是從一個(gè)網(wǎng)格入手,背包問(wèn)題的網(wǎng)格如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-BtcVv2ps-1664372500337)(…/data/images/9.2-背包問(wèn)題.jpg)]
工作原理:動(dòng)態(tài)規(guī)劃先解決子問(wèn)題,再逐步解決大問(wèn)題。從背包問(wèn)題的網(wǎng)格計(jì)算入手,可明白為何計(jì)算小背包可裝入的商品的最大價(jià)值。余下了空間時(shí),你可根據(jù)這些子問(wèn)題的答案來(lái)確定余下的空間可裝入哪些商品。計(jì)算每個(gè)單元格的價(jià)值時(shí),使用的公式都相同。 這個(gè)公式如下:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-bQDVmanL-1664372500338)(…/data/images/9.2-背包問(wèn)題公示圖.png)]
網(wǎng)格的行順序發(fā)生變化時(shí),最終答案沒(méi)有變化。各行的排列順序?qū)ψ罱K結(jié)果無(wú)關(guān)緊要。
動(dòng)態(tài)規(guī)劃功能強(qiáng)大,它能夠解決子問(wèn)題并使用這些答案來(lái)解決大問(wèn)題。 但僅當(dāng)每個(gè)子問(wèn)題都是離散的,即不依賴(lài)于其他子問(wèn)題時(shí),動(dòng)態(tài)規(guī)劃才管用。 這意味著使用動(dòng)態(tài)規(guī)劃算 法解決不了去巴黎玩的問(wèn)題。
9.1,最長(zhǎng)公共子串
通過(guò)動(dòng)態(tài)規(guī)劃問(wèn)題,得到以下啟示:
- 動(dòng)態(tài)規(guī)劃可幫助你在給定約束條件下找到最優(yōu)解在背包問(wèn)題中,你必須在背包容量給定的情況下,偷到價(jià)值最高的商品。
- 在問(wèn)題可分解為彼此獨(dú)立且離散的子問(wèn)題時(shí),就可使用動(dòng)態(tài)規(guī)劃來(lái)解決。
要設(shè)計(jì)出動(dòng)態(tài)規(guī)劃解決方案可能很難,這正是本節(jié)要介紹的。下面是一些通用的小貼士: - 每種動(dòng)態(tài)規(guī)劃解決方案都涉及網(wǎng)格。
- 單元格中的值通常就是你要優(yōu)化的值。在前面的背包問(wèn)題中,單元格的值為商品的價(jià)值。
- 每個(gè)單元格都是一個(gè)子問(wèn)題,因此你應(yīng)考慮如何將問(wèn)題分成子問(wèn)題,這有助于你找出網(wǎng)格的坐標(biāo)軸。
第十章,K最近鄰算法
第十一章 接下來(lái)如何做
總結(jié)
- 上一篇: 经济数学—线性代数第二版课后习题解析 吴
- 下一篇: 自动化睡眠分期工具:开源、免费、高效