数据结构与算法之快速排序及其改进(可视化)

1 快速排序

1.1 快速排序原理

  快速排序的思路很简单就是在无序数组中选择一个锚点anchor,然后遍历数组将小于anchor的元素放置到锚点数据的左边,将大于anchor的元素放置到锚点的右边。具体实现中是左右轮流遍历,遍历时左边找到大于anchor的元素,右边找到小于anchor的元素二者交换即可。具体实现中和上面的有出入但是大概的思想就是这样。

1.2 流程

在这里插入图片描述

1.3 流程可视化

在这里插入图片描述

1.4 实现

def partition(l, start, end):
    '''
    @brief  选择一个锚点将所有元素分割为两部分[start, end],以第一个元素为锚点
    @param  l   list
    @param  start
    @param  end
    @return 锚点
    '''
    if end <= start:
        return end
    
    value = l[start]
    i = start
    j = end
    while i < j:
        while l[j] > value and i < j:
            j -= 1
            
        l[i] = l[j]
        while l[i] <= value and i < j:  #结束之后i指向的是大于value的index
            i += 1
            
        l[j] = l[i]
    l[i] = value
    return i
    
    
def quick_sort(l, start, end):
    '''
    @brief  快速排序[start, end]
    @param  l   list
    @param  start    左边边界
    @param  end   右边边界
    '''
    if end <= start:
        return

    anchor = partition(l, start, end, hook_func)
    quick_sort(l, start, anchor - 1)        
    quick_sort(l, anchor + 1, end)  

2 快速排序优化

2.1 普通的优化方法

  这部分的优化方法类似于归并等排序算法的优化方式:当子数组小于一定阈值时启用插入排序。

def quick_sort_II(l, start, end):
    '''
    @brief  快速排序[start, end),当问题规模足够小时,使用插入排序代替
    @param  l   list
    @param  start    左边边界
    @param  end   右边边界
    '''
    if start >= end:
        return
        
    if end - start < 16:
        insert_sort.insert_sort(l, start, end, None)
        return
        
    anchor = partition(l, start, end)
    quick_sort(l, start, anchor - 1)        
    quick_sort(l, anchor + 1, end) 

2.2 三取样切分

  快速排序是将数组划分为小于锚点的子数组和大于锚点的子数组两部分。三取样切分则将数组划分为三部分:小于锚点的部分,等于锚点的部分,大于锚点的部分。

def partition3way(l, start, end):
    '''
    @param  三取样切分,将整个数组分成,小于当前元素,等于当前元素和大于当前元素三个部分[start, end)
    @param  l
    @param  start
    @param  end
    @note   [start, lt]  lower
            [lt + 1, i - 1] equal
            [gt + 1, end]   bigger
    '''
    value = l[start]    
    i = start
    lt = start - 1
    gt = end
    while i <= gt:
        if l[i] < value:
            l[i], l[lt + 1] = l[lt + 1], l[i]
            lt += 1
        elif l[i] > value:
            l[i], l[gt] = l[gt], l[i]
            gt -= 1
        else:
            i+= 1
    
    return lt, gt + 1   
        
        
def quick_sort3way(l, start, end):
    '''
    @brief  快速排序[start, end),当问题规模足够小时,使用插入排序代替
    @param  l   list
    @param  start    左边边界
    @param  end   右边边界
    '''
    if start < end:
        lt, gt = partition3way(l, start, end)
        quick_sort3way(l, start, lt)
        quick_sort3way(l, gt, end)

2.3 三切分取样优化

  针对三切分取样的一个优化便是先将数组划分为等于anchor的区域,小于anchor的区域,未处理区域,大于anchor的区域,等于anchor的区域,然后在最后将以上内容合并为三部分小于anchor的区域,等于anchor的区域,大于anchor的区域。

