排序算法总结

149 阅读5分钟

排序算法总结

选择排序

说明

每一趟找到当前最小的值,和未排序的地方交换, 如:
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. 快速排序 是里面比较快的排序算法

参考资料:

  1. [左神算法_2.1.认识复杂度和简单排序算法_bilibili](www.bilibili.com/video/BV1YL…