資料結構與演算法基礎(基於python)
用大O法表示執行時間,log都表示log2(以2為底的對數)
所有的演算法都是基於python寫的
一、二分查詢法:
1、輸入:一個有序的元素列表
2、輸出:如果要查詢的元素包含在列表中,二分查詢返回其位置,否則返回NULL.
3、使用二分查詢,每次排除一半的數字
4、演算法過程:檢查中間元素,若小了,就改變low,如果大了就修改high
5、完整程式碼如下:
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
if guess > item:
high=mid-1
if guess < item:
low=mid+1
return None
//測試一下
mylist=[1,3,5,7,9]
print binary_search(mylist,3)
print binary_search(mylist,-1)
6、執行時間:
①簡單查詢:O(n)
②二分查詢:O(log n)
③快速排序:O(n*log n)
④選擇排序:O(n^2)
⑤旅行商問題:一種非常慢的演算法O(n!)
旅行商問題:找最短路徑
二、選擇排序
1、涉及到資料結構——陣列和連結串列
①陣列:記憶體單位相連,固定好了一定的記憶體空間,如果不夠不可以臨時加,只能重新劃定一個連續記憶體空間
②連結串列:元素可儲存在記憶體的任何地方,每一個元素都儲存了下一個元素的地址,優勢在插入元素方面。但是當你要訪問某一個元素時,你就得從連結串列頭開始一個個訪問過去,對於跳躍真的很不方便,因為你不知道後面的元素的地址,只能一個個往後推著走。
執行時間 | 陣列 | 連結串列 |
---|---|---|
讀取 | O(1) | O(n) |
插入 | O(n) | O(1) |
刪除 | O(n) | O(1) |
2、插入(刪除)
①使用連結串列插入,只需要改變前面那個元素指向的地址,通常都記錄了連結串列的第一個元素和最後一個元素
②使用陣列,則必須將後面的元素都向後移,這樣感覺會很麻煩,有時候要是數目大於陣列的已劃分的記憶體的長度,那就得複製到一個新的陣列中了
③連結串列只能順序訪問,陣列支援隨機訪問
所以,插入用連結串列,刪除同理
3、選擇排序思想:
每次從剩下的元素中查詢到最大(或最小的元素),之後將元素挑出來放在新列表中接下去的位置中。
4、程式碼如下:
def findSmallest(arr):
smallest = arr[0] #用於儲存最小的值
smallest_index=0 #用於儲存最小的索引
for i in range(1,len(arr)):
if arr[i]<smallest:
smallest=arr[i]
smallest_index=i
return smallest_index
def selectionSort(arr):
newArr=[]
for i in range(len(arr)):
smallest=fomdSmallest(arr)
newArr.append(arr.pop(smallest))#找出陣列中最小的元素,並將其加入到新陣列中,從原來的陣列中pop出來
return newArr
print selectionSort([5,3,6,2,10,85])
三、快速排序
1、分而治之D&C
2、遞迴——讓解決方案更清晰
遞迴指的是函式呼叫自己,相關資料結構—棧(壓棧和出棧),每次呼叫函式時,計算機都將函式呼叫所涉及到的所有變數的值儲存在記憶體中,如果陷入沒完沒了的遞迴會導致棧溢位。
3、快速排序思想:
①找一個元素作為基準值
②找出比基準值小的元素還有比基準值大的元素,得到——一個由所有小於基準值的數字組成的子陣列 + 基準值 + 一個由所有大於基準值的數字組成的子陣列
③對子陣列繼續進行排序(遞迴呼叫)
歸納證明:歸納條件和基線條件
4、程式碼如下:
#O(n log n)
def quicksort(array):
if len(array) <2:
return array #基線條件:為空或者只包含一個元素的陣列(有序)
else:
plvot=array[0]
less=[i for i in array[1:] if i<=plvot]
greater=[i for i in array[1:] if i > plvot]
return quicksort(less)+[plvot]+quicksort(greater)
print quicksort([10,5,2,6,8,9])
5、關於大O時間
①最佳/平均:O(log n)
②最差:O(n^2)
四、散列表—dict(),由鍵和值組成
1、雜湊函式
①雜湊函式總是將同樣的輸入對映到相同的索引,每次取相同已知數的值的函式值時都能得到一樣的函式值結果
②雜湊函式將不同的輸入對映到不同的索引
③雜湊函式知道陣列多大,只返回有效的索引
2、操作散列表
①建立 number=dict() 或者 number={} ( 一對大括號 )
②新增元素number[“Marry”]=8945968
域名 → IP 就是用散列表
③散列表可以防止重複(鍵)
④將散列表用作快取:網站將資料記住,不再需要重新計算獲取。
⑤當訪問一個網站頁面時,它會先檢查散列表中是否儲存了該頁面。
cache={} #建立散列表
def get_page(url):
if cache.get(url): #散列表的get方法
return cache[url]
else:
data=get_data_from_server(url)
cache[url]=data
return data
⑥散列表用於模擬對映關係
⑦衝突:如果兩個鍵對映到了同一個位置,就在這個位置儲存一個連結串列
⑧好的雜湊函式應該做到將鍵均勻地對映到散列表的不同位置
⑨效能:在平均情況下,散列表執行各種操作的時間都是O(1),是常量時間,並不意味著馬上,而是說不管散列表多大,所需的時間都一樣。
散列表 | 平均情況 | 最糟情況 | 陣列 | 連結串列 |
---|---|---|---|---|
查詢 | O(1) | O(n) | O(1) | O(n) |
插入 | O(1) | O(n) | O(n) | O(1) |
刪除 | O(1) | O(n) | O(n) | O(1) |
⑩填裝因子=散列表包含的元素數 / 位置總數,用於度量散列表中有多少位置是空的,填裝因子大於1意味著元素數量超過了陣列的位置數。填裝因子越低,散列表效能越高。
均勻分佈:即儘量讓不同的鍵值擁有不同的雜湊值
五、廣度優先遍歷—圖演算法,
1、作用:非加權圖的最短路徑
①從節點A出發,有前往節點B的路徑嗎?
②從節點A出發,前往節點B的哪條路徑最短?
2、廣度優先遍歷思想:
首先在一度關係中搜索,確定一度關係中沒有結論後才在二度關係中進行搜尋,搜尋範圍從起點開始逐漸向外延伸
3、涉及到的資料結構—佇列:入隊、出隊 + 散列表(用於對映)
佇列的操作
①queue()
定義一個空佇列,無引數,返回值是空佇列。
②enqueue(item)
在佇列尾部加入一個數據項,引數是資料項,無返回值。
③ dequeue()
刪除佇列頭部的資料項,不需要引數,返回值是被刪除的資料,佇列本身有變化。
④isEmpty()
檢測佇列是否為空。無引數,返回布林值。
⑤ size()
返回佇列資料項的數量。無引數,返回一個整數。
⑥deque()
雙向佇列
4、程式碼如下:
from collections import deque
def search(name):
search_queue = deque()
search_queue += graph[name]
searched=[] #用於記錄檢查過的人的陣列
while search_queue:
person=search_queue.popleft() #獲取最左邊一個元素,並在佇列中刪除
if not person in searched:
if person_is_seller(person):
print person + " is a mango seller!"
return true
else:
search_queue += graph[person] #可能是個陣列
searched.append(person)
return False
graph={} #建立一個散列表
graph["you"]=["alice","bob","cici"]
graph["alice"]=["peggy"]
graph["bob"]=["ann"]
........ #表示圖的鄰居關係,一度關係
search["you"]
5、執行時間:O(V + E)
V:表示頂點數,E表示邊數
六、狄克斯特拉演算法 Dijkstra
1、作用:
找出權重最小的路徑
2、過程:
①找出最便宜的節點,即在最短時間內可以前往的節點
②對於該節點的鄰居,檢查是否有前往它們的更短路徑,如果有,更新開銷
③重複這個過程,直到對圖中的每個節點都這樣做了
④計算最終路徑
負權邊不適合用狄克斯特拉演算法,包含負權邊應該使用貝爾曼-福德演算法
3、涉及資料結構:散列表
4、程式碼如下:
def find_lowest_cost_node(costs):
lowest_cost=float("lnf")
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 is not None:
cost =costs[node]
neighbors=graph[node]
for n in neighbors.keys(): #遍歷所有的鄰居節點
new_cost=cost + neighbors[n]
if costs[n] >new_costs: #如果經當前節點前往該鄰居更近
costs[n]=new_cost #更新該鄰居的開銷
parents[n]=node #同時將該鄰居的父節點設定為當前節點
processed.append(node) #將當前節點加入到處理佇列
node=find_lowest_cost_node(costs) #找出接下來要處理的節點
七、貪婪演算法
八、動態規劃
1、將問題分成小問題,並先著手解決小問題,每個動態規劃解決方案都設計網格。
九、K最近鄰演算法—KNN
1、推薦電影?根據喜好?
2、計算兩點的距離,可以使用畢達哥斯拉公式
3、涉及機器學習:
① OCR:光學字元識別 ——人臉識別、語音識別
② 垃圾郵箱過濾器—樸素貝葉斯分類器
4、KNN用於分類和迴歸,需要考慮最近的鄰居。分類就是編組,迴歸就是預測結果
十、其他
1、數:二叉樹、B樹(平衡樹)
B樹、平衡樹是什麼?效能比較好的樹
2、反向索引:搜尋引擎、搜尋
3、傅立葉變換:分析成分,處理訊號、壓縮音樂;
4、並行演算法
5、歸併函式
6、布隆過濾器
7、SHA演算法
8、Diffie-Hellman演算法:公開金鑰演算法,非對稱
9、線性規劃:Simplex演算法