冒泡排序
基本思想
- 两两比较相邻的关键字,最大(小)的元素会被交换到最后一位
- 对n个数据依次操作n-1轮,从而完成排序(每轮都找到一个最大/小值)
- 稳定性:元素相同时不做交换,是稳定的排序算法
复杂度
最好时间复杂度
O(n),当输入数组刚好是顺序的时候,只需要挨个比较一遍就行了,不需要做交换操作
最坏时间复杂度
O(n2),当数组刚好是完全逆序的时候,每轮排序都需要挨个比较 n 次,并且重复 n 次
平均时间复杂度
O(n2),当输入数组杂乱无章时,每轮排序都需要挨个比较 n 次,并且重复 n 次
空间复杂度
O(1),使用了交换法,不需要开辟额外的空间
代码
def BubbleSort(a):
len_a = len(a)
if len_a < 2:
return a
# 一共进行n-1轮找最大值
for i in range(len_a - 1):
# 每轮需要比较的数组长度
for j in range(len_a - i - 1):
if a[j] > a[j+1]:
a[j], a[j+1] = a[j+1], a[j]
return a
array_test = [12, 23, 54, 32, 11, 5, 73]
res = BubbleSort(array_test)
print(res)
快速排序
基本思想
- 挑选一个元素,作为基准(pivot)
- 所有比基准值小的放在基准前面,所有比基准大的放在基准后面,基准处于中间位置,这个称为分区操作
- 递归地,把小于基准值和大于基准值的区间里的数进行排序(也就是重复1,2)
- 稳定性:分区涉及到交换操作,所以不稳定
复杂度
最好时间复杂度
O(nlogn),每次都能选中中位数
最坏时间复杂度
O(n2),每次分区都选中了最小值或者最大值,得到不均等的两组,那么需要n次分区操作
平均时间复杂度
O(nlogn),大部分情况下,很难选到极端情况
空间复杂度
O(logn),额外空间开销在于暂存基准值
代码
def Partion(a, l, r):
# 以这个区间的最左边的数作为基准数
pivot = a[l]
# l和r指针分别指向左边大于基准数的数字和右边大于基准数的数字
while(l < r):
# 从右边开始寻找小于基准数的数字
while(l < r and a[r] >= pivot):
r -= 1
# 把这个数字放到基准数位置上
a[l] = a[r]
# 从左边开始寻找大于基准数的数字
while(l < r and a[l] <= pivot):
l += 1
a[r] = a[l]
# 最后l指向的位置是错误的,需要用基准数填上
a[l] = pivot
return l
def QuickSort(a, l, r):
if l < r:
# 找到基准数应该摆放的位置
p = Partion(a, l, r)
QuickSort(a, l, p - 1)
QuickSort(a, p + 1, r)
return a
array_test = [12, 23, 54, 32, 11, 5, 73]
l = 0
r = len(array_test) - 1
res = QuickSort(array_test, l, r)
print(res)
插入排序
基本思想
- 第一个元素可以认为已经有序
- 取下一个元素,在已排好序的元素序列中,从后往前扫描
- 如果该元素(已排序)大于新元素,则将该元素后移
- 重复3直到找到小于或等于新元素的位置,将新元素插入该位置,重复2-4
- 稳定性:元素相同时,插入到已排好序元素的后一位置,所以是稳定的
复杂度
最好时间复杂度
O(n),数组完全顺序,每次只用比较一次就能找到正确插入位置
最坏时间复杂度
O(n2),数组完全逆序,要比较1+2+3+…+n-1次才能找到正确插入位置,
平均时间复杂度
O(n2)
空间复杂度
O(1),额外开销在于每次都要保存要插入的那个元素,否则其他已排好序的元素后移时,要插入元素会发生丢失
代码
def InsertSort(a):
if len(a) < 2:
return a
for i in range(1, len(a)):
target = a[