閱讀本文能夠幫助你解開如下疑惑:算法是什麼?算法難不難?怎麼纔可以在短期內熟悉業內的經典算法呢?這些算法用 Python 實現會是什麼樣的?它們的耗時會跟時間複雜度相關嗎?程序員
算法中的指令描述的是一個計算,當其運行時能從一個初始狀態和(可能爲空的)初始輸入開始,通過一系列有限而清晰定義的狀態,最終產生輸出並中止於一個終態。一個狀態到另外一個狀態的轉移不必定是肯定的。隨機化算法在內的一些算法,包含了一些隨機輸入。算法
一個算法應該具備 「有窮性」、「確切性」、「輸入項」、「輸出項」、「可行性」 等重要的特徵。這些特徵對應的含義以下:bash
一,數據對象的運算和操做:計算機能夠執行的基本操做是以指令的形式描述的。一個計算機系統能執行的全部指令的集合,成爲該計算機系統的指令系統。一個計算機的基本運算和操做有以下四類:微信
二,算法的控制結構:一個算法的功能結構不只取決於所選用的操做,並且還與各操做之間的執行順序有關。app
你說這個算法好、他卻說這個算法很差,兩人爭論不休。那麼好與很差應該怎麼評定呢?函數
同一問題可用不一樣算法解決,而一個算法的質量優劣將影響到算法乃至程序的效率。算法分析的目的在於選擇合適算法和改進算法。一個算法的評價主要從時間複雜度和空間複雜度來考慮。測試
以上的理論知識可讓咱們對算法有個大體的理解和認知,接下來咱們將使用 Python 實現幾個經典的 排序算法,並在文末對比 Java 的實現。ui
除了《唐門》弟子以外(斗羅大陸中的唐門),排序算法也有內外之分。spa
比較經典的排序算法以下圖所示:3d
有冒泡排序、歸併排序、插入排序、希爾排序、選擇排序、快速排序等。
它們各自的時間複雜度以下圖所示:
在開始以前,首先要感謝公衆號《五分鐘學算法》的大佬 「程序員小吳」 受權動態圖片和排序思路。
冒泡排序的過程如上圖所示,對應的算法步驟爲:
根據動態圖和算法步驟, Python 實現冒泡排序的代碼以下:
data = [5, 4, 8, 3, 2]
def bubble(data):
for i in range(len(data)-1): # 排序次數
for s in range(len(data)-i-1): # s爲列表下標
if data[s] > data[s+1]:
data[s], data[s+1] = data[s+1], data[s]
return data
print(bubble(data))
複製代碼
程序運行後輸出結果爲:
[2, 3, 4, 5, 8]
複製代碼
這是一種時間複雜度上限比較高的方法,它的排序時間會隨着列表長度的增長而增長。
選擇排序的過程和步驟如上圖所示,根據動態圖和算法步驟, Python 實現選擇排序的代碼以下:
data = [3, 4, 1, 6, 2, 9, 7, 0, 8, 5]
def selections(nums):
for i in range(len(nums)):
min_index = min(nums) # 最小值
for j in range(len(nums) - i):
if nums[min_index] < nums[j]:
min_index = j
nums[min_index], nums[len(nums) - i - 1] = nums[len(nums) - i - 1], nums[min_index]
return nums
print(selections(data))
複製代碼
其中 min() 方法能夠得到列表中的最小值,運行結果爲:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
複製代碼
既然 min() 有這個特性 (備註:max() 方法能夠得到列表中最大值),咱們能夠將它利用起來,騷一點的代碼爲:
data = [3, 4, 1, 6, 2, 9, 7, 0, 8, 5]
res = []
for i in range(0, len(data)):
aps = min(data)
data.remove(aps)
res.append(aps)
print(res)
複製代碼
運行後獲得的輸出結果爲:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
複製代碼
假如將 min() 換成 max() 方法的,獲得的輸出結果爲:
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
複製代碼
這種只選擇列表最大元素或最小元素的行爲,是否也能稱爲選擇性排序呢?
雖然這種寫法的代碼比較短,也更容易理解。可是它的時間複雜度是如何的呢?
首先要確認 min 和 max 的時間複雜度。有人給出了 list 各項操做的時間複雜度:
能夠看到 min 和 max 都是隨着列表長度而增加,再加上自己須要 for 循環一次,因此這種寫法的時間複雜度爲
真的是這樣嗎?
代碼中有一個 remove 操做,將原列表的元素刪除,可是 remove 的時間複雜度也是O(n),這豈不是變成了 O(n*n + n),如何解決這個問題呢。
觀察到 pop 的時間複雜度是 O(1),那麼是否能夠利用 pop 來下降時間複雜度呢?list 提供了獲取元素下標的方法,咱們嘗試將代碼改成:
data = [3, 4, 1, 6, 2, 9, 7, 0, 8, 5]
res = []
for i in range(0, len(data)):
aps = max(data)
result = data.pop(data.index(aps))
print(result)
res.append(aps)
print(res)
複製代碼
運行後獲得的輸出結果爲:
9
8
7
6
5
4
3
2
1
0
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
複製代碼
因而可知確實可以根據索引刪除掉 list 元素,在刪除元素這裏下降了複雜度。
慢着,上述 pop 的時間複雜度是 O(1),可是 pop(data.index(i)) 這種操做的時間複雜度呢?也是 O(1) 嗎?咱們能夠作個實驗來驗證一下:
# 崔慶才丨靜覓、韋世東丨奎因 邀請你關注微信公衆號【進擊的Coder】
from datetime import datetime
data = [i for i in range(500000)]
start_time = datetime.now()
for i in range(len(data)):
data.pop(data.index(i))
print(data)
print(datetime.now() - start_time)
複製代碼
這是 pop(data.index(i)) 的代碼,運行結果以下:
[]
0:00:40.151812
複製代碼
而若是使用 pop()
from datetime import datetime
data = [i for i in range(500000)]
start_time = datetime.now()
for i in range(len(data)):
data.pop()
print(data)
print(datetime.now() - start_time)
複製代碼
運行後的結果爲:
[]
0:00:00.071441
複製代碼
結果顯而易見,pop(i) 的時間複雜度依舊是跟元素個數有關,而不是預想中的 O(1)。因爲列表元素不斷減小,因此它的時間複雜度也不是 O(n),假設當前列表元素數量爲 k,那麼這個部分的時間複雜度則是 O(k)。說明簡短的 min max寫法可以必定程度的下降時間複雜度。
驗證一下,兩次 for 循環的選擇排序寫法和 mix max 的簡短寫法耗時狀況如何:
from datetime import datetime
data = [i for i in range(30000)]
def selections(nums):
for i in range(len(nums)):
min_index = min(nums) # 最小值
for j in range(len(nums) - i):
if nums[min_index] < nums[j]:
min_index = j
nums[min_index], nums[len(nums) - i - 1] = nums[len(nums) - i - 1], nums[min_index]
return nums
start_time = datetime.now()
selections(data)
print(datetime.now() - start_time)
複製代碼
這裏以 3 萬個元素爲例,兩次 for 循環的運行時間爲 47 秒左右。而一樣的數量,用 min max 方式排序:
from datetime import datetime
data = [i for i in range(30000)]
start_time = datetime.now()
res = []
for i in range(0, len(data)):
aps = max(data)
# del data[data.index(aps)]
data.pop(data.index(aps))
res.append(aps)
print(datetime.now() - start_time)
複製代碼
所花費的時間爲 12 秒,代碼中用 del 和 pop 方法獲得的結果同樣。
還……還有這種操做?
選擇排序也是一種時間複雜度上限比較高的方法,它的排序時間一樣會隨着列表長度的增長而增長。
插入排序的過程和步驟如上圖所示,根據動態圖和算法步驟, Python 實現插入排序的代碼以下:
from datetime import datetime
data = [i for i in range(30000)]
data.insert(60, 5)
# 崔慶才丨靜覓、韋世東丨奎因 邀請你關注微信公衆號【進擊的Coder】
def direct_insert(nums):
for i in range(1, len(nums)):
temp = nums[i] # temp變量指向還沒有排好序元素(從第二個開始)
j = i-1 # j指向前一個元素的下標
while j >= 0 and temp < nums[j]:
# temp與前一個元素比較,若temp較小則前一元素後移,j自減,繼續比較
nums[j+1] = nums[j]
j = j-1
nums[j+1] = temp # temp所指向元素的最終位置
return nums
start_time = datetime.now()
res = direct_insert(data)
print(datetime.now() - start_time)
print(len(res), res[:10])
複製代碼
生成列表後在列索引爲 60 的地方插入一個值爲 5 的元素,如今數據量爲 3 萬零 1。代碼運行獲得的輸出結果爲:
0:00:00.007398
30001 [0, 1, 2, 3, 4, 5, 5, 6, 7, 8]
複製代碼
能夠看到 3 萬零 1 個元素的列表排序耗時很短,並且經過切片能夠看到順序已經通過排列。
而後測試一下選擇,代碼以下:
from datetime import datetime
data = [i for i in range(30000)]
data.insert(60, 5)
def selections(nums):
for i in range(len(nums)):
min_index = min(nums) # 最小值
for j in range(len(nums) - i):
if nums[min_index] < nums[j]:
min_index = j
nums[min_index], nums[len(nums) - i - 1] = nums[len(nums) - i - 1], nums[min_index]
return nums
start_time = datetime.now()
res = selections(data)
print(datetime.now() - start_time)
print(len(res), res[:10])
複製代碼
代碼運行後獲得的輸出結果爲:
0:00:47.895237
30001 [0, 1, 2, 3, 4, 5, 5, 6, 7, 8]
複製代碼
能夠看到 3 萬零 1 個元素的列表排序耗並不短,耗費了 47 秒鐘,經過切片能夠看到順序已經通過排列。
接着試一下 max min 型選擇排序的寫法,獲得的結果爲:
0:00:14.150992
30001 [29999, 29998, 29997, 29996, 29995, 29994, 29993, 29992, 29991, 29990]
複製代碼
這簡直了,爲何這種操做就可以節省這麼多時間呢?
最後測試一下冒泡:
# 崔慶才丨靜覓、韋世東丨奎因 邀請你關注微信公衆號【進擊的Coder】
from datetime import datetime
data = [i for i in range(30000)]
data.insert(60, 5)
def bubble(data):
for i in range(len(data)-1): # 排序次數
for s in range(len(data)-i-1): # s爲列表下標
if data[s] > data[s+1]:
data[s], data[s+1] = data[s+1], data[s]
return data
start_time = datetime.now()
res = bubble(data)
print(datetime.now() - start_time)
print(len(res), res[:10])
複製代碼
代碼運行後獲得的輸出結果爲:
0:00:41.392303
30001 [0, 1, 2, 3, 4, 5, 5, 6, 7, 8]
複製代碼
能夠看到 3 萬零 1 個元素的列表排序耗並不短,耗費了 41 秒鐘,經過切片能夠看到順序已經通過排列。
獲得的結果爲:
問題:實在是使人匪夷所思,插入排序的速度竟然比其餘兩種排序方式耗時少那麼多。這是爲何呢?
事實上插入排序只用了 1 層 for 循環,並不是像冒泡和選擇那樣使用 2 層 for 循環,是否是由此能夠刷新上圖中對於時間複雜度的介紹呢?
問題:而兩種不一樣的選擇排序法的結果差別這麼大,這又是爲何???
請在評論區發表你的見解