排序
基本的排序算法
- 冒泡排序
- 插入排序
常考的排序算法 - 归并排序
- 快速排序
- 拓扑排序
其他排序算法
- 堆排序
- 桶排序
冒泡排序
每一轮,从杂乱无章的数组头部开始,每两个元素比较大小并进行交换;指导这一轮当中最大或者最小的元素被放置在数组的尾部;然后,不断地重复这个过程,直到所有元素都排好位置
例题:给定数组[2,1,7,9,5,8],要求按照从左到右,从小到大的顺序进行排序
python代码对冒泡排序的实现如下:
from typing import List
def bubble_sort(li: List[int]) -> List[int]:
for i in range(len(li) - 1):
for j in range(len(li) - 1 - i):
if li[j] > li[j + 1]:
li[j], li[j + 1] = li[j + 1], li[j]
return li
if __name__ == '__main__':
test_li = [2, 1, 7, 9, 5, 8]
print(bubble_sort(test_li))
空间复杂度:O(1)
假设数组的元素个数是n,整个排序的过程中,直接在给定的数组进行元素的两两交换
时间复杂度:O(n^2)
情景一:给定的数组已经排好序,只需要进行n-1次的比较,两两交换次数为0,时间复杂度为O(n),这是最好的情况
情景二:给定的数组按照逆序排列,需要进行n(n-1)/2次比较,时间复杂度为O(n^2),这是最坏的情况
情景三:给定的数组杂乱无章,在这种情况下,平均时间复杂度是O(n^2)
插入排序
与冒泡排序的对比
在冒泡排序中,经过每一轮的排序处理后,数组后端的数是排好序的;
在插入排序中,经过每一轮的排序处理后,数组前端的数都是排好序的
插入排序的算法思想
不断地将尚未排好序的数插入到已经排好序的部分
python代码对插入排序的实现如下:
from typing import List
def insert_sort(li: List[int]) -> List[int]:
for i in range(1, len(li)):
for j in range(i, 0, -1):
if li[j] < li[j-1]:
li[j], li[j-1] = li[j-1], li[j]
return li
if __name__ == '__main__':
test_li = [2, 1, 7, 9, 5, 8]
print(insert_sort(test_li))
空间复杂度:O(1)
假设数组的元素个数是n,整个排序的过程中,直接在给定的数组里进行元素的两两交换
时间复杂度:O(n^2)
情景一:给定的数组已经排好序,只需要进行n-1次的比较,两两交换次数为0,时间复杂度为O(n),这是最好的情况
情景二:给定的数组按照逆序排列,需要进行n(n-1)/2次比较,时间复杂度为O(n^2),这是最坏的情况
情景三:给定的数组杂乱无章,在这种情况下,平均时间复杂度是O(n^2)
归并排序
分治的思想:归并排序的核心思想是分治,把一个复杂问题拆分成若干个子问题来求解
归并排序的算法思想
把数组从中间分成两个子数组;
一直递归地把子数组划分成更小地子数组,直到子数组里面只有一个元素;
一次按照递归的返回顺序,不断地合并排好序地子数组,直到最后把整个数组地顺序排好
python代码对归并排序的实现如下:
from typing import List
def merge_sort(lst: List[int]) -> List[int]:
if len(lst) <= 1:
# 当列表元素只有一个的时候,直接返回
return lst
mid = len(lst) // 2
left = lst[:mid]
right = lst[mid:]
left = merge_sort(left)
right = merge_sort(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
if __name__ == "__main__":
test_li = [2, 1, 7, 9, 5, 8]
print(merge_sort(test_li))
时间复杂度:T(n)
归并算法是一个不断递归的过程,假设数组的元素个数是n
时间复杂度是T(n)的函数:T(n)=2*T(n/2)+O(n)
怎么解这个公式呢?
对于规模为n的问题,一共要进行log(n)层的大小切分;
每一层的合并复杂度都是O(n)
所以整体的复杂度就是O(nlogn)
空间复杂度:O(n)
由于合并n个元素需要分配一个大小为n的额外数组,合并完成之后,这个数组的空间就会被释放
快速排序
基本思想
快速排序也采用了分治的思想;
把原始的数组筛选成较小和较大的两个子数组,然后递归地排序两个子数组;
在分成较小和较大地两个子数组过程中,如何选定一个基准值尤为关键
最有情况下地时间复杂度
T(n)=2*T(n/2)+O(n)
O(n)是怎么得出来得
把规模大小为n的问题分解为n/2的两个子问题;
和基准值进行n-1次比较,n-1次比较的复杂度就是O(n);
快速排序的复杂度也是O(nlogn)
最复杂的情况
每次在选择基准值的时候;
都不幸选择了子数组里面的最大或者最小值;
其中一个子数组长度为1;
另一个长度只比父数组少1
空间复杂度:O(logn)
和归并排序不同,快速排序在每次递归的过程中;
只需要开辟O(1)的存储空间来完成交换操作实现直接对数组的修改;
而递归次数为logn,所以它的整体空间复杂度完全取决于压堆栈的次数
python代码对快速排序的实现如下:
from typing import List
def quick_sort(li: List[int]) -> List[int]:
if len(li) < 2:
return li
else:
pivot = li[0] # 递归条件,基准
less = [i for i in li[1:] if i <= pivot]
greater = [i for i in li[1:] if i > pivot]
return quick_sort(less) + [pivot] + quick_sort(greater)
if __name__ == '__main__':
test_li = [2, 1, 7, 9, 5, 8]
print(quick_sort(test_li))
拓扑排序
应用场合
拓扑排序就是将图论里的顶点按照相连的性质进行排序
前提
必须是有向图
图里没有环
例:有一个学生想要修完5门课程的学分,这5们课程分别用1、2、3、4、5来表示,现在已知学习这些课程有如下的要求:
- 课程2和4依赖于课程1
- 课程3依赖于课程2和4
- 课程4依赖于课程1和2
- 课程5依赖于课程3和4
那么,这个学生应该按照怎样的顺序来学习这5门课程呢?
时间复杂度:O(n)
统计顶点的入度需要O(n)的时间;
接下来每个顶点被遍历一次,同样需要O(n)的时间