幾種搜尋、排序演算法的python實現
阿新 • • 發佈:2019-02-09
一些複雜度的例項
n | 對數階 | 線性階n | 平方階 | 指數階 |
---|---|---|---|---|
100 | 7 | 100 | 10000 | 超標 |
1000 | 10 | 1000 | 1000000 | 超標 |
1000000 | 20 | 1000000 | 1000000000000 | 嚴重超標 |
搜尋演算法
搜尋最小值
def indexOfMin(lyst):
'''返回最小項的索引'''
minIndex=0
currentIndex = 1
while currentIndex<len(lyst):
if lyst[currentIndex]<lyst[minIndex]:
minIndex = currentIndex
currentIndex +=1
return minIndex
這個演算法的複雜度為O(n)
順序搜尋一個列表
def sequentialSearch(target,lyst):
'''如果找到目標返回他在列表中的索引否則返回-1'''
position = 0
while position<len(lyst):
if target == lyst[position]:
return position
position += 1
return -1
順序搜尋的分析要考慮如下這三種情況:
在最壞的情況下,目標位於列表的末尾,或者根本不在列表中。那麼,演算法必須訪問每一項,並且對大小為n的列表要執行n次迭代。因此順序搜尋的最壞情況的複雜度為O(n)
在最好的情況下,演算法只進行了一次迭代就在第一個位置找到目標項,複雜度為O(1)
要確定平均情況,把在每一個可能的位置找到目標項所需要的迭代次數相加,並用總和除以n。因此演算法執行了(n+1)/2次迭代。複雜度仍為O(n).
有序列表的二叉樹搜尋
# 假設列表升序排列
def binarySearch(target, sortedLyst):
left = 0
right = len(sortedLyst) - 1
while left <= right:
midpoint = (left + right) // 2
if target == sortedLyst[midpoint]:
return midpoint
elif target < sortedLyst[midpoint]:
right = midpoint - 1
else:
left = midpoint + 1
return -1
二叉搜尋的最壞情況的複雜度為O()
基本排序演算法
選擇排序
工作原理是每一次從待排序的資料元素中選出最小(或最大)的一個元素,存放在序列的起始位置,直到全部待排序的資料元素排完。
def selectionSort(lyst):
i = 0
while i < len(lyst) - 1:
minIndex = i
j = i + 1
while j < len(lyst): # 尋找第i項以後的最小項與第i項交換
if lyst[j] < lyst[minIndex]:
minIndex = j
j += 1
if minIndex != i:
lyst[i], lyst[minIndex] = lyst[minIndex], lyst[i]
i += 1
選擇排序的複雜度的為O()
氣泡排序
氣泡排序演算法的原理如下:
- 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
- 對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
- 針對所有的元素重複以上的步驟,除了最後一個。
- 持續每次對越來越少的元素重複上面的步驟,直到沒有任何一對數字需要比較。
def bubbleSort(lyst):
n = len(lyst)
while n > 1:
swapped = False #記錄每輪迴圈是否一次都沒有交換過資料(即已經排好序了)以便儘早退出迴圈。
i = 1
while i < n:
if lyst[i] < lyst[i - 1]: # 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
lyst[i], lyst[i - 1] = lyst[i - 1], lyst[i]
swapped = True
i +=1
if not swapped:
return None
n -=1
氣泡排序複雜度為O()
插入排序
類似於人們排列手中撲克牌的順序。也就是說,如果你按照順序排 好了前i-1張牌,抓取第i張牌並且與手中的這些牌進行比較,直到找到合適的位置
假設有一組無序序列 。
- 我們先將這個序列中下標為 0 的元素視為元素個數為 1 的有序序列。
- 然後,我們要依次把 插入到這個有序序列中。所以,我們需要一個外部迴圈,從下標 1 掃描到 n-1 。
- 接下來描述插入過程。假設這是要將 插入到前面有序的序列中。由前面所述,我們可知,插入時,前 i-1 個數肯定已經是有序了。
所以我們需要將和 ~ 進行比較,確定要插入的合適位置。這就需要一個內部迴圈,我們一般是從後往前比較,即從下標 i-1 開始向 0 進行掃描。
def insertionSort(lyst):
i = 1
while i < len(lyst):
itemToInsert = lyst[i]
j = i - 1
while j >= 0:
if lyst[j] > itemToInsert:
lyst[j + 1] = lyst[j]
j -= 1
else:
break
lyst[j + 1] = itemToInsert
i += 1
更快的排序
快速排序
快速排序所使用的策略可以概括如下:
首先,從列表的中點位置選取一項。在這一項叫做基準點。
將列表中的項分割槽,以便小於基準點的所有項都移動到基準點左邊,而剩下的項都移動到基準點的右邊。根據相關的實際項,基準點自身的最終位置也是變化的。例如,如果基準點自身是最大的項,它會位於列表的最右邊,如果基準點是最小值,它會位於最左邊。但是,不管基準點最終位於何處,這個位置都是它在 完全排序的列表中的最終位置。
分而治之。對於基準點分割列表而形成的子列表,遞迴地重複該過程。一個子列表包含了基準點左邊所有的項(現在是較小的項),另一個子列表包含了基準點右邊的所有的項(現在是較大的項)。
每次遇到少於兩個項的一個子列表,就介紹這個過程。
步驟 | 列表 |
---|---|
假設子列表是由數字組成的,其中包含一個基準點14 | 12 19 17 18 14 11 15 13 16 |
將基準點和最後一項交換 | 12 19 17 18 16 11 15 13 14 |
在第一項之前建立一個邊界 | * 12 19 17 18 16 11 15 13 14 |
掃描小於基準點的第一項 | * 12 19 17 18 16 11 15 13 14 |
將這一項和邊界之後的第一項交換。在這個例子中, 該項是與自身交換 |
* 12 19 17 18 16 11 15 13 14 |
將邊界向後移動 | 12 * 19 17 18 16 11 15 13 14 |
掃描小於基準點的第一項 | 12 * 19 17 18 16 11 15 13 14 |
將這一項和邊界之後的第一項交換 | 12 * 11 17 18 16 19 15 13 14 |
將邊界向後移動 | 12 11 * 17 18 16 19 15 13 14 |
掃描小於基準點的第一項 | 12 11 * 17 18 16 19 15 13 14 |
將這一項和邊界之後的第一項交換 | 12 11 * 13 18 16 19 15 17 14 |
將邊界向後移動 | 12 11 13 * 18 16 19 15 17 14 |
掃描小於基準點的第一項;然而這次沒有這樣的一項 | 12 11 13 * 18 16 19 15 17 14 |
將這一項和邊界之後的第一項交換。此時小於基準 項的所有項都在基準項的左邊;剩餘的項在基準點 的右邊 |
12 11 13 * 14 16 19 15 17 18 |
import random
def quicksort(lyst):
quicksortHelper(lyst, 0, len(lyst) - 1)
def quicksortHelper(lyst, left, right):
if left < right:
pivolocation = partition(lyst, left, right)
quicksortHelper(lyst, left, pivolocation - 1)
quicksortHelper(lyst, pivolocation + 1, right)
def partition(lyst, left, right):
# 找到基準點與最後一項交換
middle = (left + right) // 2
pivot = lyst[middle]
lyst[middle], lyst[right] = lyst[right], lyst[middle]
# 在第一項前建立一個邊界
boundary = left
# 將小於基準點的第一項與邊界後的第一項交換
for index in range(left, right):
if lyst[index] < pivot:
lyst[index], lyst[boundary] = lyst[boundary], lyst[index]
boundary += 1
# 全部掃描完後 將基準點與邊界後的那一項交換
lyst[right], lyst[boundary] = lyst[boundary], lyst[right]
return boundary
def main(size=20, sort=quicksort):
lyst = []
for count in range(size):
lyst.append(random.randint(1, size + 1))
print(lyst)
sort(lyst)
print(lyst)
if __name__ == '__main__':
main()
合併排序(歸併排序)
非正式的概述;
- 計算一個列表的中間位置,並且遞迴地排序其左邊和右邊的子列表(分而治之).
- 將兩個排好序的子列表重新合併為單個的排好序的列表
- 當子列表不再能夠劃分的時候,停止這個過程
from TestClass import Array
def mergeSort(lyst):
'''
合併的過程中使用了和列表相同大小的一個數組(這個陣列是在根目錄下定義的)。這個陣列名為copyBuffer。為了避免每次呼叫merge的時候為copyBuffer分配和釋放記憶體的開銷。只在mergeSort中分配一次該緩衝區,並且在後續將其作為一個引數傳遞給mergeSortHelper和merge。每次呼叫mergeSortHelper的時候,都需要知道他所操作的子列表的邊界。這些邊界通過另外的兩個引數low,high來提供。
'''
copyBuffer = Array(len(lyst))
mergeSortHelper(lyst, copyBuffer, 0, len(lyst) - 1)
def mergeSortHelper(lyst, copyBuffer, low, high):
if low < high:
middle = (low + high) // 2
mergeSortHelper(lyst, copyBuffer, low, middle)
mergeSortHelper(lyst, copyBuffer, middle + 1, high)
merage(lyst, copyBuffer, low, middle, high)
def merage(lyst, copyBuffer, low, middle, high):
'''
:param low: 第一個子列表的第一索引
:param middle: 第一個子列表的最後一索引
middle+1:第二個子列表的第一索引
:param high: 第二個子列表的最後一索引
這個函式將兩個排好序的子列表合併到一個大的拍好序的子列表中。
'''
i1 = low # 將索引指標設定為每個子列表的第一索引
i2 = middle + 1
for i in range(low, high + 1): # 從每個子列表的第一項開始重複地比較各項。將較小的項從其子列表中複製到複製快取即copyBuffer中
if i1 > middle: # 並繼續處理子列表中的下一項。重複這個過程,直到兩個子列表中的所有的項都已經複製過。如果先到達其中一個子列表的末尾,通過從另一個子列表複製剩餘的項,從而結束這個步驟。
copyBuffer[i] = lyst[i2]
i2 += 1
elif i2 > high:
copyBuffer[i] = lyst[i1]
i1 += 1
elif lyst[i1] < lyst[i2]:
copyBuffer[i] = lyst[i1]
i1 += 1
else:
copyBuffer[i] = lyst[i2]
i2 += 1
for i in range(low, high + 1): # 將copyBuffer中low 和high之間的部分,複製回lyst中對應的位置。
lyst[i] = copyBuffer[i]
排序法 | 平均時間 | 最差情形 | 穩定度 | 額外空間 | 備註 |
---|---|---|---|---|---|
冒泡 | O() | O() | 穩定 | O(1) | n小時較好 |
選擇 | O() | O() | 不穩定 | O(1) | n小時較好 |
插入 | O() | O() | 穩定 | O(1) | 大部分已排序時較好 |
基數 | O() | O() | 穩定 | O(n) | B是真數(0-9), R是基數(個十百) |
Shell | O() | O() (1 |