排序算法(Python)

各类排序对比
排序方法稳定性最好复杂度最坏复杂度期望复杂度
冒泡排序稳定 O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2)
选择排序稳定 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2)
插入排序稳定 O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2)
快速排序不稳定 O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2) O ( n l o g n ) O(nlogn) O(nlogn)
归并排序稳定 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn)

冒泡排序

核心思路是与相邻的元素比较大小,交换位置
具体步骤可分为外循环和内循环:

  • 外循环每一步将数组中最大的元素“沉”到数组末尾(升序排列的情况下);
  • 内循环每一步按照大小交换相邻元素的位置。
原数组: [1,2,9,9,4,5,6,6,3,9,4]
内循环第一步:将元素1与元素2进行比较,不交换位置(升序);
内循环第二步:将元素2与元素9进行比较,不交换位置;
内循环第三步:将元素9与元素9进行比较,不交换位置;
内循环第四步:将元素9与元素4进行比较,交换位置为 [1,2,9,4,9,5,6,6,3,9,4];
内循环第五步:将元素9与元素5进行比较,交换位置为 [1,2,9,4,5,9,6,6,3,9,4];
内循环第六步:将元素9与元素6进行比较,交换位置为 [1,2,9,4,5,6,9,6,3,9,4];
...
内循环最后一步:将元素9与元素6进行比较,交换位置为 [1,2,9,4,5,6,6,3,9,4,9];

外循环第一步即为内循环的结果,将元素9沉到末尾,为[1,2,9,4,5,6,6,3,9,4,9];
外循环第二步:将第二个元素9沉到末尾,为[1,2,4,5,6,6,3,9,4,9,9];
外循环第三步:将第三个元素9沉到末尾,为[1,2,4,5,6,3,6,4,9,9,9];
外循环第四步:将第三个元素9沉到末尾,为[1,2,4,5,3,6,4,6,9,9,9];
...
外循环最后一步:得到最终结果[1,2,3,4,4,5,6,6,9,9,9]。

算法实现

冒泡排序是一种稳定排序
最优情况下,数组都为正序,时间复杂度为 O ( n ) O(n) O(n)
最坏情况为逆序,时间复杂度为 O ( n 2 ) O(n^2) O(n2)

def bubbleSort(nums):
    if len(nums) < 2:
        return nums
    
    # 因为后面index要加1进行比较,所以这里是len(nums) - 1
    for i in range(len(nums)-1):
    	# -i是已经将i个元素沉到末尾
        for j in range(len(nums)-i-1):
            if nums[j] > nums[j+1]:
                nums[j], nums[j+1] = nums[j+1], nums[j]
    return nums

选择排序

核心思想是将剩余元素中最小(最大)的元素取出来,放在首位(末尾)。
具体步骤为:

  • 遍历n个元素,找到其中最小的元素放在首位;
  • 遍历剩下的n-1个元素,找到其中最小的元素放在首位;
  • 重复上述步骤,直到数组有序。

算法实现

选择排序的时间复杂度跟初始状态无关,为 O ( n 2 ) O(n^2) O(n2)

def selectionSort(nums):
    if len(nums) < 2:
        return nums
    
    for i in range(len(nums)-1):
        min_index = i
        for j in range(i+1,len(nums)):
            if nums[j] < nums[min_index]:
                min_index = j
        nums[min_index], nums[i] = nums[i], nums[min_index]
    
    return nums

插入排序

核心思想是在已经部分有序的数组中,寻找合适的位置并插入新元素
具体步骤如下:

  • 从数组的第二个元素开时,判断与前一个元素的大小,进行位置交换;
  • 到第三个元素,前两个元素已经有序了,按大小寻找合适的位置,插入第三个元素;
  • 如此类推,直到所有的元素都处于合适的位置。

算法实现

插入排序是一种稳定排序,最优情况下,数组都为正序,时间复杂度为 O ( n ) O(n) O(n)。最坏情况为逆序,时间复杂度为 O ( n 2 ) O(n^2) O(n2)

