[Python] 常見的排序與搜索算法


說明:

  本文主要使用python實現常見的排序與搜索算法:冒泡排序選擇排序插入排序希爾排序快速排序歸並排序以及二分查找等。

  對算法的基本思想作簡要說明,只要理解了基本的思想,與實現語言無關。

  本文主要參考網絡文章,僅供學習。

  開發環境:Python3.5

  

 

一、冒泡排序

  冒泡排序(Bubble Sort)算是一種比較常見的排序算法,重復遍歷要排序的數列,一次比較相鄰的兩個元素,如果順序錯誤即互相交換位置,遍歷直到無需再交換,則此時數列已經排序完成。此算法名字由來:因為越小的元素(升序)經由交換慢慢 “浮”到數列的頂端。

  1、冒泡排序的基本思想(運作原理):

      · 比較相鄰的元素,如果第一個比第二個大(升序),就交換它們兩個。

    · 對每一對相鄰的元素作同樣的工作,從開始第一對到結尾最后一對,這一步做完后,最后的元素會是最大的數。

    · 針對所有的元素重復以上的步驟,除了最后一個(倒數第二個與其已作比較)。

    · 持續每次對越來越少的元素重復上面的步驟,知道沒有任何一對數字需要比較。

    

    交換過程示意圖(第一次)(來自網絡):

    

 

    

  2、python實現過程:

    這里提供兩種實現過程,第二個實現過程為上面示意圖所示。  

 1 # coding=utf-8
 2 
 3 
 4 def bubble_sort(ls):
 5     """冒泡排序"""
 6     print("before: ", ls)
 7     for i in range(0, len(ls) - 1):
 8         # i = [0, 1, ...., len(ls) - 2],每次比較的第一個數的下標
 9         # j = [i + 1, i + 2, ..., len(ls) - 1],每次比較的第二個數的下標
10         for j in range(i + 1, len(ls)):
11             if ls[i] > ls[j]:
12                 ls[i], ls[j] = ls[j], ls[i]
13         print(ls)
14     print("after: ", ls)
15 
16 
17 def bubble_sort2(ls):
18     """冒泡排序"""
19     print("before:", ls)
20     for j in range(len(ls) - 1, 0, -1):
21         # j = [len(ls) - 1, len(ls) - 2, ..., 1], 每次需要比較的次數
22         # i = [0, 1, 2, ..., j - 1],需要比較的下標
23         for i in range(j):
24             if ls[i] > ls[i + 1]:
25                 ls[i], ls[i + 1] = ls[i + 1], ls[i]
26         print(ls)
27     print("after:", ls)
28 
29 
30 if __name__ == "__main__":
31     ls1 = [54, 26, 93, 17, 77, 31, 44, 55, 20]
32     ls2 = [54, 26, 93, 17, 77, 31, 44, 55, 20]
33 
34     bubble_sort(ls1)
35     print("-"*50)
36     bubble_sort2(ls2)

    

    執行結果(分割線上為 bubble_sort1() 的執行結果,分割線下為 bubble_sort2() 的執行結果):

    

  

  3、時間復雜度:

    最優時間復雜度:O(n)(表示遍歷一次發現沒有任何可以交換的元素排序結束,在內循環可以做一個標識判斷,如果首次循環沒有任何交換,則跳出)

    最壞復雜度:O(n2)

    穩定性:穩定

 

 

二、選擇排序

  選擇排序( Selection Sort )是一種簡單直觀的排序算法,基本原理:首先在未排序中找到最小(大)的元素,存放在排序序列的起始位置,然后在從剩余未排序元素中繼續尋找最小(大)元素,然后放到已排序的末尾,一次類推,直到所有元素均排序完畢

  選擇排序的主要優點與數據移動有關。如果某個元素位於正確的最終位置上,則它不會被移動。選擇排序每次交換一對元素,它們當中至少有一個唄移到其最終位置上,因此對 n 個元素的表進行排序共進行至多 n - 1次交換。在所有完成依靠交換去移動元素的排序方法中,選擇排序屬於非常好的一種。

  

  1、排序過程,圖示(圖來源網絡):

    假設右邊為已排序,然后從左邊未排序中選擇一個最大值,放到右邊來。

    

      

   

   

  2、python實現過程:

    這里代碼的思想為:假設左邊為已排序,右邊為排序。   

 1 # coding=utf-8
 2 
 3 
 4 def selection_sort(ls):
 5     """選擇排序"""
 6     # 假設左邊為已排序,右邊為未排序
 7 
 8     print("before:", ls)
 9     for i in range(0, len(ls) - 1):
