十個必知的排序算法具體代碼,並簡略的得知每種算法對於不一樣長度數列的排序時間python
1.冒泡排序2.選擇排序3.插入排序4.希爾排序5.歸併排序6.快速排序7.堆排序8.計數排序9.桶排序10.基數排序git
完整代碼和註釋以下github
# -*- coding: UTF-8 -*- #Space: https://github.com/Tri-x/exercise #Space: https://space.bilibili.com/187492698 #Author: Trix #Description: 十大經典排序算法 #Python的排序算法用的是Tim Sort 一種源自歸併排序和插入排序的穩定高效的排序算法 也許之後會單獨作一期視頻來介紹該算法 #也許之後會作一期視頻來介紹一些奇葩的排序算法 好比 睡眠排序、猴子排序、麪條排序、珠排序 from random import randint#隨機整數 from time import process_time#計時 nums_lists=[[]for n in range(4)]#隨機建立四個無序數列 用來粗略地測試每種排序算法的用時 for n in range(500):#數列長度爲500 nums_lists[0].append(randint(-400,400))#隨機範圍(-400,400) for n in range(1000): nums_lists[1].append(randint(-8000,8000)) for n in range(5001): nums_lists[2].append(randint(-2000,2000)) for n in range(10000): nums_lists[3].append(randint(-9000,9000)) #因爲建立的隨機數列太長,就不打印排序前和排序後的結果了 def bubble_sort(num_list):#冒泡排序 該算法名字的由來是由於越小的值會慢慢"浮"到數列的頂端 #循環遍歷數列,一次比較兩個相鄰的值,若是後者值大於前者值,就把他們的位置互相交換 for n in range(len(num_list)-1):#由於最後一次已經順序正確了因此循環次數-1 for m in range(len(num_list)-1-n): #循環每結束一次 這一次循環中最大的值會移動到上一次循環中最大值的前一位 數列中後面的值已經有序 因此次數-n if num_list[m]>num_list[m+1]:#大小比較 num_list[m+1],num_list[m]=num_list[m],num_list[m+1]#位置交換 return num_list def select_sort(num_list):#選擇排序 選擇最小值 #每次循環在數列中找到最小值,放到上一次循環找到的最小值的後一位,第一次放在首位 for n in range(len(num_list)-1):#由於最後一次已經順序正確了因此循環次數-1 min_index=n#設最小值爲索引爲n的值 for m in range(n,len(num_list)):#n值前已完成排序 if num_list[m]<num_list[min_index]:#最小值比較 num_list[min_index],num_list[m]=num_list[m],num_list[min_index]#位置交換 return num_list def insert_sort(num_list):#插入排序 插入數值 此版本爲插入排序最原始的版本 #每次循環把索引爲1的值到索引爲第n次的值做爲有序數列,把有序數列之外的每一個值插入有序數列的正確位置 for n in range(1,len(num_list)+1):#從第二個值開始 for m in range(n):#每一個值滾動的範圍是其索引值 if n<len(num_list):#防止n值超出數列長度 if num_list[n]<=num_list[m]:#比較大小 num_list[n],num_list[m]=num_list[m],num_list[n]#位置交換 return num_list def shell_sort(num_list):#希爾排序 插入排序的改進版 shell的音譯 #把整個無序數列按步長值分組,對每組數列分別進行插入排序,每次結束後減少步長,當步長值減少到1時對整個數列插入排序 gap=len(num_list)//2#gap爲步長值 //爲除法向下取值 while gap>=1:#控制步長範圍 #如下爲插入排序的內容 並對插入排序進行了改進 for n in range(gap,len(num_list)): while (n-gap)>=0:#控制比較範圍 if num_list[n]<num_list[n-gap]:#比較相鄰兩值大小 num_list[n],num_list[n-gap]=num_list[n-gap],num_list[n]#位置交換 n-=gap#把比較的兩個值的索引向前移 else:#超出索引範圍後結束該循環 break gap//=2#每次結束時減少步長值 return num_list def merge_sort(num_list):#歸併排序 二分法 遞歸與合併 #把無序數列遞歸地二分,直到只有兩個或一個值爲一組時,比較值的大小並排序,再返回這一組, #再遞歸地和其它組比較每一個值的大小並排序,再返回排序的結果,再遞歸地返回成有序數列 if len(num_list)<=1:#限制每組數列的最小長度 return num_list middle=len(num_list)//2#二分值 //表示除法向下取整 list_before=merge_sort(num_list[:middle])#數列前半部分爲list_before 後半部分爲list_after list_after=merge_sort(num_list[middle:])#這裏最好本身舉例一個有2或3個值的數列,初次碰見遞歸思想可能比較難理解 ''' 好比運行merge_sort([3,2,1]) [3,2,1]被分紅了list_before=merge_sort([3])list_after=merge_sort([2,1]) list_before=merge_sort([3]):merge_sort函數運行時,由於[3]長度等於1了 因此直接返回[3] 即list_before=[3] list_after=merge_sort([2,1]):merge_sort函數運行時,list_after=merge_sort 在list_after該函數內部又二分紅了list_before=merge_sort([2])list_after=merge_sort([1]) 與list_before=merge_sort([3])同理,在list_after該函數內部中,list_before=[2],list_after=[1] 接着在list_after該函數內部中,執行return merge_compare(list_before,list_after) 也就是比較[2]和[1]的大小後排序並返回 結果也就是list_after=[1,2] 再執行return merge_compare(list_before,list_after)也就是比較[3]和[1,2]大小後排序並返回 結果也就是return [1,2,3] 最後返回總列表 其它數列以此類推 ''' return merge_compare(list_before,list_after)#返回list_before,list_after比較的結果 def merge_compare(list_before,list_after):#比較兩個列表中的每一個值 result_list=[]#結果列表 before_index=after_index=0#前列表和後列表數值的索引發始值 while before_index<len(list_before) and after_index<len(list_after):#限制索引值 if list_before[before_index]<list_after[after_index]:#若是前列表的值小於後列表的值 result_list.append(list_before[before_index])#就把該值添加到結果列表 before_index+=1#結束後索引值+1 elif list_after[after_index]<=list_before[before_index]:#若是後列表的值小於等於前列表的值 result_list.append(list_after[after_index])#同理 after_index+=1 if before_index==len(list_before):#若是前列表的索引值已經等於前列表長度了 for n in list_after[after_index:]:#說明後列表的全部值大於前列表的最大值 result_list.append(n)#由於後列表在遞歸中已經完成排序了 直接追加後列表 elif after_index==len(list_after):#若是後列表的索引值已經等於後列表長度了 for n in list_before[before_index:]:#同理 result_list.append(n) return result_list#返回排序結果 def quick_sort(num_list):#調用快速排序的遞歸函數 quick_recursion(num_list,0,len(num_list)-1)#傳入數列,頭索引值和尾索引值 def quick_recursion(num_list,head_index,tail_index):#快速排序 遞歸思想 建議和歸併排序同樣舉例理解 #從數列中任意挑選一個值(該值稱爲基準-Pivot),設置一個頭索引值和尾索引值,用來限定掃描範圍,頭索引值向尾索引值移動 #掃描每一個值把全部小於基準值的數移到其前,全部大於基準的數移到其後(該步驟稱爲分區-Partition),再在每一個分區裏遞歸以上步驟 if head_index<tail_index:#限制索引值 pivot_index=quick_partition(num_list,head_index,tail_index)#獲取基準所在索引值 這一步同時也完成了分區 quick_recursion(num_list,head_index,pivot_index-1)#分別在先後分區範圍內進行快速排序 由於基準值處於前分區結尾和後分區開頭 quick_recursion(num_list,pivot_index+1,tail_index)#因此對於前分區的尾索引值-1 對於後分區的頭索引值+1 return num_list def quick_partition(num_list,head_index,tail_index):#比較大小並進行分區 pivot=num_list[tail_index]#爲了方便 選取該分區的結尾值做爲基準-Pivot exchange_index=head_index-1#設置交換索引值 頭索引值-1能把幾種狀況整合成一句代碼 for n in range(head_index,tail_index):#在兩個索引值範圍內 用基準值來區分每一個值 if num_list[n]<pivot:#若是掃描值小於基準值 exchange_index+=1#控制交換索引值 num_list[exchange_index],num_list[n]=num_list[n],num_list[exchange_index]#交換位置 num_list[exchange_index+1],num_list[tail_index]=num_list[tail_index],num_list[exchange_index+1]#注意有個exchange_index+1 ''' 好比quick_sort([1,4,5,6,3,4],0,5) 在[1,4,5,6,3,4]中第一次分區時基準值=4,exchange_index=-1 n=0時,掃描值1<基準值,exchange_index+1=0,再執行for循環時的下一行的交換位置的代碼 等效於掃描值1的位置不變 n=1時,掃描值4=基準值,exchange_index不變,exchange_index仍爲0 n=2時,掃描值5>基準值,exchange_index不變,exchange_index仍爲0 n=3時,掃描值6>基準值,exchange_index不變,exchange_index仍爲0 n=4時,掃描值3<基準值,exchange_index+1=1,再執行for循環時的下一行的交換位置的代碼 等效於掃描值4和掃描值3交換位置 此時for循環結束,執行for循環結束後的的下一行交換位置的代碼 等效於掃描值5和基準值4交換位置 第一次分區結束後咱們獲得了[1,3,4,6,4,5] partition函數返回了pivot_index=2 接着在quick_sort函數內部繼續運行quick_sort([1,3,4,6,4,5],0,2-1)和quick_sort([1,3,4,6,4,5],2+1,5) 即對於[1,3]和[6,4,5]進行partition函數,同理 不贅述 對於整個數列遞歸partition結束後 排序也就完成 其它數列以此類推 ''' return exchange_index+1#返回基準值所在的索引值 def heap_sort(num_list):#堆排序 徹底二叉樹 遞歸 #遞歸地把數列造成每一個父節點>每一個左右子節點的徹底二叉樹 #這樣每次構建後索引[0]爲最大值,在第n次結束,就把[0]和[-n]位置互換 在[0:-n-1]的範圍內進行下次的徹底二叉樹構建 list_len=len(num_list)#獲取數列長度 for n in range(list_len//2,-1,-1): #list_len//2爲父節點索引的規律 //表示除法向下取整 n∈[list_len//2,-1),n∈Z range(start,stop,step) step爲-1時爲從後往前遍歷 heapify(num_list,list_len,n)#數列徹底二叉樹的初始化 構建每一個父節點>每一個左右子節點的徹底二叉樹 for n in range(list_len-1,0,-1):#n∈[list_len-1,0),n∈Z num_list[0],num_list[n]=num_list[n],num_list[0]#對上一次構建完成的二叉樹 首尾值互換 list_len-=1#限定這一次構造徹底二叉樹的範圍 heapify(num_list,list_len,0)#因爲已經初始化完成 能夠直接從首項開始 在不包含上次最大值的範圍內進行構造徹底二叉樹 return num_list def heapify(num_list,list_len,parent_index):#構建父節點>左右子節點的徹底二叉樹 #第n排有n^(n-1),從左到右填充數值,能夠獲得父節點索引和左右子節點索引的規律 left_index=2*parent_index+1#對於每一個父節點索引 左節點索引的規律 right_index=left_index+1#對於每一個父節點索引 右節點索引的規律 max_index=parent_index#假設父節點比左右子節點值都大 默認最大值的索引爲父節點索引 if left_index<list_len and num_list[left_index]>num_list[max_index]:#若是在列表範圍內 左節點值>父節點值 max_index=left_index#父節點和左節點的索引互換 if right_index<list_len and num_list[right_index]>num_list[max_index]:#右節點同理 max_index=right_index if max_index!=parent_index:#若是如今的父節點索引不等於一開始的假設的最大值的索引 說明父節點和它的某一節點須要互換索引值 num_list[parent_index],num_list[max_index]=num_list[max_index],num_list[parent_index] heapify(num_list,list_len,max_index)#遞歸構建整個數列的徹底二叉樹 def count_sort(num_list):#計數排序 數數 #找出數列中最大值和最小值 建立min~max這麼多個0用來統計數列中每一個值出現的次數,再從最小值依次排放到最大值 max_num=max(num_list)#找到最大值 min_num=min(num_list)#找到最小值 neg_list=[]#負數數列 pos_list=[]#非負數數列 for num in num_list:#對負數和非負數分別處理 if num<0: neg_list.append(num)#向負數數列添加負數 if num>=0: pos_list.append(num)#向非負數數列添加非負數 if len(neg_list)!=0:#若是有負數 neg_counts_list=[0 for n in range(min_num,0)]#建立最小值這麼多個0來累計每一個負數出現的次數 for n in range(len(neg_list)):#對於負數數列中的每一個值 neg_counts_list[neg_list[n]]+=1 neg_index=0#初始排序索引爲0 for n in range(-len(neg_counts_list),0):#對於計數列表中的每一項 while neg_counts_list[n]>0:#不爲0就說明有計數 爲0說明沒有計數 neg_list[neg_index]=n#依次排放數值 neg_index+=1#每次排放後index+1 neg_counts_list[n]-=1#每次排放後數值所對應的計數-1 if len(pos_list)!=0:#若是有非負數 pos_counts_list=[0 for n in range(max_num+1)]#建立max+1這麼多個0來累計每一個非負數出現的次數 由於是以每一個值爲索引 因此要max+1個0 for n in range(len(pos_list)):#對於數列中的每一個非負值 pos_counts_list[pos_list[n]]+=1#計數列表索引和數列的值一一對應 pos_index=0#初始排序索引爲0 for n in range(len(pos_counts_list)):#對於計數列表中的每一項 while pos_counts_list[n]>0:#不爲0就說明有計數 爲0說明沒有計數 pos_list[pos_index]=n#依次排放數值 pos_index+=1#每次排放後index+1 pos_counts_list[n]-=1#每次排放後數值所對應的計數-1 result_list=neg_list+pos_list#負數數列和非負數數列結合 return result_list def bucket_sort(num_list):#桶排序 計數排序的改進版 #找出數列中最大值和最小值 建立min~max這麼多個桶用來統計數列中每一個值出現的次數,再從第一個桶傾倒到最後一個桶 bucket_list=[0 for n in range(max(num_list)-min(num_list)+1)]#建立min~max這麼多個桶 for n in range(len(num_list)):#對於當前值 bucket_list[num_list[n]-min(num_list)]+=1#添加到值對應的桶內 即對應桶計數+1 result_list=[]#結果列表 for n in range(len(bucket_list)):#對於每一個桶 if bucket_list[n]!=0:#若是桶內有數 result_list+=[n+min(num_list)]*bucket_list[n]#直接把桶裏的全部數倒出來 return result_list def radix_sort(num_list):#基數排序 比較位數上的值 #比較每一位上的數字大小 當每一位比較完成排序也就完成 pos_list=[]#負數數列 neg_list=[]#非負數數列 for num in num_list:#對負數和非負數分別處理 if num<0: neg_list.append(num) if num>=0: pos_list.append(num) if len(neg_list)!=0:#若是有負數 neg_num_digit=0#初始位數 while neg_num_digit<len(str(min(neg_list))):#限制條件 當數的位數小於最小值位數時 neg_values_lists=[[] for n in range(10)]#初始化數值列表 每位出現的值只多是0~9 因此建立10個數值列表 for neg_num in neg_list:#對於數列中的每一個數 neg_values_lists[int(neg_num/(10**neg_num_digit))%10].append(neg_num) #在第neg_num_digit+1次循環就比較該數的第n位上的數值 把該數添加到對應的數值列表 好比第一次循環就比較個位 第二次比較十位 neg_list.clear()#清空原數列用來填充有序的數列 for neg_value_list in neg_values_lists:#對於數值列表中的每一個列表 for neg_num in neg_value_list:#對於每一個列表中的每一個數 neg_list.append(neg_num) neg_num_digit+=1#比較下一位 if len(pos_list)!=0:#若是有非負數 pos_num_digit=0#初始位數 while pos_num_digit<len(str(max(pos_list))):#限制條件 當數的位數小於最大值位數時 pos_values_lists=[[] for n in range(10)]#初始化數值列表 每位出現的值只多是0~9 因此建立10個數值列表 for pos_num in pos_list:#對於數列中的每一個數 pos_values_lists[int(pos_num/(10**pos_num_digit))%10].append(pos_num) #在第pos_num_digit+1次循環就比較該數的第n位上的數值 把該數添加到對應的數值列表好比第一次循環就比較個位 第二次比較十位 pos_list.clear()#清空原數列用來填充有序的數列 for pos_value_list in pos_values_lists:#對於數值列表中的每一個列表 for pos_num in pos_value_list:#對於每一個列表中的每一個數 pos_list.append(pos_num) pos_num_digit+=1#比較下一位 result_list=neg_list+pos_list#結果列表 return result_list #記錄每一種排序算法對於不一樣長度無序數列的排序時間 sorts_time_dict={ bubble_sort:['Bubble Sort'],#冒泡排序 select_sort:['Select Sort'],#選擇排序 insert_sort:['Insert Sort'],#插入排序 shell_sort:['Shell Sort'],#希爾排序 merge_sort:['Merge Sort'],#歸併排序 quick_sort:['Quick Sort'],#快速排序 heap_sort:['Heap Sort'],#堆排序 count_sort:['Count Sort'],#計數排序 bucket_sort:['Bucket Sort'],#桶排序 radix_sort:['Radix Sort'],#基數排序 } for num_list in nums_lists:#因爲兩層for循環會使對數列進行快速排序時的遞歸太深 會引發python崩潰 單獨對每一個數列進行快速排序 print('正在對第'+str(nums_lists.index(num_list)+1)+'個'+'長爲'+str(len(num_list))+'的隨機數列執行Quick Sort算法') start_time=process_time()#開始計時 計時部分爲排序算法 quick_sort(num_list) end_time=process_time()#結束計時 sorts_time_dict[quick_sort].append(end_time-start_time)#記錄每種排序算法對不一樣長度數列的排序時間 單位爲秒 for num_list in nums_lists:#對每一個數列進行每一種排序算法 for func_sort,time_list in sorts_time_dict.items(): if func_sort!=quick_sort: print('正在對第'+str(nums_lists.index(num_list)+1)+'個'+'長爲'+str(len(num_list))+'的隨機數列執行'+time_list[0]+'算法') start_time=process_time()#開始計時 計時部分爲排序算法 func_sort(num_list.copy())#排序算法 .copy() 複製品 防止改變原數列 end_time=process_time()#結束計時 time_list.append(end_time-start_time)#記錄每種排序算法對不一樣長度數列的排序時間 單位爲秒 print() print('十種排序算法對於不一樣長度的隨機無序數列的排序時間結果以下:') print('{:20s}{:<15d}{:<15d}{:<15d}{:<15d}'.format('Length of Series:',500,1000,5001,10000))#格式化輸出 for time_list in sorts_time_dict.values():#每種算法 for sort_time in time_list:#每種算法的名稱和其處理每一個數列的時間 if not isinstance(sort_time,float):#若是use_time類型不爲float 即爲名稱 print('{:20s}'.format(sort_time+':'),end='') else: print('{:<15.4f}'.format(sort_time),end='')#左對齊 保留四位小數 print() print('單次隨機數列排序時間結果不表明全部') print() count_sort_list=[]#由於計數排序太快了,單首創建一個長度爲100000的數列來測試排序時間 for n in range(100000): count_sort_list.append(randint(-80000,100000)) start_time_count_sort=process_time()#開始計時 計時部分爲排序算法 count_sort(count_sort_list.copy()) end_time_count_sort=process_time()#結束計時 print('計數排序一個長度爲100000的隨機數列所用時間爲'+str(round(end_time_count_sort-start_time_count_sort,3))+'秒')