[数据结构]排序算法
冒泡排序
直接插入排序
选择排序
快速排序
归并排序
希尔排序
堆排序
桶排序
时间复杂度:快希以nlog2n的速度归堆
稳定性:考研情绪不稳定,快些选一堆好友
归并排序
原理
归并排序的原理是这样的,比如一开始我们有数组[9,6,3,2,5,8,7,4,1,0]
然后我们开始平分并递归
[9,6,3,2,5,8,7,4,1,0]
[9,6,3,2,5] [8,7,4,1,0]
[9,6,3] [2,5] [8,7,4] [1,0]
[9,6] [3] [2] [5] [8,7] [4] [1] [0]
[9] [6] [3] [2] [5] [8] [7] [4] [1] [0]
----递归返回----
[6,9] [2,3] [5,8] [4,7] [0,1]
[2,3,6,9] [4,5,7,8] [0,1]
[2,3,4,5,6,7,8,9] [0,1]
[1,2,3,4,5,6,7,8,9]
时间复杂度分析
由于我们每次都是平分区间,所以可以看成一棵二叉树,树的高度就是log2^n,
(这里出现了第一个问题,如果我们不是每次平分树的高度就会变高,例如每次都是左边分一个,右边分剩下的,树的高度就会变成n)
等到每个区间分到只剩下一个元素,我们开始递归返回,
在最下面一层,我们要把所有元素两两合并,这个操作要执行n/2次,要对n个元素都判断大小,所以这一层的时间复杂度是O(n)
在倒数第二层,我们要把所有元素两两合并,这个操作要执行n/4次,要对n个元素都判断大小,所以这一层的时间复杂度是O(n)
...
在第二层,我们要把所有元素两两合并,这个操作要执行2次,要对n个元素都判断大小,所以这一层的时间复杂度是O(n)
在第一层,我们要把所有元素两两合并,这个操作要执行1次,要对n个元素都判断大小,所以这一层的时间复杂度是O(n)
综上这个递归树log2^n层,每层复杂度O(n)
总的复杂度,O(nlog2^n)
空间复杂度分析
空间复杂度不能像时间复杂度一样累加,因为当一层递归返回的时候,它占用的内存就会被释放,所以我们来看最大的内存占用其实是最后一次合并操作的时候,我们需要一个长度为n的数组一下把所有的元素都装进去,所以空间复杂度是O(n)
这个递归的栈空间消耗也要考虑,栈空间的消耗等于树的高度,因为递归的深度最高就是树的深度,所以是O(nlog2^n)
实现
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
def merge(nums1, nums2):
if not nums1 and not nums2:
return None
if not nums1:
return nums2
if not nums2:
return nums1
len1 = len(nums1)
len2 = len(nums2)
index1 = 0
index2 = 0
res = []
while index1 <= len1 - 1 and index2 <= len2 - 1:
if nums1[index1] <= nums2[index2]:
res.append(nums1[index1])
index1 += 1
else:
res.append(nums2[index2])
index2 += 1
while index1 <= len1 - 1:
res.append(nums1[index1])
index1 += 1
while index2 <= len2 - 1:
res.append(nums2[index2])
index2 += 1
return res
def mergesort(nums):
length = len(nums)
if length == 0:
return None
if length == 1:
return nums
if length > 1:
return merge(
mergesort(nums[: length // 2]), mergesort(nums[length // 2 :])
)
return mergesort(nums)
快速排序
原理
现在我们又有一个数组[7,5,3,8,9,6,2,4,1,0]
第一次我们随机选取一个数字 3 作为枢纽元素分割数组
[2,1,0] [3] [7,5,8,9,6,4]
对两侧数组同样进行分割,
[0] [1] [2] [3] [4] [5] [7,8,9,6]
长度为一直接返回,超过一的继续分割
[0] [1] [2] [3] [4] [5] [7] [8,9,6]
[0] [1] [2] [3] [4] [5] [7] [6] [8] [9]
---递归返回---
[0,1,2] [3,4] [5,6] [7,8,9]
[0,1,2,3,4] [5,6,7,8]
[0,1,2,3,4,5,6,7,8,9]
快速排序是先排序再划分,二叉树返回的时候直接无脑合并
归并排序是先划分,二叉树在返回的时候排序
时间复杂度分析
很显然这个树的高度受到每次是否平均划分的严重影响,在归并排序中我们强制每次平分,但是在快速排序中我们不能保证选出来的枢轴元素正好是二分点,为了尽可能的二分,我们会随机选取枢轴元素,避免在原始数组本来就是递增或者递减的情况下,我们每次选出来的枢轴都在一端的尴尬情况。
在最好情况下,每次都二分,树的高度是log2^n,每一层的复杂度都是O(n),因为想要划分数组需要比较n次,复杂度是O(nlogn)
在最差情况下,每次都分成1和剩余其他,树的高度为n,每一层的复杂度都是O(n),因为想要划分数组需要比较n次,复杂度是O(n^2)
空间复杂度分析
快速排序是原地排序,这样看空间复杂度是O(1)
但是这个递归的栈空间消耗也要考虑,栈空间的消耗等于树的高度,因为递归的深度最高就是树的深度,所以在最好情况下是O(nlog2^n),最坏情况下是O(n)
实现
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
def quciksort(nums):
if not nums or len(nums) == 1:
return nums
pivot = random.choice(nums)
less = []
equal = []
bigger = []
for n in nums:
if n == pivot:
equal.append(n)
if n > pivot:
bigger.append(n)
if n < pivot:
less.append(n)
return quciksort(less) + equal + quciksort(bigger)
return quciksort(nums)
堆排序
原理
建堆:首先把元素摆放成一棵完全二叉树
调整堆:从最后一个非叶子结点开始向上调整,这个复杂度是O(n)
堆排序:取下根节点,把最后一个叶子结点替换上去,调整这个根节点,这个复杂度是O(nlog2n)
时间复杂度
o(nlog2n)

浙公网安备 33010602011771号