11_python_Merge Sort and Quick Sort

归并排序

  • 归并排序:分治策略在排序中的应用
  • 归并排序是递归算法,思路是将数据表持续分裂为两半,对两半分别进行归并排序
    • 基本结束条件:数据表仅有1个数据项,自然是排好序的
    • 缩小规模:将数据表分裂为相等的两半,规模减为原来的二分之一
    • 调用自身:将两半分别调用自身排序,然后将分别排好序的两半进行归并,得到排好序的数据表

普通版代码

def mergeSort(alist):
    print("Splitting: ", alist)
    if len(alist) > 1:  #基本结束条件
        mid = len(alist) // 2
        lefthalf = alist[:mid]
        righthalf = alist[mid:]
        
        mergeSort(lefthalf)  #递归调用
        mergeSort(righthalf)
        
        i= j= k= 0
        while i < len(lefthalf) and j < len(righthalf):
            
            if lefthalf[i] < righthalf[j]:  #拉链式交错把左右半从小到大归并到把结果列表
                alist[k] = lefthalf[i]
                i = i + 1
            else:
                alist[k] = righthalf[j]
                j = j + 1
            k = k + 1
        
        while i < len(lefthalf):  #归并左半部剩余项
            alist[k] = lefthalf[i]
            i = i + 1
            k = k + 1
        
        while j < len(righthalf):  #归并右半部剩余项
            alist[k] = righthalf[j]
            j = j + 1
            k = k + 1
    print("Merging: ", alist)
    print("----------------------------------------")
    
alist = [9, 3, 0, 34, 10, 2, 1, 6, 8, 6]
mergeSort(alist)
Splitting:  [9, 3, 0, 34, 10, 2, 1, 6, 8, 6]
Splitting:  [9, 3, 0, 34, 10]
Splitting:  [9, 3]
Splitting:  [9]
Merging:  [9]
----------------------------------------
Splitting:  [3]
Merging:  [3]
----------------------------------------
Merging:  [3, 9]
----------------------------------------
Splitting:  [0, 34, 10]
Splitting:  [0]
Merging:  [0]
----------------------------------------
Splitting:  [34, 10]
Splitting:  [34]
Merging:  [34]
----------------------------------------
Splitting:  [10]
Merging:  [10]
----------------------------------------
Merging:  [10, 34]
----------------------------------------
Merging:  [0, 10, 34]
----------------------------------------
Merging:  [0, 3, 9, 10, 34]
----------------------------------------
Splitting:  [2, 1, 6, 8, 6]
Splitting:  [2, 1]
Splitting:  [2]
Merging:  [2]
----------------------------------------
Splitting:  [1]
Merging:  [1]
----------------------------------------
Merging:  [1, 2]
----------------------------------------
Splitting:  [6, 8, 6]
Splitting:  [6]
Merging:  [6]
----------------------------------------
Splitting:  [8, 6]
Splitting:  [8]
Merging:  [8]
----------------------------------------
Splitting:  [6]
Merging:  [6]
----------------------------------------
Merging:  [6, 8]
----------------------------------------
Merging:  [6, 6, 8]
----------------------------------------
Merging:  [1, 2, 6, 6, 8]
----------------------------------------
Merging:  [0, 1, 2, 3, 6, 6, 8, 9, 10, 34]
----------------------------------------

Pythonic版代码

  • extend()函数:用于在列表末尾一次性追加另一个序列中的多个值(用新表扩展原来的列表)
    • 语法:list.extend(seq)
    • 参数seq:待追加的元素列表
    • 返回值:没有返回值,但会在已存在的列表中添加新的列表内容。
aList = [123, 'xyz', 'zara', 'abc', 123];
bList = [2009, 'manni'];
aList.extend(bList)

print ("Extended List : ", aList )
Extended List :  [123, 'xyz', 'zara', 'abc', 123, 2009, 'manni']
def merge_sort(lst):
    
    #基本结束条件
    if len(lst) <= 1: 
        return lst
    
    #分解问题,并递归调用
    middle = len(lst) // 2
    left = merge_sort(lst[:middle])  #左半部分排序
    right = merge_sort(lst[middle:])  #右半部分排序
    
    #合并左右半部,完成排序
    merged = []
    while left and right:
        if left[0] <= right[0]:
            merged.append(left.pop(0))
        else:
            merged.append(right.pop(0))
    merged.extend(right if right else left)
    
    return merged

alist = [9, 3, 0, 34, 10, 2, 1, 6, 8, 6]
merge_sort(alist)
[0, 1, 2, 3, 6, 6, 8, 9, 10, 34]

时间复杂度分析

  • 归并排序的时间复杂度为:O(nlog n)
  • 将归并排序分为两个过程来分析:分裂和归并
    • 分裂:借鉴二分查找中的分析结果,是对数复杂度,时间复杂度为:O(log n)
    • 归并:相对于分裂的每个部分,其所有数据项都会被比较和放置一次,所以是线性复杂度,其时间复杂度为:O(n)
    • 综合考虑,每次分裂的部分都进行一次O(n)的数据项归并,总的时间复杂度是:O(nlog n)

  • 归并排序使用两个切片操作,为了时间复杂度分析精度,可以通过取消切片操作,改为传递两个分裂部分的起始点和终止点,也是没问题的,只是会降低算法可读性。
  • 归并排序算法使用了额外1倍的存储空间用于归并

