merge sort 和 quick sort

归并排序 (Merge sort)

核心思想是:分治法(divide-and-conquer),时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)

首先,什么是分治法?
这里用一个经典问题来说明:假设给你27枚硬币,其中有一个假币会比真的轻一点,你有一个天平,如何仅测量三次来找到这枚假币呢?
答:先把硬币分成三等份每份9个,一次测量便可得知最轻的是哪9枚硬币。然后再平分三份每份3枚,测量,再平分三份……这样便可三次得出结果

归并排序是分治法的典型应用,这个算法顾名思义,就是要更高效地实现排序功能。

简单来说,假如有n个数,
两两一组排序,
四四分组排序,
八八

如此等比例扩大直到全部排序完毕。

看到这里可能感觉不出算法哪里有用了,下面用一个具体例子来说明:
普通解法:
给定list:[3,2,8,6,5,7]

  1. 拿3与其他数 n n n比较,如果3小 n n n就放在3右边,3大 n n n就放在3左边
    得到:[2,3,8,6,5,7] 对比次数:5
    2)拿2与其他数 n n n比较,如果3小 n n n就放在3右边,3大 n n n就放在3左边
    得到:[2,3,8,6,5,7] 对比次数:5
    3)拿8与其他数 n n n比较,如果8小 n n n就放在8右边,8大 n n n就放在8左边
    得到:[2,3,6,5,7,8] 对比次数:5
    4)拿6与其他数 n n n比较,如果6小 n n n就放在6右边,6大 n n n就放在6左边
    得到:[2,3,5,6,7,8] 对比次数:5
    排序结束

…如此下去把所有数都与其他数比对一遍,得到排序好的list,对比次数是 20 20 20, 最坏情况下是30次,时间复杂度是 O ( n 2 ) O(n^2) O(n2)

归并排序解法:
给定list:[3,2,8,6,5,7]

  1. 3与2、8与6、5与7 两两小范围排序
    得到:[2,3,6,8,5,7] 对比次数:3
  2. (2,3,6,8) 和(5,7) 进行小范围排序(这里注意,排序的细节其实需要了解,2、3和6、8之前是分别两组的,先取出每组的第一个也就是2、6对比,小的2放入第一位。然后再用第一组的下一个3与6对比,小的3放入第二位,然后6、8依次放入。看起来这步结果顺序没变,但是其实是做过比对操作的)
    得到:[2,3,6,8,5,7] 对比次数:2
  3. (2,3,6,8,5,7)进行排序 对比顺序:
    (2,5) —> [2]
    (3,5) —> [2,3]
    (6,5) —> [2,3,5]
    (6,7) —> [2,3,5,6]
    (8,7)—>[2,3,5,6,7,8]
    得到:[2,3,5,6,7,8] 对比次数:5

归并排序最后只用了10次就得到了排序好的list,效率确实要比普通的方法高,时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)
这里有个维基百科的动图十分的易于理解,我的例子也是从它而来
在这里插入图片描述
来源:维基百科

代码
def merge(left, right):
    # 合并两个有序列表
    res = []
    while len(left) > 0 and len(right) > 0:
        if left[0] < right[0]:
            res.append(left.pop(0))
        else:
            res.append(right.pop(0))
    if left:
        res.extend(left)
    if right:
        res.extend(right)
    return res

def mergeSort(arr):
    # 归并函数
    n = len(arr)
    if n < 2:
        return arr
    middle = n // 2
    left = arr[:middle] # 取序列左边部分
    right = arr[middle:]# 取序列右边部分
    # 对左边部分序列递归调用归并函数
    left_sort = mergeSort(left) 
    # 对右边部分序列递归调用归并函数
    right_sort = mergeSort(right)
    # 
    return merge(left_sort, right_sort)

快速排序 (Quick sort)

核心思想也是分治法(divide-and-conquer),时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),最坏情况下 O ( n 2 ) O(n^2) O(n2)

给定一个list,通过把选取一个基准值,把小于基准值和大于基准值的数分为两个list,然后再递归不断地分割排序。

步骤为:

  1. 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot),
  2. 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成,
  3. 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。

快速排序的不确定性就在基准值的选取上,如果基准值每次都能选到中间数,那么一共遍需 l o g n logn logn次分割,最终的时间复杂度也就是 O ( n l o g n ) O(nlogn) O(nlogn)。但是如果每次都选在了最大值或最小值,那么这次分割就失去了意义,分割后会得到一个n-1长度的list而不是两个 n / 2 n/2 n/2长度的list。这样最坏的情况时间复杂度将会是 n 2 n^2 n2
在这里插入图片描述
来源:维基百科

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值