def insertSort(nums):
    if len(nums) < 2:
        return nums
        
    for i in range(1,len(nums)):
        value = nums[i]
        j = i - 1
        while j >= 0 and nums[j] > value:
            nums[j+1] = nums[j]
            j -= 1
        nums[j+1] = value
    return nums

if __name__ == "__main__":
    nums = [1,2,9,9,4,5,6,6,3,9,4]
    print(insertSort(nums))
输出:[1, 2, 3, 4, 4, 5, 6, 6, 9, 9, 9]

快速排序

核心思想是分而治之。具体步骤如下:

  • 在数组中选择一个元素作为基准,可以取任意值,但一般取中值帮助理解;
  • 数组中所有的数组都与基准进行比较,比基准小就移动基准的左边,比基准大就移动到基准右边;
  • 以基准两边的子数组作为新数组,重复第一二步,直到子数组剩下一个元素。

分治思想的排序在处理大数据集的效果比较好,小数据性能差一些,规模达到一定小时改用插入排序。


算法实现

快排是一种不稳定排序其期望时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),最坏情况时间复杂度为 O ( n 2 ) O(n^2) O(n2)
快排的实现多种多样,选取一个最好理解的:分治+递归

def quickSort(nums):
    if len(nums) < 2:
        return nums
    
    mid = nums[len(nums)//2]
    left, right = [], []
    nums.remove(mid)
    for num in nums:
        if num >= mid:
            right.append(num)
        else:
            left.append(num)
    
    return quickSort(left) + [mid] + quickSort(right)

if __name__ == "__main__":
    nums = [1,2,9,9,4,5,6,6,3,9,4]
    print(quickSort(nums))
输出:[1, 2, 3, 4, 4, 5, 6, 6, 9, 9, 9]

归并排序

归并排序也应用了分治的思想,主要步骤如下:

  • 把初始序列的n个元素都看作是有序的子序列;
  • 进行两两归并,有序序列的长度增加一倍;
  • 重复上述步骤,直到得到一个长度为n的有序序列。

可以结合下图观察:

在这里插入图片描述

一开始将序列[38, 27, 43, 3, 9, 82, 10]分为7个长度为1的序列;
进行两两归并,得到4个有序序列[27,38],[3,43],[9,82],[10];
再次进行两两归并,得到两个有序序列[3,27,38,43],[9,10,82];
直到最后归并为一个长度为7的有序序列[3,9,10,27,38,43,82]

算法实现

归并排序是一种稳定排序,最好最差时间复杂度均为 O ( n l o g n ) O(nlogn) O(nlogn),因此期望复杂度也为 O ( n l o g n ) O(nlogn) O(nlogn)

def merge(left, right):
    res = []
    i, j = 0, 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            res.append(left[i])
            i += 1
        else:
            res.append(right[j])
            j += 1
    
    if i == len(left):
        res += right[j:]
    else:
        res += left[i:]
    
    return res

def mergeSort(nums):
    if len(nums) <= 1: return nums
   
    mid = len(nums)//2
    left = mergeSort(nums[:mid])
    right = mergeSort(nums[mid:])
    
    return merge(left, right)

简化写法

以.pop代替两个指针的操作(.pop时间复杂度为 O ( 1 ) O(1) O(1));
返回不判断 left 还是 right 为空,因为必然一个为空,另一个不为空;
left 和 right 有序,所以直接拼接起来即可。

def merge(left, right):
    res = []
    while left and right:
        if left[0] <= right[0]:
            res.append(left.pop(0))
        else:
            res.append(right.pop(0))
    
    return res + left + right

def mergeSort(nums):
    if len(nums) <= 1: return nums
    
    mid = len(nums)//2
    left = mergeSort(nums[:mid])
    right = mergeSort(nums[mid:])
    
    return merge(left, right)

2019.7.15 补充插入排序
2019.7.16 补充冒泡排序,选择排序,增加对比表格
2019.7.30 补充归并排序


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值