1. Timsort算法(PYTHON中sort函数的御用算法)
冒泡排序
第 1 轮:依次比较第 1 和第 2 个,第 2 和第 3 个,…,第 n-1 和第 n 个元素,大的向后移,第 1 大的移到倒数第 1 个位置。
第 2 轮:依次比较第 1 和第 2 个,第 2 和第 3 个,…,第 n-2 和第 n-1 个元素,大的向后移,第 2 大的移到倒数第 2 个位置。
▪ ▪ ▪ ▪ ▪ ▪
第 n-1 轮:比较第 1 和第 2 个,大的向后移,第 n-1 大的移到倒数第 n-1 个位置,所有元素排序完成。
冒泡排序低效的原因之一是它对于含有n个元素的序列,总要进行n - 1轮共计n(n - 1)/2次比较,即使该序列已经完全有序。于是一种改进方法是跟踪每一轮的比较结果,如果在某一轮完成所有两两比较后,发现没有发生任何交换,就说明此时已经完成了排序任务,我们提前收工。下面是这种优化的冒泡算法以及实现代码。
▪ ▪ ▪ ▪ ▪ ▪
直到某轮比较过程中,未发生任何交换,表明排序已完成。
def optimized_bubble_sort(nums):
n = len(nums)
for i in range(n-1):
swap = False
for j in range(n-1-i):
if nums[j] > nums[j+1]:
nums[j], nums[j+1] = nums[j+1], nums[j]
# 注意此处的实现细节,每轮只有第一次交换才会赋值 swap = True
if not swap:
swap = True
if not swap:
break
return nums
插入排序
第 1 轮:将第 2 个元素插到第 1 个元素的左边或右边,前 2 个元素排序完成。
第 2 轮:将第 3 个元素插到前 2 个元素的合适位置上,前 3 个元素排序完成。
▪ ▪ ▪ ▪ ▪ ▪
第 n-1 轮:将第 n 个元素插到前 n-1 个元素的合适位置上 (依次将其与第 n-1 个,第 n-2 个,…,第 1 个元素相比,若小于则交换位置,大于等于则跳出循环),所有元素排序完成。
def insersion_sort(nums):
n = len(nums)
for i in range(n-1):
for j in range(i+1, 0, -1):
if nums[j] < nums[j-1]:
nums[j], nums[j-1] = nums[j-1], nums[j]
else:
break
return nums
选择排序
第 1 轮:从第 1 个元素开始,找到最小元素的指标,将该元素与第 1 个元素交换。
第 2 轮:从第 2 个元素开始,找到最小元素的指标,将该元素与第 2 个元素交换。
▪ ▪ ▪ ▪ ▪ ▪
第 n-1 轮:从第 n-1 个元素开始,找到最小元素的指标,将该元素与第 n-1 个元素交换。
def selection_sort(nums):
n = len(nums)
for i in range(n-1):
min_index = i
for j in range(i+1, n):
if nums[j] < nums[min_index]:
min_index = j
if min_index != i:
nums[i], nums[min_index] = nums[min_index], nums[i]
return nums
归并排序
归并排序的整体思路,是对于一个较大的序列,不断将其细分为较小序列,并递归地调用归并排序对每一小段分别排序,具体步骤和代码如下。
平均分割:把当前序列平均分割为两个子序列。
递归排序:对以上两个子序列,分别递归地用归并排序法排序 (在递归调用过程中,若序列为空或只有一个元素,表明已经完成排序)。
保序集成:将上一步得到的两个有序子列,保序集成到一起,完成整个序列的排序。
def merge(left, right): # 保序集成算法
result = []
while left and right:
if left[0] < right[0]:
result.append(left.pop(0))
else:
result.append(right.pop(0))
if left:
result += left
if right:
result += right
return result
def merge_sort(nums):
n = len(nums)
if n <= 1: # 若序列为空或只有一个元素,表明已经完成排序
return nums
m = n//2 # 平均分割
left = nums[:m]
right = nums[m:]
left = merge_sort(left) # 递归排序
right = merge_sort(right)
return merge(left, right) # 保序集成
快速排序
挑选基准:选取序列的最后一个元素作为基准,其值为基准值。
基准分割:对序列重新排序,所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准后面 (与基准值相等的元素可到任一边),在这个分割结束之后,完成对基准的排序。
递归排序:递归地将基准前面的子序列和基准后面的子序列用此快速排序法排序 (在递归调用过程中,若序列为空或只有一个元素,表明已经完成排序)。
由于其采用基准来划分序列,因而可以省略归并排序中对子列的集成这一步骤,不过相应的代价就是基准的选取方法会对算法性能产生较大影响。
def poor_quick_sort(nums):
n = len(nums)
if n <= 1: # 若序列为空或只有一个元素,表明已经完成排序
return nums
less = []
more = []
pivot = nums.pop() # 挑选基准
for i in nums: # 基准分割
if i < pivot:
less.append(i)
else:
more.append(i)
nums.append(pivot) # 这一句是为了让传入的 nums 保持不变
return poor_quick_sort(less) + [pivot] + poor_quick_sort(more) # 递归排序
但是,当使用完全逆序整数序列 list(range(9999, -1, -1)) 对其进行测试时,由于每次所选基准两边元素的数量差都达到最大,也就是遇到了所谓的“最坏状况”,结果程序抛出了 RecursionError,即超过递归深度限制的异常,如下所示。解决这个问题的一种办法就是在第一步随机挑选基准。
Timsort
Timsort 是一种结合了插入排序和归并排序的混合算法。比较复杂,以后有空可以继续看看。