算法導論之全部排序算法的Python實現

最近一段時間學習了算法導論第二版書的第一部分和第二部分的內容,本身編寫了其中排序相關的幾乎所有算法,包括冒泡排序(bubble sort)、選擇排序( selection sort)、插入排序(insertion sort)、希爾排序(shell sort)、歸併排序(merge sort)、快速排序(quick sort)、計數排序(count sort)、基數排序(radix sort)、桶排序(bucket sort)、指望線性時間的第k個順序統計量選擇、最壞狀況線性時間的中位數選擇,並給出了習題8.4-5和9.3—8的算法思路及Python實現。其中大部分算法的基本原理不贅述,着重寫了我對桶排序的理解,並詳細分析了算法導論書中兩道題目。堆排序由於涉及二叉樹,會在下一部分數據結構的學習中實現。

初學Python,代碼雖能準確運行,可是語言的使用方面可能不簡潔或不許確,但願各位前輩指正。
python

一、冒泡排序(bubble sort) 

層層比較,不贅述,時間複雜度O(n^2)。算法

 1 def bubble_sort(a_list):
 2     for last in range(len(a_list)-1,0,-1):
 3         exchange=0
 4         for i in range(0,last):
 5             if a_list[i]>a_list[i+1]:
 6                 temp=a_list[i]
 7                 a_list[i]=a_list[i+1]
 8                 a_list[i+1]=temp
 9                 exchange +=1
10         if exchange==0:
11             break
12 
13 測試:
14 a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20]
15 bubble_sort(a_list)
16 print(a_list)
17 輸出:
18 [17, 20, 26, 31, 44, 54, 55, 77, 93]  
View Code 

二、選擇排序( selection sort) 
該算法依次找出最大、次大……的元素並置於應屬的位置,與冒泡排序的實現效果相同,只不過選擇排序只是在每一輪選擇結束後交換一次元素,減小了交換元素的次數。但比較次數沒有減小,故時間複雜度依然是O(n^2)。shell

 1 def selection_sort(a_list):
 2     for last in range(len(a_list)-1,0,-1):
 3         max=a_list[last]
 4         pos=last
 5         for i in range(last):
 6             if a_list[i]>max:
 7                 pos=i
 8                 max=a_list[i]
 9         a_list[pos]=a_list[last]
10         a_list[last]=max
11 
12 測試:     
13 a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20]
14 selection_sort(a_list)
15 print(a_list)
16 輸出:
17 [17, 20, 26, 31, 44, 54, 55, 77, 93]
View Code

三、插入排序(insertion sort) 
倒着比較,將新元素插入已排序列的正確位置,採用遞歸,時間複雜度仍爲O(n^2)。 數組

 1 def insertion_sort(a_list):
 2     last=len(a_list)-1
 3     if last>0:
 4         list1=insertion_sort(a_list[:last])
 5         i=len(list1)-1
 6         temp=a_list[last]
 7         while i>=0:
 8             if temp<=list1[i]:
 9                 i-=1
10             else:
11                 break
12         for pos in range(last,i+1,-1):
13             a_list[pos]=list1[pos-1]
14         a_list[i+1]=temp
15         a_list[:i+1]=list1[:i+1]
16     return a_list  
17  
18 測試:
19 a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20]
20 print insertion_sort(a_list)
21 輸出:
22 [17, 20, 26, 31, 44, 54, 55, 77, 93]
View Code

四、希爾排序(shell sort)

選取合適的gap值,將待排序元素數組中相隔gap個位置的元素歸爲一組,總共分紅gap個組,組內排序(用插入排序實現),縮小gap,遞歸調用自身得到更新的排序結果。數據結構

 

 1 def shell_sort(a_list,gap):
 2     if gap>0:
 3         for i in range(0,gap):
 4             for j in range(i+gap,len(a_list),gap):
 5                 k=j-gap
 6                 temp=a_list[j]
 7                 while a_list[k]>temp and k>=i:
 8                     a_list[k+gap]=a_list[k]
 9                     k-=gap
10                 a_list[k+gap]=temp
11         shell_sort(a_list,gap//2)
12 測試:
13 a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20]
14 shell_sort(a_list,4)
15 print(a_list)
16 輸出:
17 [17, 20, 26, 31, 44, 54, 55, 77, 93]
View Code