10         # i = [0, 1, 2,,, len(ls) - 2]
11         # j = [i + 1, i + 2,,, len(ls) - 1]
12         min_index = i
13         for j in range(i + 1, len(ls)):
14             if ls[j] < ls[min_index]:
15                 min_index = j
16 
17         if min_index != i:
18             ls[min_index], ls[i] = ls[i], ls[min_index]
19         print(ls)
20     print("after:", ls)
21 
22 
23 if __name__ == "__main__":
24     ls = [54, 26, 93, 17, 77, 31, 44, 55, 20]
25 
26     selection_sort(ls)

    

  3、時間復雜度:

    最優時間復雜度:O(n2)

    最壞時間復雜度:O(n2)

    穩定性:不穩定(考慮升序每次選擇最大的情況)

    

 

三、插入排序

  插入排序(Insert ion Sort),其工作原理:通過構建有序序列,對於未排序數據中從后向前掃描,找到相應位置並插入。插入排序在實現上,在從后面向前掃描過程中,需要反復把已排序元素逐步向后挪位,為最新元素提供插入空間。

  

  1、排序過程,圖示意(圖片來自網絡):

    

 

     

    

  2、python實現過程:

    從小標為 1 開始,往 0 遍歷,比較交換。

 1 # coding=utf-8
 2 
 3 
 4 def insert_sort(ls):
 5     """插入排序"""
 6     # 假設左邊已排序,右邊為未排序,每次從右邊取一個數,遍歷已排序的子序列,直到找到次數的位置。
 7     print("before: ", ls)
 8     for j in range(1, len(ls)):
 9         for i in range(j, 0, - 1):
10             if ls[i] < ls[i - 1]:
11                 ls[i], ls[i - 1] = ls[i - 1], ls[i]
12         print(ls)
13     print("after: ", ls)
14 
15 
16 if __name__ == "__main__":
17     ls = [54, 26, 93, 17, 77, 31, 44, 55, 20]
18 
19     insert_sort(ls)

    

    執行結果:

    

    

  3、時間復雜度:

    最優時間復雜度:O(n)(升序序列,序列已經處於升序狀態)

    最壞時間復雜度:O(n2)

    穩定性:穩定

 

 

