排序算法的Python实现
1. 冒泡排序
多次遍历列表。
比较相邻元素,将不和顺序的交换。
每一轮遍历都将下一个最大值放在正确的位置上(如末尾)。
冒泡排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2) 。
def bubbleSort(alist): # 升序排列
for passnum in range(len(alist) - 1, 0, -1):
for i in range(passnum):
if alist[i] < alist[i+1]: # exchange
temp = alist[i]
alist[i] = alist[i+1]
alist[i+1] = temp
冒泡排序通常被认为是效率最低的排序算法,因为在确定最终的位置前必须交换元素。“多余”的交换操作代价很大。不过,由于冒泡排序要遍历列表中未排序的部分,因此它具有其他排序算法没有的用途。特别是,如果在一轮遍历中没有发生元素交换,就可以确定列表已经有序。可以修改冒泡排序函数,使其在遇到这种情况时提前终止。对于只需要遍历几次的列表,冒泡排序可能有优势,因为它能判断出有序列表并终止排序过程。这种排序通常被称作短冒泡。
def shortBubbleSort(alist):
exchanges = True
passnum = len(alist) - 1
while passnum > 0 and exchanges:
exchanges = False
for i in range(passnum):
if alist[i] > alist[i+1]:
exchanges = True
temp = alist[i]
alist[i] = alist[i+1]
alist[i+1] = temp
passnum = passnum - 1
2. 选择排序
每次遍历列表时只做一次交换。
在每次遍历时寻找最大值,并在遍历完之后将它放到正确位置上(如末尾)。
选择排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2) 。
相比于冒泡排序算法,选择排序算法通常更快。
def selectionSort(alist):
for fillslot in range(len(alist) - 1, 0, -1):
positionOfMax = 0
for location in range(1, fillslot + 1):
if alist[location] > alist[positionOfMax]:
positionOfMax = location
temp = alist[fillslot]
alist[fillslot] = alist[positionOfMax]
alist[positionOfMax] = temp
3. 插入排序
在列表较低的一端维护一个有序的子列表,并逐个将每个新元素按照大小“插入”这个子列表。
插入排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2) 。
def insertionSort(alist):
for index in range(1, len(alist)):
currentvalue = alist[index]
position = index
while position > 0 and alist[position-1] > currentvalue:
alist[position] = alist[position-1] # 比当前元素大的都要向后移一位,以便腾出空位
position = position - 1
alist[position] = currentvalue
4. 希尔排序
也称为“递减增量排序”。
对插入排序进行了改进,将列表分成数个子列表,并对每一个子列表应用插入排序。
关键是如何切分:使用步长i选取所有间隔为i的元素组成子列表。
列表已经由增量的插入排序做了预处理,所以最后一步插入排序不需要进行多次比较或移动。也就是说,每一轮遍历都生成了“更有序”的列表,这使得最后一步非常高效。
希尔排序的时间复杂度介于 O ( n ) O(n) O(n) 和 O ( n 2 ) O(n^2) O(n2) 之间。通过改变增量,比如采用 2 k − 1 2^k-1 2k−1 的步长,希尔排序的时间复杂度可以达到 O ( n 3 2 ) O(n^{\frac{3}{2}}) O(n23) 。
def shellSort(alist):
sublistcount = len(alist) // 2 # 步长为列表长度的1/2
while sublistcount > 0:
for startposition in range(sublistcount):
gapInsertionSort(alist, startposition, sublistcount)
print("After increments of size", sublistcount, "The list is", alist)
sublistcount = sublistcount // 2
def gapInsertionSort(alist, start, gap):
for i in range(start + gap, len(alist), gap):
currentvalue = alist[i]
position = i
while position >= gap and alist[position - gap] > currentvalue:
alist[position] = alist[position - gap]
position = position - gap
alist[position] = currentvalue
>>> alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
>>> shellSort(alist)
After increments of size 4 The list is [20, 26, 44, 17, 54, 31, 93, 55, 77]
After increments of size 2 The list is [20, 17, 44, 26, 54, 31, 77, 55, 93]
After increments of size 1 The list is [17, 20, 26, 31, 44, 54, 55, 77, 93]
5. 归并排序
使用分治策略改进排序算法。
递归:基本情况为列表为空或者只有一个元素,则认为列表是有序的。分治策略:每次将列表一分为二,并对两部分都递归调用归并排序,当两部分都有序后就进行归并(将两个小的有序列表并为一个大的有序列表)。
归并排序算法的时间复杂度为 O ( n log ( n ) ) O(n\log(n)) O(nlog(n)) 。
但是,mergeSort() 函数需要额外的空间来存储切片操作得到的两半部分。
def mergeSort(alist):
print("Splitting ", alist)
if len(alist) > 1:
mid = len(alist) // 2
lefthalf = alist[:mid]
righthalf = alist[mid:]
mergeSort(lefthalf)
mergeSort(righthalf)
# 归并 —— 每次从有序列表中取出最小值,放回初始列表alist
i = 0
j = 0
k = 0
while i < len(lefthalf) and j < len(righthalf):
if lefthalf[i] < righthalf[j]:
alist[k] = lefthalf[i]
i = i + 1
else:
alist[k] = righthalf[j]
j = j + 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)
>>> b = [54, 26, 93, 17, 77, 31, 44, 55, 20]
>>> mergeSort(b)
Splitting [54, 26, 93, 17, 77, 31, 44, 55, 20]
Splitting [54, 26, 93, 17]
Splitting [54, 26]
Splitting [54]
Merging [54]
Splitting [26]
Merging [26]
Merging [26, 54]
Splitting [93, 17]
Splitting [93]
Merging [93]
Splitting [17]
Merging [17]
Merging [17, 93]
Merging [17, 26, 54, 93]
Splitting [77, 31, 44, 55, 20]
Splitting [77, 31]
Splitting [77]
Merging [77]
Splitting [31]
Merging [31]
Merging [31, 77]
Splitting [44, 55, 20]
Splitting [44]
Merging [44]
Splitting [55, 20]
Splitting [55]
Merging [55]
Splitting [20]
Merging [20]
Merging [20, 55]
Merging [20, 44, 55]
Merging [20, 31, 44, 55, 77]
Merging [17, 20, 26, 31, 44, 54, 55, 77, 93]
6. 快速排序
使用分治策略。但不需要使用额外空间。
首先选出一个基准值(比如第一个元素),帮助切分列表。在最终的有序列表中,基准值的位置通常被称作分割点。算法在分割点切分列表,以进行对快速排序的子调用。
下一步是分区操作。首先找到两个目标:leftmark和rightmark,它们分别位于列表剩余元素的开头和末尾。首先加大leftmark,直到遇到一个大于基准值的元素;然后减小rightmark,直到遇到一个小于基准值的元素。互换这两个元素的位置,然后重复上述过程。
当rightmark小于leftmark时,过程终止。此时rightmark的位置就是分割点。将基准值与当前分割点的位置互换,即可使基准值位于正确位置。此时,分割点左边的元素都小于基准值,右边的元素都大于基准值。
接着,在分割点处将列表一分为二,针对左右两部分进行递归调用快速排序函数。
对于长度为 n n n 的列表,如果分区操作总是发生在列表的中部,就会切分 log ( n ) \log(n) log(n) 次,则时间复杂度为 O ( n log n ) O(n\log n) O(nlogn) ;如果切分得极不均匀,如分割点在列表的某一端,则时间复杂度变为 O ( n 2 ) O(n^2) 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 rightmark >= leftmark and alist[rightmark] >= pivotvalue:
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