內容說明
最近在面試,在面試過程中面試官經常會要求手寫代碼。其中大都是數據結構及其查找和排序的基本算法,所以基於python-data-structure-cn這本書的內容對數據結構進行個人理解及代碼實現。由於個人的閱讀習慣原因,分享內容的順序可能與書中不一致。原書地址如下,有學習的小夥伴可以直接在線閱讀和練習。
https://facert.gitbooks.io/python-data-structure-cn
什麼是排序
排序是以某種順序從集合中放置元素的過程。對大量項進行排序可能需要大量的計算資源。排序算法的效率與正在處理的項的數量有關。對於小集合,複雜的排序方法可能更麻煩,開銷太高。另一方面,對於更大的集合,我們希望利用儘可能多的改進。
分析排序算法時,可用於分析排序過程的操作:
- 比較的總數 ,在進行排序的過程中,必須對兩個值進行比較,這就需要一些系統的方法來比較值。比較的總數將是測量排序過程的最常見方法;
- 交換的總數,當值相對於彼此不在正確的位置時,可能需要交換它們。這種交換是一種昂貴的操作,並且交換的總數對於評估算法的整體效率也將是很重要的。
下面就向大家介紹常見的幾種算法。
冒泡排序
算法原理:逐次遍歷列表元素,兩兩比較,每次將較大的值放在右邊,每次遍歷都會把最大值放在最右側,即每次遍歷的次數都減一. n,n-1,n-2…, 算法複雜度爲O(n2)。
優缺點:冒泡排序通常被認爲是最低效的排序方法,因爲它必須在最終位置被知道之前交換項。但冒泡排序具有識別排序列表和及時停止的優點。
變種:短冒泡排序–判斷是否已經完全排序,如果已經完全排序則停止遍歷。
代碼實現:
# 冒泡排序
def bubble_sort(alist):
for i in range(len(alist)-1,0,-1):
for j in range(i):
if alist[j]>alist[j+1]:
alist[j], alist[j+1] = alist[j+1],alist[j]
return alist
# 短冒泡排序
def Shortbubblesort(alist):
ex=True
l=len(alist)-1
while l>0 and ex:
ex=False
for i in range(l):
if alist[i]>alist[i+1]:
ex=True
alist[i],alist[i+1]=alist[i+1],alist[i]
l = l-1
return alist
選擇排序
算法原理:對整個列表進行遍歷,每次遍歷只做一次交換,將最大值放到最右邊。遍歷n-1次排序n個項。時間複雜度O(n2)。
優缺點:與冒泡排序有相同的複雜度O(n2),然而交換數量的減少,選擇排序通常在基準研究中執行的更快。
代碼實現:
# 選擇排序
def selectionsort(alist):
for i in range(len(alist)-1,0,-1):
lmax=i
for j in range(i):
if alist[j] > alist[lmax]:
lmax=j
alist[lmax],alist[i] = alist[i],alist[lmax]
return alist
c=[2,4,3,5,1,8]
selectionsort(c)
print(c)
插入排序
算法原理: 每次遍歷都是通過維護列表的子列表的排序,對新加入的元素進行插入(移位),時間複雜度也是O(n2)。
優缺點:存在n-1個遍歷以對n個元素排序。從位置 1 開始迭代並移動位置到 n-1,因爲這些是需要插回到排序子列表中的項。第 8 行執行移位操作,將值向上移動到列表中的一個位置,在其後插入。請記住,這不是像以前的算法中的完全交換。關於移位和交換的一個注意事項也很重要。通常,移位操作只需要交換大約三分之一的處理工作,因爲僅執行一次分配.在基準研究中,插入排序有非常好的性能。
代碼實現:
# 插入排序
def insertionsort(alist):
for i in range(1,len(alist)-1):
curvalue=alist[i]
index=i
while index>0 and alist[index-1]>curvalue:
alist[index]=alist[index-1]
index=index-1
alist[index]=curvalue
return alist
希爾排序(遞減遞增排序)
算法原理:將一個列表拆分爲多個較小列表來改進插入排序,每個子列表使用插入排序進行排序。選擇這些子列表的方是希爾排序的關鍵。不是將列表拆分爲連續項的子列表,而是使用增量i(或者gap),通過選擇i個項的所有項來創建子列表。最後再使用增量爲1的插入排序:即標準插入排序。在通過執行之前的子列表排序後,我們減少了將列表置於其最終順序所需的移位操作的總數。
優缺點:希爾排序在每一次的遍歷後都會產生比前一個更有序的列表,這使得最後一次的完整插入排序不需要進行更多的移位。它的時間複雜度落於O(n)與O(n2)之間的某處,比直接插入排序要快。
代碼實現:
# 希爾排序
def gapInsertionSort(alist,start,gap): # 對子列表進行插入排序
for i in range(start+gap,len(alist),gap):
curvalue=alist[i]
index = i
while index >= gap and curvalue < alist[index-gap]:
alist[index] = alist[index-gap]
index = index-gap
alist[index]=curvalue
def ShellSort(alist): # 調用子列表插入排序函數,對增量進行逐次變化,以n/2爲起始,最終1爲止
gap=len(alist)//2
while gap > 0 :
for start in range(gap):
gapInsertionSort(alist,start,gap)
print('gap:',gap,'result:',alist)
gap = gap//2
歸併排序
算法原理:歸併排序的原理是分而治之,將列表依次從中間切割,直到切割到單一元素,之後再根據切割的順序依次進行排序合併,最終達到排序的結果。其中實現的思想是通過遞歸進行分割和合並子列表。
優缺點:通過兩個不同的實現過程來分析。首先是將列表分爲兩半。將列表分爲一般需要logn次,其中n是列表的長度。第二個過程是合併。列表中的每個項最終被處理並放置在排列的列表上。因此,大小爲n的列表的合併操作需要n個操作。則歸併排序的時間複雜度爲O(nlogn)。但由於歸併算法需要額外的空間來保存兩個半部分,所以如果列表很大的時候會產生影響。
代碼實現:
def mergesort(alist):
print("splitting:",alist)
if len(alist)>1:
mid = len(alist)//2
lefthalf=alist[:mid]
righthalf=alist[mid:]
mergesort(lefthalf)
mergesort(righthalf)
i=0
j=0
k=0
while i < len(lefthalf) and j < len(righthalf):
if lefthalf[i] > righthalf[j]:
alist[k]=righthalf[j]
j=j+1
else:
alist[k]=lefthalf[i]
i=i+1
k=k+1
while i < len(lefthalf):
alist[k]=lefthalf[i]
i=i+1
k=k+1
while j < len(righthalf):
alist[k]=righthalf[j]
j=j+1
k=k+1
print("Merging:",alist)
快速排序
算法原理:快速排序的原理也是分而治之,但不需要額外的空間。在快速排序中增加了一個新的角色–樞軸值(pivot)。樞軸值從英文釋義上即中心,其作用是拆分列表,即列表進行拆分時的拆分點。實現過程爲:首先在列表中選擇一個元素爲樞軸點,按照樞軸值左側的值小於樞軸點,右側的值大於樞軸點的邏輯從列表兩端進行遍歷並移位,當左側標記點的位置大於右側標記點的位置時,右側標記點的位置即爲拆分位置,將拆分位置的元素與樞軸值進行交換後,即得到第一次排序。後續經過遞歸操作,對拆分點左右兩側的子列表分別進行拆分排序,最終完成整體排序。
優缺點:快速排序在進行拆分的時候不會佔用額外空間,如果分裂點在列表中間,則時間爲O(nlogn),但是當分裂點不在列表中間,則時間爲O(n2),所以選擇樞軸值是方法很重要的。常用的方法是中值三:考慮第一個元素,中間元素和最後一個元素。然後選擇中間值爲樞軸值。這樣將選出更好的中間值,當原列表部分有序時將特別有用。
代碼實現:
def quickSort(alist):
quickSortHelper(alist,0,len(alist)-1)
def quickSortHelper(alist,first,last):
if first<last:
splitpoint = partition(alist,first,last)
quickSortHelper(alist,first,splitpoint-1)
quickSortHelper(alist,splitpoint+1,last)
def partition(alist,first,last):
pivotvalue = alist[first]
leftmark = first+1
rightmark = last
done = False
while not done:
while leftmark <= rightmark and alist[leftmark] <= pivotvalue:
leftmark = leftmark + 1
while alist[rightmark] >= pivotvalue and rightmark >= leftmark:
rightmark = rightmark -1
if rightmark < leftmark:
done = True
else:
temp = alist[leftmark]
alist[leftmark] = alist[rightmark]
alist[rightmark] = temp
temp = alist[first]
alist[first] = alist[rightmark]
alist[rightmark] = temp
return rightmark
總結
冒泡排序,選擇排序和插入排序是 O(n2)算法。
希爾排序通過排序增量子列表來改進插入排序。它落在 O(n) 和 O(n2) 之間。
歸併排序是 O(nlogn),但是合併過程需要額外的空間。
快速排序是 O(nlogn),但如果分割點不在列表中間附近,可能會降級到O(n2) 。它不需要額外的空間。