五、歸併排序(merge sort)

把待排序數組分紅兩部分分別排序,以遞歸調用自身實現,對已排好的兩部分比較合併。app

 

 1 def merge_sort(a_list):
 2       if len(a_list)>1:
 3           split=len(a_list)//2
 4           left_list=merge_sort(a_list[0:split])
 5           right_list=merge_sort(a_list[split:len(a_list)])
 6           i=0
 7           j=0
 8           k=0
 9           while i <len(left_list) and j <len(right_list):
10                if left_list[i]<right_list[j]:
11                    a_list[k]=left_list[i]
12                    i+=1
13                    k+=1
14                else:
15                    a_list[k]=right_list[j]
16                    j+=1
17                    k+=1
18            if i==len(left_list):
19                a_list[k:]=right_list[j:]
20            if j==len(right_list):
21                a_list[k:]=left_list[i:]
22        return a_list
23  測試:
24 a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20]
25 print merge_sort(a_list)
26 輸出:
27 [17, 20, 26, 31, 44, 54, 55, 77, 93]
View Code

六、快速排序(quick sort) dom

採用分治思想,每次選取list中的第一個元素做爲主元(pivot),經過比較找到其應屬的位置,將原list劃分爲左右兩個list,分別遞歸調用自身。ide

 

 1   def quick_sort(a_list):
 2       if len(a_list)>1:
 3           pivot=a_list[0]
 4           i=0
 5           j=1
 6           while j<len(a_list):
 7               if a_list[j]<pivot:
 8                   i+=1
 9                   temp=a_list[i]
10                   a_list[i]=a_list[j]
11                   a_list[j]=temp
12               j+=1
13           a_list[0]=a_list[i]
14           a_list[i]=pivot
15           a_list=quick_sort(a_list[:i])+[a_list[i]]+quick_sort(a_list[i+1:])
16       return a_list
17  
18  測試:
19  a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20]
20  print quick_sort(a_list)
21  輸出:
22  [17, 20, 26, 31, 44, 54, 55, 77, 93]
View Code

 

下面附上隨機化快排中隨機劃分的部分Python代碼:函數

 1 import random
 2 def random_partition(a_list):
 3     p=random.randint(0,len(a_list)-1)
 4     pivot=a_list[p]
 5     a_list[p]=a_list[0]
 6     a_list[0]=pivot
 7     i=0
 8     j=1
 9     while j<len(a_list):
10         if a_list[j]<pivot:
11             i+=1
12             a_list[i],a_list[j]=a_list[j],a_list[i]
13         j+=1
14     a_list[0],a_list[i]=a_list[i],a_list[0]
15     return (pivot,i+1,a_list[:i],a_list[i+1:])
16 
17 測試: 
18 a_list=[2,4,13,5,8,6,14,7]
19 pivot,i,list_left,list_right=random_partition(a_list)
20 print (pivot,i,list_left,list_right)
21 輸出:
22 (13, 7, [7, 4, 2, 5, 8, 6], [14])
View Code

七、計數排序(count sort) 
不是比較排序類算法,沒有Ω(nlogn)時間複雜度限制,但要求待排序元素爲非負整數。選擇合適的待排序數的數值上限k,建立大小爲k的list C,待排元素的數值做爲C的index,在C[index]裏記錄該元素出現的次數,把C中某一index前的全部元素求和即爲index對應的待排元素應屬的位置。線性時間複雜度。  學習

 

 1 def count_sort(a_list,k):
 2     B=[None]*len(a_list)  # ordered list
 3     C=[0]*k     # helper list to count
 4     for i in a_list:
 5         C[i]+=1
 6     for j in range(1,k):
 7         C[j]=C[j-1]+C[j]
 8     for p in range(len(a_list)-1,-1,-1):
 9         B[C[a_list[p]]-1]=a_list[p]      # the index of a list starts from 0, not 1
10         C[a_list[p]]-=1
11     return B
12 
13 a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20, 26, 93]
14 print count_sort(a_list,100)
View Code

 

