1. 程式人生 > 實用技巧 >演算法圖解-筆記

演算法圖解-筆記

ch1_演算法簡介

對數的定義:冪運算的逆運算

大O表示法:指出演算法有多快,也就是演算法執行時間的增速

  • 談論演算法的速度時,我們說的是隨著輸入的增加,其執行時間將以什麼樣的速度增加
  • O(logn)比O(n)快
  • 演算法執行時間是從增速的角度衡量的

ch2_選擇排序

陣列:意味著所有資料在記憶體中是緊密相連的

  • 資料過大需要轉移位置
  • 預備可能增加的資料需要留出位置,這將會浪費記憶體
  • 陣列支援隨機訪問
  • 陣列讀取速度很快
  • 同一個陣列中,所有元素型別必須相同

連結串列:可以存放在記憶體的任何地方,每一個元素都存放了下個元素的地址

  • 插入刪除元素很方便
  • 無法迅速找到某個元素的地址
陣列 連結串列
讀取 O(1) O(N)
插入 O(n) O(1)
刪除 O(n) O(1)

選擇排序:檢查列表中每個元素,找到最大最小的,讓後從剩下的資料中繼續找,直到排序結束,O(N*N)

ch3_遞迴

遞迴函式有兩部分

  • base case:函式不用在呼叫自己的條件
  • recursive case:函式呼叫自己的條件
def countdown(i):
    print i
    if i<= 1:
        return
    else:
        countdown(i-1)
  • 遞迴指的是呼叫自己的函式
  • 每個遞迴函式都有兩個條件:基線條件和遞迴條件
  • 棧有兩種操作:壓入和彈出
  • 所有函式呼叫都進入呼叫棧(LIFO)
  • 呼叫棧可能很長,將佔用大量記憶體

ch4_快速排序

利用了分而治之的思想(divide and conquer(D&C))

  • 找出簡單的基線條件
  • 確定如何縮小問題的規模,使其複合base case(函式不呼叫自己)

注意:再回到basecase前,函式呼叫都不會完成,遞迴會爆粗你這些函式呼叫的狀態,放入棧區。

涉及陣列的遞迴函式,基線條件通常是陣列為空或者只包含了一個元素。

def sum(arr):
    total = 0
    if len(arr) == 0:
        return 0
    elif len(arr) == 1:
        return arr[0]
    else:
        return arr[0]+sum(arr[1:])

快速排序程式碼:

  • 基線條件是當陣列為空或者只有一個元素
  • 目標是不斷逼近基線
  • 首先選取一個元素
  • 找到比它小的部分放左邊,比他大的部分放右邊
  • 左右兩邊遞迴呼叫
def quicksort(array):
    if len(array)<2:
        return array
    else:
        pivot = array[0]
        less = [i for i in array[1:] if i<=pivot ]
        greater = [i for i in array[1:] if i> pivot]
        return quicksort(less)+[pivot]+quicksort(greater)

總結:

  • D&C將問題逐步分解,使用D&C時,基線條件很可能是空陣列或只包含一個元素的陣列
  • 實現快速排序時,請隨機選擇作為基準的元素,平均時間為O(nlogn)
  • 大O表示法隱去了常量,這就是快速排序比合並排序更快的原因

CH5_散列表

散列表的查詢時間為O(1)

雜湊函式

  • 必須是一致的(同樣的輸入返回同樣的輸出)
  • 將不同的輸入對映到不同的數字
  • 雜湊函式直到陣列多大,只返回有效的索引

散列表的應用

  • DNS解析:將域名變為ip

  • 檢查重複

  • 散列表應用於快取

小結:

  • 散列表適用於模擬對映關係
  • 防止衝突
  • 快取/記住資料,避免伺服器在通過處理生成他們

散列表的衝突

衝突:給兩個鍵分配的位置相同

處理方案:如果兩個鍵對映到了同一個位置,就在這個位置存放一個連結串列

如果雜湊函式選擇的不好,所有的鍵都會對映到一個位置,理想狀態是函式將鍵均勻的對映到散列表的不同位置

