十個必知的排序算法|Python實例系列[1]

實例內容:

十個必知的排序算法具體代碼,並簡略的得知每種算法對於不一樣長度數列的排序時間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))+'秒')
相關文章
相關標籤/搜索