八、基數排序(radix sort) 
基本思想是,對於n個r進制k位數進行排序,採用穩定排序算法,先依低位排序再依高位排序,以此保證依高位排序時,相同高位的數可保持其依低位已排好的次序。時間複雜度爲O(k(n+r))。 
下面代碼以十進制數的排序爲例。可是代碼中的兩個問題我沒有想明白,如Question1和Question2描述,但願各位前輩指點~

 

 1 def radix_sort(a_list,k):
 2     B=[0]*len(a_list)
 3     for j in range(1,k+1):
 4         C=[0]*10            # helper list to count
 5         # A=[(i%10**j)//(10**(j-1))  for i in a_list]  # Question1: why is this way wrong to def A?
 6         A=[]
 7         for t in a_list:
 8             A.append((t%10**j)//(10**(j-1)))   
 9         for i in A:
10             C[i]+=1
11         for q in range(1,10):
12             C[q]=C[q-1]+C[q]
13         for p in range(len(A)-1,-1,-1):
14             B[C[A[p]]-1]=a_list[p]      # the index of a list starts from 0, not 1
15             C[A[p]]-=1
16         a_list=B
17         B=[0]*len(a_list)           # Question2: why must B set to be 0 here ????
18     return a_list
19 
20 a_list = [54, 26, 93, 17, 77, 31, 44, 55, 20, 26, 93]
21 print radix_sort(a_list,2)
View Code

 

九、桶排序(bucket sort) 
桶排序的原始定義針對(0,1)範圍內均勻分佈的數,下面代碼也是針對這種狀況編寫的。

 

 1 from insertion_sort import insertion_sort
 2 import random
 3 def bucket_sort(a_list):
 4     bucket=[[] for i in range(len(a_list))]   # [[]]*len(list) is shallow copy, wrong
 5     for i in a_list:
 6         if i== 1:
 7             i= 0.99
 8         bucket[int(i*len(a_list))].append(i)
 9     a_list=[]
10     for j in bucket:
11         a_list=a_list+insertion_sort(j)
12     return a_list
13 測試: 
14 a_list=[random.uniform(0,1) for i in range(13)]
15 print 'before sort: \n %s '% a_list
16 print 'after sort: \n %s ' % bucket_sort(a_list)
17 輸出:
18 before sort: 
19  [0.6017131497978297, 0.5257088814737517, 0.5308222308002468, 0.9233128021687357, 0.8701655171999804, 0.5526799446055798, 0.11875967581091884, 0.9175286740805743, 0.8919451407674414, 0.5193946279480275, 0.8656422841387905, 0.7077658852432671, 0.7646975175708084] 
20 after sort: 
21  [0.11875967581091884, 0.5193946279480275, 0.5257088814737517, 0.5308222308002468, 0.5526799446055798, 0.6017131497978297, 0.7077658852432671, 0.7646975175708084, 0.8656422841387905, 0.8701655171999804, 0.8919451407674414, 0.9175286740805743, 0.9233128021687357] 
View Code

 

桶排序算法的核心思想是經過合理設置桶,來知足每一個桶所含元素數的平方和的指望與輸入規模成線性關係,達到線性指望時間複雜度。 
直觀上理解,我認爲就是對分佈較密的數據區域多劃分些桶,對數據分佈稀疏的區域少劃分桶,以達到平均每一個桶內分配常數個元素(原始定義裏分紅n個桶時每一個桶平均分配1個元素)。對通常狀況可採起的辦法是,針對輸入序列超出(0,1)範圍的狀況,可經過歸一操做保證待排成數據在(0,1)範圍內;針對輸入序列非均勻分佈的狀況,可經過某種映射 f 將待排序列映射爲均勻分佈。 
以算法導論第二版的習題8.4-5來講明使用該算法的更通常的狀況。

Ex8.4-5 一個隨機變量X的機率分佈函數P(x)定義爲 P(x)=Pr{X<=x}。假設n個隨機變量X1,X2,……,Xn 符合一個連續機率分佈函數P,它能夠在O(1)時間內計算。說明如何在線性指望時間內排序這n個數。 

若考慮採用桶排序算法,則桶的大小要依據X的分佈來設置,設n個數分配n個桶,P(x)爲[0,1]上的增函數,爲了使每一個桶中平均分配一個元素,只需將P(x) n均分(即[0,1] n均分),函數值對應的自變量即爲桶劃分的界。對於第i個桶,其範圍( P1((i1)/n),P1(i/n)) 。因爲反函數運算不便,可直接將xi映射爲P(xi)分析。則xi將被放入第P(xi)n個桶中。

十、指望線性時間的第k個順序統計量選擇 
隨機化選擇主元,分治處理,返回第k個順序統計量。代碼中base的設置是爲了更新當前處理list中的元素在原a_list中的位置。  

 

 1 import random
 2 def random_partition(a_list):
 3     p=random.randint(0,len(a_list)-1)
 4     pivot=a_list[p]
 5     a_list[p]=a_list[0]
 6     a_list[0]=pivot
 7     i=0
 8     j=1
 9     while j<len(a_list):
10         if a_list[j]<pivot:
11             i+=1
12             a_list[i],a_list[j]=a_list[j],a_list[i]
13         j+=1
14     a_list[0],a_list[i]=a_list[i],a_list[0]
15     return (pivot,i+1,a_list[:i],a_list[i+1:])
16 
17 def random_select(a_list,k):
18     base =0
19     list_next=a_list
20     while list_next !=[]:
21         pivot,i,list_left,list_right= random_partition(list_next)
22         i=base+i
23         if i==k:
24             return pivot
25         elif i<k:
26             list_next=list_right
27             base =i
28         else:
29             list_next=list_left
30     return False
31 
32 測試: 
33 a_list=[2,4,13,5,8,6,14,7]
34 print random_select(a_list,5)
35 輸出:
36 7   
View Code

 

十一、最壞狀況線性時間的中位數選擇 
隨機化算法能夠得到良好的指望時間複雜度,卻對最壞狀況無能爲力。該求取中位數(偶數指下中位數)的算法優化了主元的選擇方法,將n個元素以每組5個元素分紅n/5組,將每組的中位數組成新的list,遞歸調用自身,可保證獲得的新list的中位數位於該n位有序序列的1/4~3/4位置範圍(還可更精確),杜絕了隨機化算法可能引發的最差狀況,保證了線性時間複雜度。 
在下面的partition函數中,爲了獲得主元的index,註釋行用了list.index()函數,可是該方法時間複雜度O(n),怎樣修改才能使partition函數直接傳入主元的index參數呢?求大神幫忙~

 

 1 def insertion_sort(a_list):     
 2     last=len(a_list)-1
 3     if last>0:
 4         a_list1=insertion_sort(a_list[:last])
 5         i=len(a_list1)-1
 6         temp=a_list[last]
 7         while i>=0:
 8             if temp<=a_list1[i]:
 9                 i-=1
10             else:
11                 break
12         for pos in range(last,i+1,-1):
13             a_list[pos]=a_list1[pos-1]
14         a_list[i+1]=temp
15         a_list[:i+1]=a_list1[:i+1]
16     return a_list   
17 
18 def partition(a_list,key):  
19     p=a_list.index(key)      #Question: such an enbarrasing thing to find the key from a_list, how to do?
20     pivot=a_list[p]
21     a_list[p]=a_list[0]
22     a_list[0]=pivot
23     i=0
24     j=1
25     while j<len(a_list):
26         if a_list[j]<pivot:
27             i+=1
28             a_list[i],a_list[j]=a_list[j],a_list[i]
29         j+=1
30     a_list[0],a_list[i]=a_list[i],a_list[0]
31     return (i+1,a_list[:i],a_list[i+1:])
32 
33 
34 def mid_select(a_list):
35     if len(a_list)>5:
36         k=(len(a_list)+1)//2
37         a_list_next=a_list[:]
38         base=0
39         while a_list_next !=[]:
40             a_list_mid =[]
41             for i in range(len(a_list_next)//5):  
42                 a_list_part=a_list_next[5*i:5*(i+1)]
43                 a_list_order=insertion_sort(a_list_part)
44                 mid_part=a_list_order[2]
45                 a_list_mid.append(mid_part)
46             if len(a_list_next)%5 !=0:
47                 a_list_part=a_list_next[5*(len(a_list_next)//5):]
48                 a_list_order=insertion_sort(a_list_part)
49                 mid_part =a_list_order[(len(a_list_order)+1)//2-1]
50                 a_list_mid.append(mid_part)
51             mid= mid_select(a_list_mid)
52             i,a_list_left,a_list_right = partition(a_list_next,mid)  
53 
54             i =base +i                      # revise the index of mid in the original a_list
55             if i==k:
56                 return mid 
57             elif i <k:
58                 a_list_next=a_list_right
59                 base =i
60             else:
61                 a_list_next=a_list_left
62         return False
63     else:
64         a_list_order=insertion_sort(a_list)
65         return a_list_order[(len(a_list)+1)//2-1]
66 
67 
68 a_list=[2,54,26,4,13,5,8,14,7,93,6,17,77,31,44,55,20,26,93]
69 
70 print (mid_select(a_list))
71  
View Code

 

 

算法導論第二版書的習題9.3-8也用到了相似的思想。下分析。 
EX 9.3-8 設x[1..n]和Y[1..n]爲兩個數組,每一個都包含n個已排好序的數。給出一個求數組X和Y中全部2n個元素的中位數的O(logn)時間的算法。 
看到O(logn)時間複雜度,首先想到分治思想的T(n)=T(n/2)+O(1)模式,那麼如何才能經過常數次比較(最好一次),直接排除一部分不多是中位數的元素呢(這部分元素的數量與n成線性關係便可)?首先最直觀的考慮這部分元素數量爲n/2,因而想到比較X和Y的中位數(偶數指下中位數),索引爲mid,若是X[mid]=Y[mid],則X[mid](Y[mid])就是該2n個元素的中位數;若是X[mid]<Y[mid],則若將X和Y這2n個元素排序,Y[mid]必然排在第n個元素以後(反證法可得),同理 X[mid]必然排在前n個元素,所以 X[mid]以前的元素和Y[mid]以後的元素能夠直接扔掉,由於它們確定不是中位數。因而保留一半的元素遞歸調用自身。算法思路理清。 
可是還有一個問題,當n爲偶數時,取的下中位數進行比較,若兩者不等,則下一次處理的兩數組長度不等,遇到困難。所以n爲偶數時,我對捨棄後一半元素的數組保留至其中位數的後一位,保證了算法的正常遞歸。代碼以下。

 

 1 import random
 2 def mid_array_938(list_a,list_b):
 3     if len(list_a) >2:  
 4         len_mid=(len(list_a)+1)//2
 5         mid_a= list_a[len_mid-1]
 6         mid_b= list_b[len_mid-1]
 7         if mid_a == mid_b:
 8             return mid_a
 9         elif mid_a< mid_b:
10             if len(list_a)%2==0:
11                 return mid_array_938(list_a[len_mid-1:],list_b[:len_mid+1])
12             else:
13                 return mid_array_938(list_a[len_mid-1:],list_b[:len_mid])
14         else:
15             if len(list_a)%2==0:
16                 return mid_array_938(list_a[:len_mid+1],list_b[len_mid-1:])
17             else:
18                 return mid_array_938(list_a[:len_mid],list_b[len_mid-1:])
19     else:
20         list_ab=list_a + list_b
21         list_ab=insertion_sort(list_ab)
22         return list_ab[(len(list_ab)+1)//2-1]
23 
24 
25 def insertion_sort(a_list):
26     last=len(a_list)-1
27     if last>0:
28         list1=insertion_sort(a_list[:last])
29         i=len(list1)-1
30         temp=a_list[last]
31         while i>=0:
32             if temp<=list1[i]:
33                 i-=1
34             else:
35                 break
36         for pos in range(last,i+1,-1):
37             a_list[pos]=list1[pos-1]
38         a_list[i+1]=temp
39         a_list[:i+1]=list1[:i+1]
40     return a_list   
41 
42 測試:
43 list_a=[1,4,6,9,13,19,35,52]
44 list_b=[2,3,15,18,19,20,43,45]
45 print mid_array_938(list_a,list_b)      
46 輸出:
47 15
View Code
相關文章
相關標籤/搜索