散列表儲存的連結串列很長,散列表的速度將會急速下降

為了避免解決衝突

  • 較低的裝填因子

  • 良好的雜湊函式

裝填因子 = 散列表包含的元素數/位置總數

一旦裝填因子超過0.7,就應該調整散列表長度

ch6_廣度優先搜尋

用於解決圖問題中的最短路徑問題

什麼是圖:用於模擬不同的東西是如何相連的

廣度優先搜尋解決兩類問題:

  • 從A節點 出發,有前往B節點的路徑嗎?
  • 從節點A出發,前往節點B的那條路徑最短?

佇列

佇列不能隨機訪問隊中的元素,只能入隊和出隊

是一種先進先出(FIFO)的資料結構

棧則是一種(LIFO)的資料結構

#利用散列表實現圖
graph = {}
graph["you"] = ["alice","bob","claire"]
graph["bob"] = ["anuj","peggy"]
graph["alice"] = ["peggy"]
graph["claire"] = ["thom","jonny"]
graph["anuj"] = []
graph["peggy"] = []
graph["jonny"] = []
graph["thom"] = []
#芒果經銷商案例
#廣度優先
# 1、建立一個佇列,用於儲存要檢查的人
from collections import deque
def person_is_seller(name):
    return name[-1] == 'm'
def search(name):
    search_queue = deque() 
    search_queue += graph["you"]#將你的鄰居放進佇列
    searched = []
    # 2、當佇列不為空,就去出佇列中的第一個人

    while search_queue:
        person = search_queue.popleft()
        #3 當這個個人沒有被檢查
        if person not in searched:
            #4 檢查這個人是否是芒果經銷商
            if person_is_seller(person):
                print(person+"is a mongo seller!")
                return True
            else:
                #5 不是芒果經銷商、將這個人的朋友都加入搜尋隊列
                search_queue += graph[person]
                searched.append(person)
    return False

執行時間:O(V+E):V是頂點數、E是邊數

總結:

  • 對於檢查過的人、不要再檢查,會導致無限迴圈
  • 佇列是先進先出的
  • 棧是後進先出的
  • 搜尋列表必須是佇列

ch7_狄克斯特拉演算法

簡介:適用於有向無環圖

  • 找出”最便宜“的節點,可在短時間內到達的節點
  • 更新該節點鄰居的開銷
  • 重複這個過程
  • 計算最終路徑
"""狄克斯特拉演算法"""
# ------------整個圖的散列表(字典)--------
graph = {}

# 起點
graph["start"] = {} # 起點是一個散列表(字典)
graph["start"]["A"] = 6 # 儲存權重,start-A
graph["start"]["B"] = 2 # 儲存權重,start-B
print(graph["start"].keys())

# 其他節點
graph["A"] = {} # A節點也是一個字典
graph["A"]["destination"] = 1 # A-終點

graph["B"] = {}
graph["B"]["A"] = 3 # B-A
graph["B"]["destination"] = 5 # B - 終點

# 終點
graph["destination"] = {} # 終點沒有任何鄰居
print(graph["B"])

#建立開銷表
#對於不知道的開銷(終點),我們設定為無窮大
infinity = float("inf")

costs = {}
costs["B"] = 6
costs["A"] = 2
costs["destination"] = infinity

#建立一個儲存父節點的散列表
parents = {}
parents["A"] = "start"
parents["B"] = "start"
parents["destination"] = None

#需要一個數據記錄處理過的節點
processed = []

# -------------找到開銷最低的節點-------------
def find_lowest_cost_node(costs):
    lowest_cost = float("inf")
    lowest_cost_node = None
    for node in costs: # 遍歷所有節點
        cost = costs[node] # 對應為鍵值
        if cost < lowest_cost and node not in processed:#如果當前節點開銷更低且從未處理過
            lowest_cost = cost
            lowest_cost_node = node
    return lowest_cost_node

"""狄克斯特拉演算法"""
node = find_lowest_cost_node(costs) # 在未處理的節點中找到權重開銷最小的節點