def partition3way_faster(l, start, end):
    '''
    @param  三取样切分,将整个数组分成,小于当前元素,等于当前元素和大于当前元素三个部分[start, end)
    @param  l
    @param  start
    @param  end
    @note   将start和end分为四段=anchor,<anchor,>anchor,=anchor    [start,p,i,j,q,end]
            [start, p)   =v
            [p, i)      <v
            [i,j)       ?
            [j,q)       >v
            [q,end)   =v
    '''
    anchor = l[start]
    i = start + 1
    j = end
    p = i
    q = j
    
    while i < j:
        if l[i] == anchor:
            l[i], l[p] = l[p], l[i]
            i += 1
            p += 1
        elif l[i] < anchor:
            i += 1
        else:
            l[i], l[j] = l[j], l[i]
            j -= 1
            
        if i >= j:
            break
            
        if [j] == anchor:
            l[j], l[q] = l[q], l[j]
            j -= 1
            q -= 1
        elif l[j] < anchor:
            j -= 1
        else:
            l[i], l[j] = l[j], l[i]
            i += 1
        
        #结束之后
        #[start, p, ij, q, end]
        while p != start:
            l[p - 1], l[i - 1] = l[i - 1], l[p - 1]
            p = p - 1
            i = i - 1
        
        while q != end:
            l[q + 1], l[j + 1] = l[q + 1], l[j + 1]
            q = q + 1
            j = j + 1
            
    return i, j
    
    
def quick_sort3way_faster(l, start, end):
    '''
    @brief  快速排序[start, end),当问题规模足够小时,使用插入排序代替
    @param  l   list
    @param  start    左边边界
    @param  end   右边边界
    '''
    if start < end:
        lt, gt = partition3way(l, start, end)
        quick_sort3way_faster(l, start, lt)
        quick_sort3way_faster(l, gt, end)

2.4 n取样切分

  快速排序的一个优化方向就是寻找更好的anchor方便划分,其中一个办法就是随机选取n个数,选择这n个数的中位数作为anchor。

def partition5sample(l, start, end, sample_no=5):
    '''
    @brief  五取样切分使用从数组中采样的五个数的中位数进行parition
    @param  l   
    @param  start
    @param  end
    @param  sample_no   采样数
    '''
    def get_mid(l, part_list):
        '''
        @brief  返回中位数对应的value在l中的索引,如果l的数组为偶数则选择len(l)/2
        '''
        part_values = [l[i] for i in part_list]
        mid_value = part_values[int(len(part_values)/2)]
        for i in part_list:
            if l[i] == mid_value:
                return i, mid_value
    
    part_index_list = [random.randint(start, end) for i in range(sample_no)]
    anchor, _ = get_mid(l, part_index_list)
    
    l[anchor], l[start] = l[start], l[anchor]
    
    return partition(l, start, end, hook_func)
    
    
def quick_sort5sample(l, start, end):
    '''
    @brief  快速排序[start, end),parition时使用从数组中随机采样的五个数的中位数进行切分
    @param  l   list
    @param  start    左边边界
    @param  end   右边边界
    '''
    if start < end:
        anchor = partition5sample(l, start, end)
        quick_sort5sample(l, start, anchor - 1)
        quick_sort5sample(l, anchor + 1, end)

2.5 快速排序的非递归版本

def quick_sort_cyc(l, start, end):
    '''
    @brief  快速排序非递归版本
    @param  l   list
    @param  start
    @param  end
    @note   建立一个容器,容器的每个元素是一个tuple表示需要进行partition的start和end
    '''
    rst = stack.stack()
    snd = stack.stack()
    rst.push((start, end))
    while not rst.empty():
        while not rst.empty():
            start, end = rst.pop()
            if end < start:
                continue
                
            anchor = partition(l, start, end, hook_func)
            snd.push((start, anchor - 1))
            snd.push((anchor + 1, end))
            
        rst, snd = snd, rst

3 性能

3.1 不同版本性能对比

数据分布性能对比
无序在这里插入图片描述
有序在这里插入图片描述
逆序在这里插入图片描述
90%有序在这里插入图片描述
高斯分布在这里插入图片描述
泊松分布在这里插入图片描述
所有元素相同在这里插入图片描述
数组中只有两个值在这里插入图片描述

3.2 算法复杂度

算法时间复杂度空间复杂度
快速排序 O ( n l o g n ) O(nlog{n}) O(nlogn) O ( l o g n ) O(log n) O(logn)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值