快速排序

  • 快速排序的思路是依据一个‘中值’数据项把数据表分为两半:小于中值的一半和大于中值的一半,然后每部分分别进行快速排序(递归)。
    • 基本结束条件:数据表仅有1个数据项,自然是排好序的
    • 缩小规模:根据中值,将数据表分为两半,最好情况是相等规模的两半
    • 调用自身:将两半分别调用自身进行排序(排序基本操作在分裂过程中)

  • 中值取值:如果希望这两半拥有相等数量的数据项,则应该找到数据表的中位数,但是找中位数需要计算开销!要想没有开销,只能随意找一个数来充当中值(一般找列表中的第一个数据项)
  • 分裂数据表的目标:找到‘中值’的位置
  • 手段:
    • 设置左右标,左标向右移动,右标向左移动
      • 左标一直向右移动,碰到比中值大的就停止
      • 右标一直向左移动,碰到比中值小的就停止
      • 然后把左右标所指的数据项交换
    • 继续移动,直到左标移动到右标的右侧,停止移动
    • 此时,右标所指位置就是‘中值’应处的位置,将中值和这个位置的数据项交换
    • 分裂完成,左半部分比中值小,右半部分比中值大

代码

def quickSort(alist):
    quickSortHelper(alist, 0, len(alist) - 1)

def quickSortHelper(alist, first, last):
    if first < last:   #基本结束条件
        #分裂
        splitpoint = partition(alist, first, last)
        #递归调用
        quickSortHelper(alist, first, splitpoint - 1)
        quickSortHelper(alist, splitpoint + 1, last)

def partition(alist, first, last):
    pivotvalue = alist[first] #选定中值
    
    leftmark = first + 1  #左右标初始值
    rightmark = last
    
    done = False
    while not done:
        while leftmark <= rightmark and alist[leftmark] <= pivotvalue:  #向右移动左标
            leftmark = leftmark + 1
        
        while rightmark >= leftmark and alist[rightmark] >= pivotvalue:  #向左移动右标
            rightmark = rightmark - 1
        
        if rightmark < leftmark: #两标相错就结束移动
            done = True
        else:
            alist[leftmark],alist[rightmark] = alist[rightmark],alist[leftmark]   #左右标的值交换
    
    alist[first],alist[rightmark] = alist[rightmark],alist[first]  #中值就位
    
    return rightmark  #中值点,即分裂点
        
alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
quickSort(alist)
print(alist)
[17, 20, 26, 31, 44, 54, 55, 77, 93]

时间复杂度分析

  • 快速排序的时间复杂度为:O(nlog n)
  • 快速排序过程分为两部分:分裂和移动
    • 分裂:如果分裂总能把数据表分为相等的两部分,那么就是O(log n)的复杂度
    • 移动:移动需要将每项都与中值进行比对,还是O(n)

  • 优点:算法运行过程中不需要额外的存储空间
  • 缺点:如果中值所在的分裂点过于偏离中部,造成左右两部分数量不平衡。极端情况下,有一部分始终没有数据,这样时间复杂度退化为O(n^2)。还需要加上递归调用的开销(比冒泡排序还糟糕)。

  • 改进中值的选取方法,让中值更有代表性
    • 三点取样:从数据表的头、尾、中间选出中值。=>会产生额外计算开销,仍然不能排除极端情况

排序算法总结

算法总结参考网址
在这里插入图片描述

  • 排序算法有时候并不存在绝对的优劣,尤其是时间复杂度相同的算法们。

  • 要在特定的应用场合取得最高排序性能的话,还需要对数据本身进行分析,针对数据的特性来选择相应排序算法。

  • 有时空间复杂度也是需要考虑的关键因素。或许还要考虑包括运行环境要求、处理数据对象的特性等各方面的因素。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python的排序算法的时间复杂度可以根据具体的实现方式有所不同。下面是几种常见的排序算法及其时间复杂度: 1. 冒泡排序(Bubble Sort):最坏情况下的时间复杂度为O(n^2),平均情况下的时间复杂度也为O(n^2)。 2. 选择排序(Selection Sort):最坏情况下的时间复杂度为O(n^2),平均情况下的时间复杂度也为O(n^2)。 3. 插入排序(Insertion Sort):最坏情况下的时间复杂度为O(n^2),平均情况下的时间复杂度也为O(n^2)。 4. 快速排序(Quick Sort):最坏情况下的时间复杂度为O(n^2),平均情况下的时间复杂度为O(nlogn)。 5. 归并排序(Merge Sort):最坏情况下的时间复杂度为O(nlogn),平均情况下的时间复杂度也为O(nlogn)。 6. 堆排序(Heap Sort):最坏情况下的时间复杂度为O(nlogn),平均情况下的时间复杂度也为O(nlogn)。 7. 希尔排序(Shell Sort):最坏情况下的时间复杂度取决于增量序列的选择,平均情况下的时间复杂度为O(n^1.3)。 8. 计数排序(Counting Sort):时间复杂度为O(n+k),其中k是待排序数组中的最大值。 9. 桶排序(Bucket Sort):时间复杂度为O(n+k),其中k是桶的数量。 10. 基数排序(Radix Sort):时间复杂度为O(d(n+k)),其中d是数字的位数,k是每个位数可能的取值范围。 需要注意的是,这些排序算法的时间复杂度都是基于比较的排序算法。非基于比较的排序算法,例如计数排序、桶排序和基数排序,可以在特定情况下达到线性时间复杂度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值