while node:
    cost = costs[node]
    neighbors = graph[node]
    for i in neighbors.keys(): # 遍歷當前節點的所有鄰居xiaojie
        new_cost = cost + neighbors[i] # neighbors[i]相當於neighbors.value鍵值,即對應的權重值
        if costs[i] > new_cost:  # 原先直接前往i節點的開銷和現在路勁進行比較
            costs[i] = new_cost
            parents[i] = node
    processed.append(node)
    node = find_lowest_cost_node(costs)
    
print(costs["destination"])
  • 廣度優先搜素用於在非加權圖中查詢最短路徑
  • 狄克斯特拉演算法用於在加權圖中查詢最短路徑
  • 僅當權重為正時,狄克斯特拉演算法才管用
  • 如果有負權邊,請用貝爾曼-福德演算法

ch8_貪婪演算法

每一步都尋找全域性最優解、最終得到的就是全域性最優解

NP完全問題

  • 集合覆蓋問題、旅行商問題
  • 涉及集合和序列、難以解決
  • 不能將問題分成小問題,必須考慮各種可能的情況
  • 涉及所有組合的問題

貪婪演算法:

  • 尋找區域性最優解,企圖以這種方式獲得全域性最優解
  • 對於NP完全問題,沒有快速解決的方案
  • NP完全問題,最好的方法是近似演算法
  • 貪婪演算法是個好主意

有了上述分析,再用一句大白話來總結一下,P就是能在多項式時間內解決的問題,NP就是能在多項式時間驗證答案正確與否的問題。說一個問題是NP的,並不是說這個問題不能在多項式時間內解決,而是說目前為止,可能暫時尚未找到解法。所以,再強調一遍,NP不是P的否定。NP與P不是對立的,因為所有的P問題都是NP問題。

tips

在學習演算法設計與分析時,經常會提到NP完全性問題。目前已知的NP完全性問題就有2000多個,在圖上定義的許多組合優化問題是NP完全性問題,如貨郎問題、排程問題、最大團問題、最大獨立集合問題、Steiner樹問題、揹包問題、裝箱問題等,遇到這類問題時,通常從以下幾個方面來考慮,並尋求解決辦法:

(1) 動態規劃法:較高的解題效率。
(2) 分枝限界法: 較高的解題效率。
(3) 概率分析法: 平均效能很好。
(4) 近似演算法: 近似解代替最優解。
(5)啟發式演算法:根據具體問題的啟發式搜尋策略在求解,在實際使用可能很有效,但有時很難說清它的道理。

# You pass an array in, and it gets converted to a set.
states_needed = set(["mt", "wa", "or", "id", "nv", "ut", "ca", "az"])
 
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"])
 
final_stations = set()
 
while states_needed:
  best_station = None
  states_covered = set()
  for station, states in stations.items():
    # stations 是一個鍵值對
    # seation = station的 key
    # states = station 的value
    covered = states_needed & states #交集運算,兩者都有的元素
    if len(covered) > len(states_covered):
      best_station = station
      states_covered = covered
 
  states_needed -= states_covered
  final_stations.add(best_station)
 
print(final_stations)

ch9_動態規劃

動態規劃將大問題拆解為小問題,逐步計算最大價值。
由於縱軸粒度加大,需要擴充表格
但僅當每個子問題都是離散的,即不依賴於其他子問題時,動態規劃才管用。

動態規劃c語言

https://blog.csdn.net/qq_34207422/article/details/69067708

其他要學習的部分

  • 二叉樹:資料查詢時,插入新陣列需要重新排序很麻煩,設計二叉查詢樹,插入和刪除操作更快

    • 二叉查詢樹不能隨機訪問
    • B數(資料庫)、紅黑樹、堆、伸展樹
  • 反向索引:搜尋引擎常用

  • 傅立葉變換:數字訊號分析

  • 並行演算法

  • mapreduce:分散式演算法、一種特殊的並行演算法

  • 布隆過濾器

  • SHA

  • 線性規劃