排序算法总结
选择排序
说明
每一趟找到当前最小的值,和未排序的地方交换, 如:
a. 第一趟,找到 0~n-1的最小值,和0交换
b. 第二趟,找到1~n-1的最小值,和1交换
关键词:每趟找最小值,再交换
耗时分析
- 时间复杂度 O(n²),空间复杂度 O(1)
- 多次遍历比较,每趟找到最小值再交换,意味着要多次进行遍历比较;
- 无法利用已排序信息,相较于
快速排序,归并排序;
代码实现
def selectSort(nums):
n = len(nums)
if n < 2: return nums
for i in range(n):
curr_min = i
j = i
while j < n:
if nums[curr_min] > nums[j]: curr_min = j
j+=1
if i != curr_min:
nums[i], nums[curr_min] = nums[curr_min], nums[i]
return nums
冒泡排序
说明
两两比较,依次冒泡,如
a. 第一趟, 0~n-1,0,1比较,0大就交换,1,2比较,1大就交换.......,最后最大的到n-1
b. 第二趟,0~n-2, 0,1比较,0大就交换,1,2比较,1大就交换.......,最后最大的到n-2
关键词:两两比较,冒泡
耗时分析
- 时间复杂度 O(n²),空间复杂度 O(1)
- 多次遍历,由于每次只能确定最后一个位置;
- 多次比较交换,基于两两比较的核心,会进行多次比较交换
- 无法利用已排序信息,相较于
快速排序,归并排序; - 数据局部性差,由于频繁访问、更新数据,导致缓存利用率低,硬件执行效率低;
代码实现
def bubbleSort(nums)
n = len(nums)
if n<2: return nums
for i in range(n):
j = 0
while j < n-1-i
if num[j] > nums[j+1]:
nums[j], nums[j+1] = nums[j+1], nums[j]
j+=1
return nums
插入排序
说明
依次插入,依次有序,如:
a. 0-0有序,插入一个数,0-1有序,0-2有序.....
关键词:插入,依次有序
耗时分析
- 时间复杂度 O(n²),空间复杂度 O(1);
- 多次比较、移动,基于依次有序的核心,元素到合适位置会经过很多次的比较和移动;
- 数据局部性差,由于频繁更新,移动元素,导致缓存利用率低,硬件执行效率低;
代码实现
def insertSort(nums):
n = len(nums)
if n<2: return nums
for i in range(n):
curr = i
while curr-1>=0 and nums[curr-1]>nums[curr]:
nums[curr-1], nums[curr] = nums[curr], nums[curr-1]
curr -= 1
return nums
归并排序
说明
使用递归,左边有序,右边有序,merge合并,达到整体有序 分治思想,递归分割
耗时分析
- 时间复杂度 O(nlogn),空间复杂度 O(n);
- 递归分割,减小问题规模,可以充分利用多核并行处理;
- 稳定的时间复杂度,最突出的优势;
- 额外的内存,需要一个跟原数组等大的临时数组;
- 数据频繁复制,会消耗一定的时间和内存;
代码实现
def mergeSort(nums):
n = len(nums)
if n<2:return nums
def process(left, right):
if left == right: return
mid = left + (right-left) >> 1
process(left, mid)
procee(mid+1, right)
merge(left, mid, right)
def merge(left, mid, right):
help = []
p1 = left
p2 = mid+1
while p1<=mid and p2<=right:
if nums[p1] < nums[p2]:
help.append(nums[p1])
p1 += 1
else:
help.append(nums[p2])
p2 += 1
while p1 <= mid:
help.append(nums[p1])
p1 += 1
while p2 <= right:
help.append(nums[p2])
p2 += 1
# 拷贝回原数组
for i in range(len(help)):
nums[left+i] = help[i]
process(0, n-1)
快速排序
说明
可类比荷兰国旗问题
a. 随机 或 最后一个,定为 num 标准划分值
b. 划分成 左边小于区域,中间等于区域,右边大于区域
耗时分析
- 时间复杂度 O(nlogn) 但不稳定,空间复杂度 O(1);
- 分治策略,将一个大问题拆分成两个相同的小问题
- 原地排序,不需要使用额外的空间
代码实现
def quickSort():
n = len(nums)
if n<2: return nums
def process(left, right):
if left < right:
from randint import random
temp = randint(left, right)
nums[temp], nums[right] = nums[right], nums[temp]
# 与标准划分值相等的区域,【左边界,有边界】
area = partition(left, right)
process(left, area[0]-1) # 小于区域
process(area[1]+1, right) # 大于区域
def partition(left, right):
less = left -1
more = right
while left < more:
if nums[left] < nums[right]:
less += 1
nums[left], nums[less] = nums[less], nums[left]
left += 1
elif nums[left] > nums[right]:
more -= 1
nums[left], nums[more] = nums[more], nums[left]
else:
left += 1
# 将划分值与大于区域的第一个进行交换
nums[right], nums[more] = nums[more], nums[right]
return (less+1, more)
process(0, n-1)
堆排序
说明
进入堆结构(大根堆,小根堆)
a. 经历一整个 heapInsert, 全部元素进入堆结构;
b. 堆顶 和 堆最后位置交换,
c. 重新变成有效的大根堆结构,循环
关键词:堆
耗时分析
- 时间复杂度 O(nlogn),空间复杂度 O(1);
- 高效的数据结构,或是大根堆,或是小根堆,使用堆的数据结构;
- 原地排序,不需要使用额外的空间;
代码实现
def heapSort(nums):
n = len(nums)
if n<2:return nums
def heapInser(idx):
parent = (idx-1)//2 if (idx-1)//2>0 else 0
while nums[idx]>nums[parent]:
nums[parent], nums[idx] = nums[idx], nums[parent]
idx = parent
def heapify(idx, heap_size):
left = idx*2+1
while left < heap_size:
if left+1 < heap_size and nums[left+1] > nums[left]:
largest = left + 1
else:
largest = left
if nums[idx] > nums[largest]: largest = idx
if largest == idx: break
nums[idx], nums[largest] = nums[largest], nums[idx]
idx = largest
left = idx*2+1
for i in range(n):
heapInsert(i)
heap_size = n
nums[0], nums[n-1] = nums[n-1], nums[0]
heap_size -= 1
while heap_size > 0:
heapify(0, heap_size)
nums[0], nums[heap_size], nums[heap_size], nums[0]
heap -= 1
return nums 排序算法的总结和对比
上述排序算法的比较
| 排序算法名称 | 时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|
| 选择排序 | O(N^2) | O(1) | × |
| 冒泡排序 | O(N^2) | O(1) | √ |
| 插入排序 | O(N^2) | O(1) | √ |
| 归并排序 | O(N * logN) | O(N) | √ |
| 快速排序 | O(N * logN) | O(logN) | × |
| 堆排序 | O(N * logN) | O(1) | × |
说明
a. 目前没有找到 时间复杂度是 O(N * logN),空间复杂度是O(1),稳定的算法;
b. 目前最低的时间复杂度就是 O(N * logN);
c. 有得就会有付出,想要稳定,空间就要大一些,想要空间小,就不稳定,根据诗句情况进行选择;
d. 快速排序 是里面比较快的排序算法
参考资料:
- [左神算法_2.1.认识复杂度和简单排序算法_bilibili](www.bilibili.com/video/BV1YL…