Python排序算法总结
Python排序算法總結
遞歸
在正式講算法之前,先介紹一下遞歸。遞歸是一種解決問題的思路。
特點
應用場景
知道結束的條件,但不確定循環次數。
示例1
觀察以下函數,如果x=3,哪些是遞歸,輸出結果是什么
def func1(x):print(x)func1(x-1)# func1 沒有結束條件,passdef func2(x):if x>0:print(x)func2(x+1)# func2 結束條件永遠不成立,passdef foo(x):if x>0:print(x)foo(x-1)# foo 結果:3 2 1def bar(x):if x>0:bar(x-1)print(x)# bar 結果:1 2 3上面的幾個函數,foo 和 bar 的輸出結果是相反的,我們來分析一下它們的執行流程:
foo函數
- 調用foo(3),入棧,stack=[foo(3)]
- if 3>0 成立
- 打印當前x值為3
- 調用 foo(2),入棧,stack=[foo(3), foo(2)]
- if 2>0 成立
- 打印當前x值為2
- 調用 foo(1),入棧,stack=[foo(3), foo(2), foo(1)]
- if 1>0 成立
- 打印當前x值為1
- 調用 foo(0),入棧,stack=[foo(3), foo[2], foo(1), foo(0)]
- if 0>0,條件不成立,下面代碼不再執行。
- foo(0)執行完畢,出棧,stack=[foo(3), foo[2], foo(1)]
- foo(1) 執行完畢,出棧,stack=[foo(3), foo[2]]
- foo(2) 執行完畢,出棧,stack=[foo(3)]
- foo(3)執行完畢,出棧,???#xff0c;stack=[ ]
圖示:
bar函數
- 調用bar(3),入棧,stack=[bar(3)]
- if 3>0 成立
- 調用 bar(2),入棧,stack=[bar(3), bar(2)]
- if 2>0 成立
- 調用 bar(1),入棧,stack=[bar(3), bar(2), bar(1)]
- if 1>0 成立
- 調用 bar(0),入棧,stack=[foo(3), foo[2], foo(1), foo(0)]
- if 0>0,條件不成立,下面代碼不再執行。
- foo(0)執行完畢,出棧,stack=[bar(3), bar[2], bar(1)]
- 打印當前x值為1
- foo(1) 執行完畢,出棧,stack=[foo(3), foo[2]]
- 打印當前x值為2
- foo(2) 執行完畢,出棧,stack=[foo(3)]
- 打印當前x值為3
- foo(3)執行完畢,出棧,???#xff0c;stack=[ ]
圖示:
小結
觀察bar和foo的執行,都是要”跳進去“,然后”跳出來“,bar進去的時候打印,foo是出來的時候打印,因此它們的輸出相反。正式因為還要跳出來,導出遞歸效率不高,盡管如此,有些問題必須用遞歸思想思想才能解決。
其實如果不跳出來,遞歸的速度也不慢。這個涉及尾遞歸,在此不討論。
示例2
用遞歸打印下面這句話:
觀察跳進去的時候打印,跳出來的時候也打印,實現如下
def little_fish(x):print('抱著',end='')if x == 0:print('我的小鯉魚',end='')else:little_fish(x-1)print('的我',end='')print('嚇得我抱起了') little_fish(2) """ 嚇得我抱起了 抱著抱著抱著我的小鯉魚的我的我的我 """示例3
漢諾塔問題,將所有的盤子,從a柱移到c柱,保持小的在上面,大的在下面,問怎么移?
def hanoi(x, a, b, c): # 所有的盤子從 a 移到 cif x>0:hanoi(x-1, a, c, b) # step1:除了下面最大的,剩余的盤子 從 a 移到 bprint('%s->%s'%(a, c)) # step2:最大的盤子從 a 移到 chanoi(x-1, b, a, c) # step3: 把剩余的盤子 從 b 移到 chanoi(2, 'a', 'b', 'c') """ 2個的情況,不論有多少個,最終都是這個模式 a->b a->c b->c """hanoi(3, 'a', 'b', 'c') """ 3個的情況 a->c a->b c->b a->c b->a b->c a->c """時間復雜度
看代碼,猜快慢
下面四組代碼,哪組運行時間最短?
print('Hello World') for i in range(n):print('Hello World') for i in range(n):for j in range(n):print('Hello World') for i in range(n):for j in range(n):for k in range(n):print('Hello World')直覺告訴我們,肯定是第一組。那么用什么方式來體現代碼(算法)運行的快慢呢?時間復雜度
我們來類比一下生活中的場景:
也就是說,時間復雜度是一個估算的結果,用它來描述算法的快慢。用描述上限的數學符號O()來表示算法在最壞情況下的運行時間。
漸進分析
1)對于一些輸入,第一個算法可能比第二個快,對于另外一些輸入呢,第二個又比第一個好。
2)也有可能對于一些輸入,第一個算法在一個機器上比第二個算法好,但是在另一臺機器上第二個又比第一個好。
漸近分析是一個大問題,它就是在算法分析中處理上面的問題的。在漸近分析中,我們用輸入的大小來評估算法的性能(我們不測量具體的運行時間)。我們計算的是隨著輸入大小的增加,算法所需要的時間(或者空間)。例如,我們考慮一個有序數組的搜索問題(搜索一個指定項)。
一個方法就是線性查詢(遞增順序是線性的),另一個方法就是二分查詢(遞增順序是對數級的)。為了能夠很好滴理解漸近分析是怎樣在算法分析中解決上面提到的問題,我們假設讓線性查找在一個快的機器上跑,而讓二分查詢在一個慢的機器上跑。對于輸入數組的大小比較小的時候,那么快的計算機花費的時間可能較少。但是,當輸入的數組大小增長到一定程度的時候,二分查詢的花費時間毫無疑問要比線性查詢花費的時間要少,盡管二分查詢是在比較挫的機器上跑的。原因是對遞增數組進行二分查詢對于輸入的大小是對數級的,而線性查詢則是線性級的。所以在特定的輸入大小之后,機器的本身是可以忽略的。
在確定時間復雜度度時,使用漸近分析的方式:我們不關注常數因子和低階項,比如有如下表達式:
T(n)=168n3+65n2+n+10000
根據數學原理,當一個函數(如這里的T(n))的n變得非常大以至于趨于無窮時,函數值的大小主要是由函數的最高階項來決定的。T(n)的最高階項是n3,去掉低階項和常數因子后,T(n)的時間復雜度可以用O(n3)來表示。
根據漸進分析的思想,現在我們可以大致估計,之前四組代碼的時間復雜度分別是O(1),O(n),O(n2),O(n3)
繼續猜
print('Hello World') print('Hello Python') print('Hello Algorithm')答案:O(1)
for i in range(n):print('Hello World’)for j in range(n):print('Hello World')答案:O(n2)
while n > 1:print(n)n = n // 2# n=64輸出:64 32 16 8 4 2先科普一下對數,忘了的自己補中學數學知識
26=64
log264=6
答案:O(log2n)或者O(logn) 計算機中都是以2為底,2常常省去
一眼判斷算法的時間復雜度
常見的時間復雜度
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n3)
tips: O(logn)和O(n)比較,可以這樣類比,切一根1米長棍子,前者每次切剩余的一半,后者每次切1公分,自然是前者快。剩余的比較直接作除法約分。
空間復雜度
用來評估算法內存占用大小的,以目前硬件性能我們較少關注它。另外,很多算法都是以“空間換時間的”。
列表查找
列表查找:從列表中查找指定元素
對于一段有序列表[1,2,3,4,5,6,7,8,9],我們可以通過以下兩種方式來進行查找
順序查找
也稱為線性查找,從列表第一個元素開始,順序進行搜索,直到找到為止
def linear_search(data_set, value):for i in data_set:if data_set[i] == value:return iprint(linear_search([1,2,3,4,5,6,7,8,9],4))線性查找,查最小的快,查最大的慢。在最壞的情況下,它的時間復雜度是O(n)
二分查找
搜索過程從數組的中間元素開始,如果中間元素正好是要查找的元素,則搜索過程結束;如果某一特定元素大于或者小于中間元素,則在數組大于或小于中間元素的那一半中查找,而且跟開始一樣從中間元素開始比較。如果在某一步驟數組為空,則代表找不到。這種搜索算法每一次比較都使搜索范圍縮小一半。
二分查找每一次比較都使搜索范圍縮小一半,因此它的時間復雜度是O(logn)
下面用代碼實現一下,這里選擇移動下標而不是切片的方式(每次切片都會將列表切片部分復制一份,空間開銷大):
方法一:循環
def bin_search(data, val):low = 0high = len(data)-1while low <= high: # 當low和high重合后,還沒有找到,那么循環結束,默認返回Nonemid = (low + high) // 2if data[mid] == val:return midelif data[mid] > val:high = mid - 1else:low = mid + 1print(bin_search(data, 4)) # 3方法二:遞歸
def bin_search_rec(data, val, low, high):if low <= high:mid = (low + high) // 2if data[mid] == val:return midelif data[mid] > val:return bin_search_rec(data, val, low, mid-1) else:return bin_search_rec(data, val, mid+1, high)print(bin_search_rec(data, 11, 0, len(data)-1)) # 結果是:3 # 注意,需要return遞歸的值,否則結果為None二分查找雖然快,但是要求列表是有序列表,這就要求對列表進行排序
列表排序
定義
將無序列表變為有序列表
輸入:無序列表;輸出:有序列表
應用場景
LOW B 三人組
冒泡排序
思路
列表中每兩個相鄰的數,如果前邊的比后邊的大,那么交換這兩個數……
關鍵詞
- loop(趟),loop等于列表長度-1 (最后一趟只剩最小的數在下面,不用比較,整個列表已經有序)
- 有序區,初始時,有序區長度為0
- 無序區,初始時,無序區長度為列表長度
過程
- loop1
- 指針在0,指針所指元素和下一個元素比較,大數上浮
- 指針在1,指針所指元素和下一個元素比較,大數上浮
- ……
- loop1 結束,無序區中最大數上浮至無序區頂部;有序區長度 + 1,無序區長度 - 1
- loop2
- 指針在0,指針所指元素和下一個元素比較,大數上浮
- 指針在1,指針所指元素和下一個元素比較,大數上浮
- ……
- loop2 結束,無序區中最大數上浮至無序區頂部;有序區長度 + 1,無序區長度 - 1
- ……
data傳入算法后,不需要返回,因為Python中列表是可變數據類型。
選擇排序
思路
一趟趟遍歷找出最小的數往前放
關鍵詞
- loop(趟),loop等于列表長度-1 (最后一趟只剩最大的數在后邊,不用比較,整個列表已經有序)
- 有序區,初始時,有序區長度為0
- 無序區,初始時,無序區長度為列表長度
過程
- loop1
- min_index指向第1個元素
- 循環剩余元素,只要有元素的值小于min_index所指元素,就令min_index指向該元素;循環結束后,min_index將指向列表中的最小元素
- ……
- loop1 結束,如果min_index不指向第1個元素,第1個元素與min_index所指元素交換值;有序區長度 + 1,無序區長度 - 1
- loop2
- min_index指向第2個元素
- 循環無序區,只要有元素的值小于min_index所指元素,就令min_index指向該元素,循環結束后,min_index將指向無序區的最小元素
- ……
- loop2 結束,如果min_index不指向第2個元素,第2個元素與min_index所指元素交換值;有序區長度 + 1,無序區長度 - 1
- ……
插入排序
思路
列表被分為有序區和無序區兩個部分:最初有序區只有一個元素,每次從無序區取出一個元素,插入到有序區的位置,直到無序區變空。
關鍵詞
- 有序區,初始時,有序區長度為1
- 無序區,初始時,無序區長度為列表長度-1
過程
- loop1
- 取出無序區的第1個元素,賦值為tmp
- 指針p指向有序區最后一個元素,如果tmp大于等于p所指的元素,直接放在p的下一個位置。如果tmp小于p所指的元素,交換二者的值
- 指針p往前移,繼續判斷tmp和p所指元素的大小
- ……
- loop1 結束,tmp被放置在有序區合適的位置;有序區長度 + 1,無序區長度 - 1
- loop2
- ……
- ……
data[p + 1] 就是tmp的值,不直接寫tmp,是為了保證隨著指針p的移動,tmp值不變
小結
冒泡排序 插入排序 選擇排序:
時間復雜度都是:O(n2)
空間復雜度都是:O(1) ,因為都是基于移動下標(指針)的方式
NB 三人組
快速排序
好寫的排序算法里最快的,快的排序算法里最好寫的。
思路
- 取一個元素p(第一個元素),使元素p歸位;
- 列表被p分成兩部分,左邊都比p小,右邊都比p大;寫一個partition 歸位函數
- 遞歸完成排序。
關鍵詞
- 整理(partition函數)
- 遞歸
過程
partition 歸位函數
- 指針left指向列表第一個元素,right指向最后一個元素
- left指向的元素 賦值給臨時變量tmp,left 指向的位置為空
- 將所有的元素和 tmp 比較,直到比它小的在左邊,比它大的在右邊
- left 指向的位置為空時,right 指向的值和 tmp 比較:
- 如果 right 指向的元素 >= tmp,說明該元素應該在tmp的右邊,位置不變
- right 指針向左移動1位,準備將下一個元素和 tmp 進行比較
- 否則該元素在tmp的左邊
- right 指向的元素移動至left所指的空位,right 指向的位置為空
- 如果 right 指向的元素 >= tmp,說明該元素應該在tmp的右邊,位置不變
- 一旦right 所指的位置為空,left 向右移動,left 指向的元素和 tmp 比較:
- 如果left 指向的元素 <= tmp,說明該元素應該在tmp的左邊,位置不變
- left 指針向右移動1位,準備將下一個元素和 tmp 進行比較
- 否則該元素在 tmp 右邊
- left 指向的元素 移動至 right 所指的空位,left 所在位置為空
- 如果left 指向的元素 <= tmp,說明該元素應該在tmp的左邊,位置不變
- 當 left 和 right 指向同一位置時,tmp 歸位,這時左邊的元素都比它小,右邊的元素都比它大
- 這時的 left (或者right)所在位置,就是列表中元素大小的分界線,函數返回left。
- left 指向的位置為空時,right 指向的值和 tmp 比較:
quick_sort 快排:不斷地調用 partition 歸位函數,直到整個列表有序。
- 指針 left 指向列表的左邊,right 指向列表的右邊
- 只要 left < right :
- 調用 partition 歸位函數,找出列表的分界線,將列表分為左右兩部分,分別調用quick_sort函數
快排每次排序問題規??s小一半, 在大多數場景下都是最快的,但是也有最壞的情況,那就數據是逆序的情況,比如9,8,7,…2,1 這時的時間復雜度變為O(n2)。為了避免這種情況,可以選擇不取第一個元素,而是隨機取。
實現二
下面給出第二種實現:
def parttions(data):mid = data[0]low = [x for x in data[1:] if x <= mid]high = [x for x in data[1:] if x > mid]return low, mid, highdef func(data):if len(data) <= 1:return datalow, mid, high = parttions(data)return func(low) + [mid] + func(high)li = [2, 4, 10, 5, 9, 6, 8, 1] print(func(li))堆排序
比較復雜,在NB三人組中是最慢的,先占個坑。
歸并排序
假設現在的列表分兩段有序,如何將其合成為一個有序列表
這種操作被稱為一次歸并
一次歸并過程
def merge(data, low, mid, high):i = lowj = mid + 1ltmp = []# 依次將兩段有序部分的元素作比較,將小的加入臨時列表中while i <= mid and j<= high:if data[i] <= data[j]:ltmp.append(data[i])i += 1else:ltmp.append(data[j])j += 1# 兩段有序部分的長度可能不一致,比較完后,其中一段可能有剩余元素未加入臨時列表中while i <= mid:ltmp.append(data[i])i += 1while j <= high:ltmp.append(data[j])j += 1data[low: high+1] = ltmpdata = [2,5,7,8,9,1,3,4,6] merge(data, 0, len(data)//2, len(data)-1) print(data)思路
- 分解:將列表越分越小,直至分成一個元素。
- 一個元素是有序的。
- 合并:將兩個有序列表歸并,列表越來越大。
小結
三種排序算法的時間復雜度都是O(nlogn)
一般情況下,就運行時間而言:
- 快速排序 < 歸并排序 < 堆排序
三種排序算法的缺點:
- 快速排序:極端情況下排序效率低
- 歸并排序:需要額外的內存開銷
- 堆排序:在快的排序算法中相對較慢
總結
以上是生活随笔為你收集整理的Python排序算法总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 线程同步时,哪些操作会释放锁?哪些操作不
- 下一篇: 十二. python面向对象主动调用其他