目录
1.基本排序算法
1.1 选择排序
1.2 冒泡排序
1.3 插入排序
2. 更快排序算法
2.1 快速排序
2.2 归并排序(合并排序)
计算机科学家设计了很多巧妙的策略对列表的项进行排序。每个Python排序函数都是在整数的一个列表上进行操作的,并且都会使用一个swap函数来交换列表中的两项的位置。
def swap(lyst, i , j):
"""Exchange the items at positions i and j."""
temp = lyst[i]
lyst[i] = lyst[j]
lyst[j] = temp
排序算法可能最简单的策略就是搜索整个列表,找到最小项的位置。如果该位置不是列表的第1个位置,算法就会交换这两个位置的项。然后,算法回到第2个位置并且重复这个过程,如果必要的话,将最小项和第2个位置的项交换。当算法到达整个过程的最后一个位置,列表就是排序好了的。这个算法就叫做选择排序(Selection Sort)。因为每一次经过主循环的时候,都会选择一个移动的项。图1展示了对于5个项的一个列表进行选择排序,在每一次搜索和交换之后的状态。在每一轮都只是交换两项,这两项的后面有星号表示。
未排序的列表 | 第1轮之后 | 第2轮之后 | 第3轮之后 | 第4轮之后 |
5 | 1* | 1* | 1* | 1* |
3 | 3 | 2* | 2* | 2* |
1 | 5 | 5 | 3* | 3* |
2 | 2 | 3 | 5 | 4* |
4 | 4 | 4 | 4 | 5* |
Python实现:
def selectionSort(lyst):
i = 0
while i < len(lyst) - 1: # 做n-1次搜索
minIndex = i
j = i + 1
while j < len(lyst):
if lyst[j] < lyst[minIndex]:
minIndex = j
j += 1
if minIndex != i:
swap(lyst, i, minIndex)
i += 1
return lyst
选择排序在各种情况下的复杂度为O()。
冒泡排序(Bubble Sort)策略是从列表的开头处开始,并且比较一对数据项,直到移动到列表的末尾。每当成对的两项之间的顺序不正确的时候,算法就交换其位置。这个过程的效果就是将最大的项以冒泡的方式排到列表的末尾。然后,算法从列表开头到倒数第2个列表项重复这一过程,以此类推,直到该算法从列表的最后一项开始执行。此时,列表是已经排序好的。
图2展示了对5个项的一个列表进行冒泡排序的过程。这个过程把嵌套的循环执行了4次,将最大的项冒泡到了列表的末尾。再一次,只有交换的项用星号标记了出来。
未排序的列表 | 第1轮之后 | 第2轮之后 | 第3轮之后 | 第4轮之后 |
5 | 4* | 4 | 4 | 4 |
4 | 5* | 2* | 2 | 2 |
2 | 2 | 5* | 1* | 1 |
1 | 1 | 1 | 5* | 3* |
3 | 3 | 3 | 3 | 5* |
Python实现:
def bubbleSort(lyst):
n = len(lyst)
while n > 1:
i = 1
while i < n:
if lyst[i] < lyst[i - 1]:
swap(lyst, i , i-1)
i += 1
n -= 1
return lyst
和选择排序一样,冒泡排序也有一个嵌套的循环。冒泡排序的复杂度是O()。和选择排序一样,如果列表是已经排序好的,冒泡排序不会执行任何的交换。然而,在最坏情况下,冒泡排序交换工作超过线性方式。
可以对冒泡排序进行一个小的调整,将其在最好情况下的性能提高到线性阶。如果在通过主循环的时候,没有发生交换,那么,列表就是已经排序好的。这种情况可能发生在任何一个轮次,但是在最好的情况下,第1轮就会发生。可以使用一个布尔标志来记录交换动作的出现,并且当内部循环没有设置这个标志的时候,就从函数返回。下面是修改后的冒泡排序函数:
def bubbleSortWithTweak(lyst):
n = len(lyst)
while n > 1:
swapped = False # 如果在通过主循环的时候,没有发生交换,那么,列表就已经是排好序的了。
i = 1
while i < n:
if lyst[i] < lyst[i - 1]:
swap(lyst, i , i-1)
swapped = True
i += 1
if not swapped:
return lyst
n -= 1
return lyst
注意,这一修改只是改进了最好情况下的行为。在平均情况下,冒泡排序的这个版本的复杂度仍然是O()。
我们修改后的冒泡排序,对于已经排序好的列表来说,其性能比选择排序要好。但是如果列表中的项是没有顺序的,我们修改后的冒泡排序版本的性能仍然很糟糕。另一种算法叫作插入排序(Insertion Sort),它试图以一种不同的方式来对列表进行排序。其策略如下:
图3展示了对5个项的一个列表进行插入排序,以及在每一次通过外围循环之后的状态。在下一个轮次中插入的项用一个箭头←标记出来,在将这个项插入之后,用星号将其标记出来。
未排序的列表 | 第1轮之后 | 第2轮之后 | 第3轮之后 | 第4轮之后 |
2 | 2 | 1* | 1 | 1 |
5 ← | 5(没有插入) | 2 | 2 | 2 |
1 | 1← | 5 | 4* | 3* |
4 | 4 | 4← | 5 | 4 |
3 | 3 | 3 | 3← | 5 |
Python实现:
def insertionSort(lyst):
i = 1
while i < len(lyst):
itemToInsert = lyst[i]
j = i - 1
while j >= 0:
if itemToInsert < lyst[j]:
lyst[j + 1] = lyst[j]
j -= 1
else:
break
lyst[j + 1] = itemToInsert
i += 1
插入排序的最坏情况的复杂度是O()。列表中排好序的项越多,插入排序的效果越好,在最好情况下,列表本来就是有序的。那么,插入排序的复杂度是线性阶的。然而,在平均情况下,插入排序的复杂度仍然是二次方阶的。
到目前为止,我们上面的3种排序算法都有O()的运行时间。这些排序算法还有几种变体,其中的一些稍微快一些。但是,在最坏情况下和平均情况下,它们的性能还是O()。然而,我们可以利用一些复杂度为O()的更好地算法。这些更好的算法的秘诀就是,采用分而治之(divide-and-conquer)的策略。也就是说,每一个算法都找到了一种方法,将列表分解为更小的子列表。随后,这些子列表再递归地排序。理想情况下,如果这些子列表的复杂度为log(n),而重新排列每一个子列表中的数据所需的工作量为n,那么,这样的排序算法总的复杂度就是O()。
快速排序(Quick Sort)所使用的策略之一如下:
Python实现:
def quicksort(lyst, left, right):
if left >= right:
return lyst
low = left
high = right
key = lyst[low]
while left < right:
while left < right and lyst[right] > key:
right -= 1
array[left] = lyst[right]
while left < right and lyst[left] <= key:
left += 1
lyst[right] = lyst[left]
array[right] = key
quicksort(lyst, low, left - 1)
quicksort(lyst, left + 1, high)
return lyst
快速排序的最好情况的复杂度是O()。在最坏的情况下的复杂度是O()。
归并排序(Merge Sort)算法利用了递归、分而治之的策略来突破O()的障碍。概述如下:
有3个Python函数在这个顶层的设计策略中协作:
Python实现:
def mergeSort(lyst):
# lyst list being sorted
# copyBuffer temporary space needed during merge
copyBuffer = Array(len(lyst))
mergeSortHelper(lyst, copyBuffer, 0, len(lyst) - 1)
def mergeSortHelper(lyst, copyBuffer, low, high):
# lyst list being sorted
# copyBuffer temporary space needed during merge
# low, high bounds of sublist
# middle midpoint of sublist
if low < high:
middle = (low + high) // 2 # 返回商的整数部分(向下取整)
mergeSortHelper(lyst, copyBuffer, low, middle)
mergeSortHelper(lyst, copyBuffer, middle + 1, high)
merge(lyst, copyBuffer, low, middle, high)
def merge(lyst, copyBuffer, low, middle, high):
# lyst list that is being sorted
# copyBuffer temporary space needed during merge process
# low beginning of first sorted sublist
# middle end of first sorted sublist
# middle + 1 beginning of second sorted sublist
# high end of second sorted sublist
# Initialize i1 and i2 to the first items in each sublist
i1 = low
i2 = middle + 1
# Interleave items from the sublists into the
# copyBuffer in such a way that order is maintained.
for i in range(low, high + 1):
if i1 > middle:
copyBuffer[i] = lyst[i2] # First sublist exhausted
i2 += 1
elif i2 > high:
copyBuffer[i] = lyst[i1] # Second sublist exhasuted
i1 += 1
elif lyst[i1] < lyst[i2]: # Item in first sublist <
copyBuffer[i] = lyst[i1]
i1 += 1
else:
copyBuffer[i] = lyst[i2] # Item in second sublist <
i2 += 1
for i in range(low, high + 1): # Copy sorted items back to propoer position in lyst
lyst[i] = copyBuffer[i]
merge函数将两个排好序的子列表合并到一个大的排好序的子列表中。第1子列表在low和middle之间,第2子列表在middle+1到high之间。这个过程包含了3个步骤:
归并排序的运行时间由两条for语句主导,其中每一条都循环(high-low+1)次。结果,该函数的运行时间是O(high-low),在一个层的所有合并花费时间是O(n)。由于mergeSortHelper在每一层都是尽可能平均地分割子列表,层级数是O(),并且在所有情况下,该函数的最大运行时间是O()。