排序算法:
人们曾经研究最多的一类算法
依然是最基础的、在程序中使用频率最高的算法之一
排序算法根据时间复杂度分两类:
( N − 1 ) + ( N − 2 ) + . . . + 1 = N ( N − 1 ) / 2 = O ( N 2 ) (N-1) + (N-2) + ... + 1 = N(N-1)/2 = O(N^2) (N−1)+(N−2)+...+1=N(N−1)/2=O(N2)
# python
def selection_sort(nums):
for i in range(len(nums), 1, -1):
for j in range(0, i-1):
if nums[j] > nums[j+1]:
temp = nums[j]
nums[j] = nums[j+1]
nums[j+1] = temp
return nums
# example
nums = [12, 9, 3, 6, 7, 14, -6, 13, 24, 7, 6, -9, 5, 0, 5, 6, 14, 16, 12, 18, 18]
print(selection_sort(nums))
“最坏”的排序算法,因为在一个长为N的数组中挑出最大的那个数,最多需要进行 O ( N ) O(N) O(N)次操作,整个数组最多这样重复 N N N次,也就排好序了。所以 O ( N 2 ) O(N^2) O(N2)可以说是排序算法的上界。
对于未排序数组,不断从后向前扫描,对于每一个扫描的元素,找到相应的位置插入:小的数字插入数组的前面,大的插入后面(像打扑克牌时的抓牌过程)。所有的元素扫描一遍,全部插入相应的位置,也就实现了排序。
仍然是 O ( N 2 ) O(N^2) O(N2)
原因:插入的这个动作复杂度是 O ( N ) O(N) O(N),因为插入时需要插入位置后面所有的元素都后移一位。所以即使数组只扫描一遍,时间复杂度并没有改变。
# python
def insert_sort(nums):
for i in range(1, len(nums)):
temp = nums[i]
j = i -1
while j>=0 and temp < nums[j]:
nums[j+1] = nums[j]
j -= 1
nums[j+1] = temp
return nums
# example
nums = [12, 9, 3, 6, 7, 14, -6, 13, 24, 7, 6, -9, 5, 0, 5, 6, 14, 16, 12, 18, 18]
print(insert_sort(nums))
虽然从前向后只扫描了一遍数据,但是插入的时候从后向前需要再扫描一遍数据给需要插入的数据腾出位置,所以算法复杂度依然是 O ( N 2 ) O(N^2) O(N2)。
(1)和(2)两种算法做了很多次无谓的比较和数据的移动:
选择排序中,
插入排序中,
提出人:冯·诺伊曼(于1945年。分治算法和递归的典型应用。)
(其中 B , C B, C B,C序列的排序过程采用递归算法即可)
递归次数 O ( l o g N ) O(logN) O(logN),每次递归的计算量都是 O ( N ) O(N) O(N),所以时间复杂度是 O ( N l o g N ) O(NlogN) O(NlogN)。
# python
def merge_sort(nums):
if len(nums) <= 1:
return nums
mid = len(nums) // 2
left = merge_sort(nums[: mid])
right = merge_sort(nums[mid:])
return merge(left, right)
def merge(left, right):
result = [] # 这里新建了个list,存储空间额外占用 O(N)
i = 0
j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result += left[i:]
result += right[j:]
return result
nums = [12, 9, 3, 6, 7, 14, -6, 13, 24, 7, 6, -9, 5, 0, 5, 6, 14, 16, 12, 18, 18]
print(merge_sort(nums))
寻找更优化算法的精髓就在于少做无用功。
在计算机科学中很难有绝对的好,因为衡量好的标准有很多维度。
提出人:加拿大计算机科学家约翰·威廉斯 (于1964年)。
# python
# 看讲解利用完全二叉树排序很简单,代码就迷糊了
def heap_sort(nums):
n = len(nums)
for i in range(n // 2 - 1, -1, -1):
heapify(nums, n, i)
for i in range(n - 1, 0, -1):
nums[i], nums[0] = nums[0], nums[i]
heapify(nums, i, 0)
return nums
def heapify(nums, n, i):
largest = i
l = 2 * i + 1
r = 2 * i + 2
if l < n and nums[i] < nums[l]:
largest = l
if r < n and nums[largest] < nums[r]:
largest = r
if largest != i:
nums[i], nums[largest] = nums[largest], nums[i]
heapify(nums, n, largest)
return nums
nums = [12, 9, 3, 6, 7, 14, -6, 13, 24, 7, 6, -9, 5, 0, 5, 6, 14, 16, 12, 18, 18]
print(heap_sort(nums))
提出人:英国计算机科学家托尼·霍尔
# python
def quick_sort(nums, low, high):
if low < high:
temp = partition(nums, low, high)
quick_sort(nums, low, temp-1)
quick_sort(nums, temp+1, high)
return nums
def partition(nums, low, high):
i = low - 1
pivot = nums[high]
for j in range(low, high):
if nums[j] <= pivot:
i = i + 1
nums[i], nums[j] = nums[j], nums[i]
nums[i+1], nums[high] = nums[high], nums[i+1]
return i + 1
nums = [12, 9, 3, 6, 7, 14, -6, 13, 24, 7, 6, -9, 5, 0, 5, 6, 14, 16, 12, 18, 18]
print(quick_sort(nums, 0, len(nums)-1))
算法 | 平均时间复杂度 | 最坏时间复杂度 | 额外空间复杂度 | 稳定性 |
---|---|---|---|---|
归并排序 | O ( N l o g N ) O(NlogN) O(NlogN) | O ( N l o g N ) O(NlogN) O(NlogN) | O ( N ) O(N) O(N) | ✔ |
堆排序 | O ( N l o g N ) O(NlogN) O(NlogN) | O ( N l o g N ) O(NlogN) O(NlogN) | O ( 1 ) O(1) O(1) | ✗ |
快速排序 | O ( N l o g N ) O(NlogN) O(NlogN) | O ( N 2 ) O(N^2) O(N2) | O ( l o g N ) O(logN) O(logN) | ✗ |
三种排序算法各有千秋,体现出在计算机科学领域做事的两个原则:
归并排序、堆排序和快速排序至今仍在使用,但是它们都不圆满。
科学家们依然在考虑在某个特定的应用中寻找一些更好的排序算法,但一种排序算法可能难以兼顾前面讲到的各个维度的多种需求。所以,现在人们对排序算法的改进大多是结合几种排序算法的思想,形成混合排序算法。
任何一个随机序列内部通常都有很多递增的子序列或者递减的子序列,相邻两个数总是一大一小交替出现的情况并不多。
蒂姆排序就是利用了数据的这个特性来减少排序中的比较和数据移动的。
实际应用时蒂姆排序要比归并排序快几倍
蒂姆排序速度和快速排序基本相当
蒂姆排序是一种稳定的排序算法,便于多列列表的排序,今天应用非常广泛
假定有25名短跑选手比赛争夺前三名,赛场上有五条赛道,一次可以有五名选手同时比赛。比赛并不计时,只看相应的名次。假设选手的发挥是稳定的,也就是说如果约翰比张三跑得快,张三比凯利跑得快,约翰一定比凯利跑得快。最少需要几次比赛才能决出前三名?(在第6章给出了这一问题的解答。(难度系数3颗星))[5]
所以最少赛7场就能决出前三名。
# python
import numpy as np
def merge_sort(nums): # 归并排序,后面题目代码偷懒会用到,所以这里列出来了
if len(nums) <= 1:
return nums
mid = len(nums) // 2
left = merge_sort(nums[: mid])
right = merge_sort(nums[mid:])
return merge(left, right)
def merge(left, right): # 归并排序的一部分
result = []
i = 0
j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result += left[i:]
result += right[j:]
return result
def run(nums): # 比赛的代码
nums = np.array(nums) # 25名选手
nums = nums.reshape(5, 5) # 分成5组
sorts = []
for i in range(5):
sorts.append(merge_sort(list(nums[i]))) # 5次比赛(每组用归并排序从小到大排好序)
for i in range(5):
for j in range(i + 1, 5):
if sorts[i][0] > sorts[j][0]: # 根据每组的第一名将5组排序,1次比赛,决出第一名
temp = sorts[i]
sorts[i] = sorts[j]
sorts[j] = temp
second_third = [sorts[0][1], sorts[0][2], sorts[1][0], sorts[1][1], sorts[2][0]] # 选出可能是第二名,第三名的选手
second_third = merge_sort(second_third) # 1次比赛, 决出第二名和第三名(根据归并排序从小到大排序)
return sorts[0][0], second_third[0], second_third[1] # 返回前三名
# example
nums = [12, 9, 3, 23, 33, 30, 15, 13, 24, 7, 11, 4, 5, 1, 8, 6, 14, 16, 21, 31, 18, 20, 22, 2, 25]
print(merge_sort(nums)) # 归并排序排出的结果
print(run(nums)) # 7次比赛得出的最终结果(与上面归并排序的前三名相同)
如果有 N N N个区间 [ l 1 , r 1 ] , [ l 2 , r 2 ] , … , [ l N , r N ] [l_1, r_1], [l_2, r_2], …, [l_N, r_N] [l1,r1],[l2,r2],…,[lN,rN],只要满足下面的条件我们就说这些区间是有序的:存在 x i ∈ [ l i , r i ] x_i ∈ [l_i, r_i] xi∈[li,ri],满足 x 1 < x 2 < . . . < x N x_1
比如,[1, 4]、[2, 3]和[1.5, 2.5]是有序的,因为我们可以从这三个区间中选择1.1、2.1和2.2三个数。同时[2, 3]、[1, 4]和[1.5, 2.5]也是有序的,因为我们可以选择2.1、2.2和2.4。但是[1, 2]、[2.7, 3.5]和[1.5, 2.5]不是有序的。
对于任意一组区间,如何将它们进行排序?(难度系数3颗星) [6]
综上,发现1~3中存在交集的区间排序与否都是可以的,但是分析4中的区间必须排序,所以如果用暴力解法,只需要将所有区间的左端点或右端点进行排序即可(上面的分析是基于少做无用功的原则,代码我写不出来T.T,真是一顿操作猛如虎,一看战绩0杠5…)代码如下:
# python
def interval_sort(nums):
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i][0] > nums[j][0]: # 这里选择左端点排序
temp = nums[i]
nums[i] = nums[j]
nums[j] = temp
return nums
# example
nums = [[1,2], [1.5, 4], [1, 3], [2, 9], [11, 12]]
print(interval_sort(nums))
参考:
[1] 插入排序代码实现:https://www.runoob.com/python3/python-insertion-sort.html
[2] 归并排序代码实现:https://www.jianshu.com/p/3ad5373465fd
[3] 堆排序代码实现:https://zhuanlan.zhihu.com/p/105624690
[4] 快速排序代码实现:https://www.runoob.com/python3/python-quicksort.html
[5] 赛跑问题:https://blog.csdn.net/laozhuxinlu/article/details/51745463
[6] 区间排序:https://blog.csdn.net/sinat_40896008/article/details/126571560