1.冒泡排序
冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢"浮"到数列的顶端。
算法步骤
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
def bubbleSort(arr):
n = len(arr)
# 遍历所有数组元素
for i in range(n):
# Last i elements are already in place
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
print(arr)
arr = [64, 34, 25, 12, 22, 11, 90]
bubbleSort(arr)
冒泡排序优化
设定一个变量为False,如果元素之间交换了位置,将变量重新赋值为True,最后再判断,
在一次循环结束后,变量如果还是为False,则brak退出循环,结束排序。
def bubble_sort(items):
for i in range(len(items) - 1):
flag = False
for j in range(len(items) - 1 - i):
if items[j] > items[j + 1]:
items[j], items[j + 1] = items[j + 1], items[j]
flag = True
print(items)
if not flag:
break
return items
items=[1,2,6,4,5]
print(bubble_sort(items))
2.快速排序
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为较小和较大的2个子序列,然后递归地排序两个子序列。
步骤为:
- 挑选基准值:从数列中挑出一个元素,称为"基准"(pivot);
- 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成;
- 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。
递归到最底部的判断条件是数列的大小是零或一,此时该数列显然已经有序。
选取基准值有数种具体方法,此选取方法对排序的时间性能有决定性影响。
def quick_sort(arr):
if len(arr) < 2:
return arr
# 选取基准,随便选哪个都可以,选中间的便于理解
mid = arr[len(arr) // 2]
# 定义基准值左右两个数列
left = []
right = []
# 从原始数组中移除基准值
arr.remove(mid)
for item in arr:
# 大于基准值放右边
if item >= mid:
right.append(item)
else:
# 小于基准值放左边
left.append(item)
# 使用迭代进行比较
return quick_sort(left) + [mid] + quick_sort(right)
arr = [64, 34, 25, 12, 22, 11, 90]
print(quick_sort(arr))
快速排序优化
双游标,提高稳定性
具体步骤
1.定义三个变量,to_sort表示先安排谁的位置,默认为一个排列的第一个数字;low表示左边的游标,和to_sort相同,默认为一个排列的第一个数字;high表示右边的游标,默认为一个排列的最后一个数字。
2.对于一个排列,第一个数字就是我们要安排的数字,已经存入了to_sort变量中,实际上是一个坑,一个可以被覆盖的空位。首先从high游标开始往左走,遇到小于to_sort的数字就停止,然后把这个数字扔给low游标(这里low游标的数字会被覆盖,而且high本身的位置上由于数值已经备份给了low游标,所以形成一个可以被覆盖的坑),此时轮到low游标走,low游标向右走,遇到比to_sort大或者相等的(这里我们统一把相等的值扔到右边)就停下扔给high,然后high继续走,high和low交替向中间靠拢直到相遇,相遇的位置会形成一个可以被覆盖的坑(它的值已经在其他位置被保存),把to_sort扔到最后剩下的坑里,完成第一次排序
3.第一次排序之后,左右两边分别形成一个排列,对排列重复2过程,当最后子排列中只有一个数值的时候,就完成了排序,这是一个递归过程。
def quick_sort(arr, start, end):
# 递归的出口
if start >= end:
return
# 需要三个变量,分别记录要找位置的数值(默认为第一个,存在to_sort中),左边的low游标,右边的high游标
to_sort = arr[start]
# 左边游标
low = start
# 右边游标
high = end
# 当low >= high的时候,说明完成了一个元素位置的查找,跳出循环
while low < high:
# 控制右边游标的移动,如果数字比to_sort大或者相等,游标持续左移,不满足条件时,将值赋给另一个游标
while low < high and arr[high] >= to_sort:
high -= 1
arr[low] = arr[high]
# 控制左边游标的移动,如果数字比to_sort小,游标持续右移,不满足条件时,将值赋给另一个游标
while low < high and arr[low] < to_sort:
low += 1
arr[high] = arr[low]
# 剩下一个low的坑,用来放to_sort
arr[low] = to_sort
# 这个地方用递归,对左右两边的排列再次调用快排
quick_sort(arr, start, low-1)
quick_sort(arr, low+1, end)
arr1 = [20, 87, 20, 89, 20, 20, 10]
quick_sort(arr1, 0, len(arr1)-1)
print(arr1)
时间复杂度的探讨
最优时间复杂度O(nlogn)
最优的情况就是我们每个要安排的元素都可以把待排序的区间分裂成左右两个排列。
第1次排序 得到2个排列 共安排了1个元素 时间复杂度n
第2次排序 得到4个排列 共安排了2+1个元素 时间复杂度n
第3次排序 得到8个排列 共安排了4+2+1个元素 时间复杂度n
以此类推,如果要安排n个元素,需要log2(n+1)次排序,而单次排序的时间复杂度固定为n,所以时间复杂度为nlogn
最坏时间复杂度O(n2)
最坏的情况就是每次要安排的元素都位于边缘,只在to_sort的一边有元素,而另一边没有。
第1次排序 得到1个排列 共安排了1个元素 时间复杂度n
第2次排序 得到1个排列 共安排了1+1个元素 时间复杂度n
第3次排序 得到1个排列 共安排了1+1+1个元素 时间复杂度n
以此类推,如果要安排n个元素,需要n次排序,而单次排序的时间复杂度固定为n,所以时间复杂度为O(n2)