四、希爾排序

  希爾排序(Shell Sort)是插入排序的一種。也稱為增量排序,是直接插入排序算法的一種更高效的改進版本。希爾排序是把紀錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至 1 時,整個序列恰被分成一組,算法便終止。

  

  1、希爾排序過程:

    基本思想:將數組列在一個表中並對列分別進行插入排序,重復這過程,不過每次用更長的列(步長更長了,列數更少了)來進行。最后整個表就只有一列了。將數組轉換至表識為了更好理解這算法,算法本身還是使用數組進行排序。

    例如,假設有這樣一組數[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我們以步長為5開始進行排序,我們可以通過將這列表放在有5列的表中進行更好的描述算法,這樣它們就應該看起來是這樣(豎着的元素是步長組成):

    

    最后以 1 步長進行排序(此時就是簡單的插入排序)

     

    

  2、python實現過程:

    實現過程基本和插入排序類似,只是插入排序的 step 固定為 1,而希爾排序的 step 會變化直至 為 1。

 1 # coding=utf-8
 2 
 3 
 4 def shell_sort(ls):
 5     """希爾排序"""
 6     print("before: ", ls)
 7 
 8     step = len(ls) // 2     # 初始步長
 9 
10     while step > 0:
11         # 插入排序
12         for j in range(step, len(ls)):
13             for i in range(j, 0, - step):
14                 if ls[i] < ls[i - step]:
15                     ls[i], ls[i - step] = ls[i - step], ls[i]
16         step //= 2
17         print(ls)
18     print("shell_sort :", ls)
19 
20 
21 if __name__ == "__main__":
22     ls = [54, 26, 93, 17, 77, 31, 44, 55, 20]
23 
24     shell_sort(ls)

 

    執行結果:

    

  

  3、時間復雜度:

    最優時間復雜度:根據步長序列的不同而不同。

    最壞時間復雜度:O(n2)。

    穩定性:不穩定。

    

 

五、快速排序

  快速排序(Quick Sort),又稱為划分交換排序(Partition-exchange Sort),通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要笑,然后在按此方法對這兩部分數據分別進行快速排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。

  

  1、快速排序過程:

    ① 從數列中選出一個元素,稱為“基准”(pivot)。

    ② 重新排序數列,所有元素比基准值小的擺放在基准前面,所有元素比基准大的擺在基准的后面(相同的數,可以放到任意一邊)。在這個分區結束之后,該基准就處於數列的中間位置。這個稱為分區(partition)操作。

    ③ 遞歸(recursive)把小於基准值元素子數列和大於基准值元素的子數列排序。

    遞歸的的最底部情形,是數列的大小是零或一,也就是永遠都已經被排序好了。

 

    

     

  

  2、python實現過程:

    這里提供兩種快速排序的方式,基本思想一樣,第一種是在原有列表進行操作(通過游標進行),第二種則是新建左右子列表進行存儲。

 1 # coding=utf-8
 2 
 3 
 4 def quick_sort1(ls, start, end):
 5     """
 6         快速排序-1
 7         low 和 high 分別指向序列的頭和尾
 8         low += 1, high -= 1
 9         在low自增過程中,直到找到大於 mid_val 的下標
10         在high自增減過程中,直到找到小於 mid_val 的小標
11         然后將這兩個值交換
12     """
13 
14     # 遞歸退出條件
15     if start >= end:
16         return
17 
18     low = start
19     high = end
20     mid_val = ls[low]
21 
22     while low < high:
23         while low < high and ls[high] > mid_val:
24             high -= 1
25         ls[low] = ls[high]
26 
27         while low < high and ls[low] < mid_val:
28             low += 1
29         ls[high] = ls[low]
30 
31     ls[low] = mid_val
32 
33     print("mid:", mid_val, ls)
34 
35     quick_sort1(ls, start, low - 1)  # 左邊的子序列
36     quick_sort1(ls, low + 1, end)    # 右邊的子序列
37 
38     return ls
39 
40 
41 def quick_sort2(ls):
42     """快速排序-2"""
43 
44     # 遞歸退出條件
45     if len(ls) <= 1:
46         return ls
47 
48     left_ls, right_ls = [],[]
49     mid_val = ls[0]
50     for i in range(1, len(ls)):
51         if ls[i] < mid_val:
52             left_ls.append(ls[i])
53         else:
54             right_ls.append(ls[i])
55 
56     print(left_ls, mid_val, right_ls)
57 
58     # 遞歸調用,左右子列表
59     left_res = quick_sort2(left_ls)
60     right_res = quick_sort2(right_ls)
61 
62     return left_res + [mid_val] + right_res
63 
64 
65 
66 if __name__ == "__main__":
67     ls1 = [54, 26, 93, 17, 77, 31, 44, 55, 20]
68     ls2 = [54, 26, 93, 17, 77, 31, 44, 55, 20]
69 
70     print("before:", ls1)
71     res1 = quick_sort1(ls1, 0, len(ls1) - 1)
72     print("quick sort1: ", res1)
73 
74     print("-"*50)
75     print("before: ", ls2)
76     res2 = quick_sort2(ls2)
77     print("quick sort2:", res2)

 

    執行結果:

      

    

  3、時間復雜度:

    最優時間復雜度:O(nlogn)

    最壞時間復雜度:O(n2)

    穩定性:不穩定

    

 

 

六、歸並排序

  歸並排序是采用分治法的一種非常典型的應用。歸並排序的思想就是先遞歸分解數組,再合並數組

  將數組分解最小之后,然后合並兩個有序數組,基本思路:比較兩個數組的最前面的數,誰小就先取誰,取了后相應的指針就往后移一位。然后再比較,直到一個數組為空,最后把另外一個數組的剩余部分復制過來即可。

 

  1、歸並排序過程,圖示:

    

    

  2、python實現過程:

    先把序列拆分成 left_ls 和 right_ls ,然后再合並成一個res。

 1 # coding=utf-8
 2 
 3 
 4 def merge_sort(ls):
 5     """歸並排序"""
 6     n = len(ls)
 7 
 8     # 遞歸退出條件
 9     if n <= 1:
10         return ls
11 
12     mid = n // 2
13 
14     # 1、拆分子序列
15     left_ls = merge_sort(ls[:mid])
16     right_ls = merge_sort(ls[mid:])
17 
18     # 2、合並子序列:left_ls 和 right_ls
19     left_point, right_point = 0, 0
20     res = []
21 
22     # 當left_ls或者right_ls 結束,就會退出 while,而另外一個則可能未結束,所有后面需要 res +=
23     while left_point < len(left_ls) and right_point < len(right_ls):
24         # 比較兩個子序列,小的先加入到 res[]
25         if left_ls[left_point] < right_ls[right_point]:
26             res.append(left_ls[left_point])
27             left_point += 1
28         else:
29             res.append(right_ls[right_point])
30             right_point += 1
31     print("res:", res)
32 
33     res += left_ls[left_point:]
34     res += right_ls[right_point:]
35 
36     return res
37 
38 
39 if __name__ == "__main__":
40     ls = [54, 26, 93, 17, 77, 31, 44, 55, 20]
41 
42     print("before: ", ls)
43     res = merge_sort(ls)
44     print("merge sort: ", res)

    

    執行結果:

      

 

  3、時間復雜度:

    最優時間復雜度:O(nlogn)

    最壞時間復雜度:O(nlogn)

    穩定性:穩定

 

 

七、二分查找

  二分查找又稱折半查找,優點是比較次數少,查找速度快,平均性能好,其缺點是要求待查找表為有序表,且插入刪除困難。因此,折半查找方法適用於不經常變動而查找頻繁的有序列表。

  基本思想:假設表中元素是按升序排序,將表中間位置記錄關鍵字與查找關鍵字比較,如果兩者相等,則查找成功,否則利用中間位置記錄分成前、后兩個子表,如果中間位置記錄的關鍵字大於查找關鍵字,則進一步查找前一子表,否則進一步查找后一個子表。重復以上過程,知道找到滿足條件的記錄,使查找成功,或直到子表不存在為止,此時查找不成功。

  

  1、二分查找過程,圖示(圖片來源網絡):

    

    

  2、python實現過程:

    這里主要兩種實現方式,一種遞歸,另一種非遞歸。

 1 # coding=utf-8
 2 
 3 
 4 def binary_search_recursion(ls, item):
 5     """二分查找---遞歸"""
 6     n = len(ls)
 7     if n < 1:
 8         return False
 9 
10     mid = n // 2
11 
12     # 與中間值比較
13     if item == ls[mid]:
14         return True
15 
16     # 去左邊子序列查找
17     elif item < ls[mid]:
18         return binary_search_recursion(ls[:mid], item)
19 
20     # 去右邊子序列查找
21     else:
22         return binary_search_recursion(ls[mid + 1:], item)
23 
24 
25 def binary_search(ls, item):
26     """二分查找---非遞歸"""
27     n = len(ls)
28     start = 0
29     end = n - 1
30 
31     while start <= end:
32         mid = (start + end) // 2
33 
34         if item == ls[mid]:
35             return True
36         elif item < ls[mid]:
37             end = mid - 1
38         else:
39             start = mid + 1
40     return False
41 
42 
43 if __name__ == "__main__":
44     ls = [17, 20, 26, 31, 44, 54, 55, 77, 93]
45 
46     num = int(input("請輸入一個整數:"))
47     res = binary_search(ls, num)
48     print("查找結果:", res)

 

 

八、完整代碼

  1 # coding=utf-8
  2 
  3 
  4 def bubble_sort(ls):
  5     """冒泡排序"""
  6     print("before: ", ls)
  7     for i in range(0, len(ls) - 1):
  8         # i = [0, 1, ...., len(ls) - 2],每次比較的第一個數的下標
  9         # j = [i + 1, i + 2, ..., len(ls) - 1],每次比較的第二個數的下標
 10         for j in range(i + 1, len(ls)):
 11             if ls[i] > ls[j]:
 12                 ls[i], ls[j] = ls[j], ls[i]
 13         print(ls)
 14     print("after: ", ls)
 15 
 16 
 17 def bubble_sort2(ls):
 18     """冒泡排序"""
 19     print("before:", ls)
 20     for j in range(len(ls) - 1, 0, -1):
 21         # j = [len(ls) - 1, len(ls) - 2, ..., 1], 每次需要比較的次數
 22         # i = [0, 1, 2, ..., j - 1],需要比較的下標
 23         for i in range(j):
 24             if ls[i] > ls[i + 1]:
 25                 ls[i], ls[i + 1] = ls[i + 1], ls[i]
 26         print(ls)
 27     print("after:", ls)
 28 
 29 
 30 def selection_sort(ls):
 31     """選擇排序"""
 32     # 假設左邊為已排序,右邊為未排序
 33 
 34     print("before:", ls)
 35     for i in range(0, len(ls) - 1):
 36         # i = [0, 1, 2,,, len(ls) - 2]
 37         # j = [i + 1, i + 2,,, len(ls) - 1]
 38         min_index = i
 39         for j in range(i + 1, len(ls)):
 40             if ls[j] < ls[min_index]:
 41                 min_index = j
 42 
 43         if min_index != i:
 44             ls[min_index], ls[i] = ls[i], ls[min_index]
 45         print(ls)
 46     print("after:", ls)
 47 
 48 
 49 def insert_sort(ls):
 50     """插入排序"""
 51     # 假設左邊已排序,右邊為未排序,每次從右邊取一個數,遍歷已排序的子序列,直到找到次數的位置。
 52     print("before: ", ls)
 53     for j in range(1, len(ls)):
 54         for i in range(j, 0, - 1):
 55             if ls[i] < ls[i - 1]:
 56                 ls[i], ls[i - 1] = ls[i - 1], ls[i]
 57         print(ls)
 58     print("after: ", ls)
 59 
 60 
 61 def shell_sort(ls):
 62     """希爾排序"""
 63     print("before: ", ls)
 64 
 65     step = len(ls) // 2     # 初始步長
 66 
 67     while step > 0:
 68         # 插入排序
 69         for j in range(step, len(ls)):
 70             for i in range(j, 0, - step):
 71                 if ls[i] < ls[i - step]:
 72                     ls[i], ls[i - step] = ls[i - step], ls[i]
 73         step //= 2
 74         print(ls)
 75     print("shell_sort :", ls)
 76 
 77 
 78 def quick_sort1(ls, start, end):
 79     """
 80         快速排序-1
 81         low 和 high 分別指向序列的頭和尾
 82         low += 1, high -= 1
 83         在low自增過程中,直到找到大於 mid_val 的下標
 84         在high自增減過程中,直到找到小於 mid_val 的小標
 85         然后將這兩個值交換
 86     """
 87 
 88     # 遞歸退出條件
 89     if start >= end:
 90         return
 91 
 92     low = start
 93     high = end
 94     mid_val = ls[low]
 95 
 96     while low < high:
 97         while low < high and ls[high] > mid_val:
 98             high -= 1
 99         ls[low] = ls[high]
100 
101         while low < high and ls[low] < mid_val:
102             low += 1
103         ls[high] = ls[low]
104 
105     ls[low] = mid_val
106 
107     print("mid:", mid_val, ls)
108 
109     quick_sort1(ls, start, low - 1)  # 左邊的子序列
110     quick_sort1(ls, low + 1, end)    # 右邊的子序列
111 
112     return ls
113 
114 
115 def quick_sort2(ls):
116     """快速排序-2"""
117 
118     # 遞歸退出條件
119     if len(ls) <= 1:
120         return ls
121 
122     left_ls, right_ls = [],[]
123     mid_val = ls[0]
124     for i in range(1, len(ls)):
125         if ls[i] < mid_val:
126             left_ls.append(ls[i])
127         else:
128             right_ls.append(ls[i])
129 
130     print(left_ls, mid_val, right_ls)
131 
132     # 遞歸調用,左右子列表
133     left_res = quick_sort2(left_ls)
134     right_res = quick_sort2(right_ls)
135 
136     return left_res + [mid_val] + right_res
137 
138 
139 def merge_sort(ls):
140     """歸並排序"""
141     n = len(ls)
142 
143     # 遞歸退出條件
144     if n <= 1:
145         return ls
146 
147     mid = n // 2
148 
149     # 1、拆分子序列
150     left_ls = merge_sort(ls[:mid])
151     right_ls = merge_sort(ls[mid:])
152 
153     # 2、合並子序列:left_ls 和 right_ls
154     left_point, right_point = 0, 0
155     res = []
156 
157     # 當left_ls或者right_ls 結束,就會退出 while,而另外一個則可能未結束,所有后面需要 res +=
158     while left_point < len(left_ls) and right_point < len(right_ls):
159         # 比較兩個子序列,小的先加入到 res[]
160         if left_ls[left_point] < right_ls[right_point]:
161             res.append(left_ls[left_point])
162             left_point += 1
163         else:
164             res.append(right_ls[right_point])
165             right_point += 1
166     print("res:", res)
167 
168     res += left_ls[left_point:]
169     res += right_ls[right_point:]
170 
171     return res
172 
173 
174 def binary_search_recursion(ls, item):
175     """二分查找---遞歸"""
176     n = len(ls)
177     if n < 1:
178         return False
179 
180     mid = n // 2
181 
182     # 與中間值比較
183     if item == ls[mid]:
184         return True
185 
186     # 去左邊子序列查找
187     elif item < ls[mid]:
188         return binary_search_recursion(ls[:mid], item)
189 
190     # 去右邊子序列查找
191     else:
192         return binary_search_recursion(ls[mid + 1:], item)
193 
194 
195 def binary_search(ls, item):
196     """二分查找---非遞歸"""
197     n = len(ls)
198     start = 0
199     end = n - 1
200 
201     while start <= end:
202         mid = (start + end) // 2
203 
204         if item == ls[mid]:
205             return True
206         elif item < ls[mid]:
207             end = mid - 1
208         else:
209             start = mid + 1
210     return False
排序與查找算法.py

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM