【排序篇】快速排序、快速选择 以及 python实现

本文详细介绍了快速排序算法的实现过程,包括基本逻辑、优化方法(单边递归和基准值选择),以及快速选择算法的应用,强调了时间复杂度的优化。同时提到了如何解决Top-K问题。
摘要由CSDN通过智能技术生成

【排序篇】快速排序、快速选择

一. 快速排序

1. 快速排序实现逻辑顺序

  1. 选择一个排序字段边界值作为基准值
  2. 将原数组分割成前后两个部分,保证前部分小于基准值,后部分大于基准值【partition 操作】
  3. 对基准值的左右两侧,递归地进行第一步和第二步

2. 快速排序实现示例

## 应注意程序单个函数的流程性,保证逻辑流畅
## 习惯判断程序函数的适用边界,当不满足函数使用时退出函数,防止程序出错或流程不直观
## refer_value 参数取值时需注意是在总列表里提取,而非新列表
def quick_sort(lists, start, end):
    
    if start >= end:
        return 
    left = start
    right = end
    ## 1. 选择一个排序字段边界值作为基准值
    refer_value = lists[left]

	## 2. partition 操作
    while left < right:
        while (left < right and lists[right] >= refer_value):
            right -= 1
        if (left < right):
            lists[left] = lists[right]
            left += 1
        while (left < right and lists[left] <= refer_value):
            left +=  1
        if (left < right):
            lists[right] = lists[left]
            right -= 1
    
    lists[left] = refer_value

    ## 3. 对基准值的左右两侧,递归地进行第一步和第二步
    quick_sort(lists, start, left - 1)
    quick_sort(lists, left+1, end)
    return

test_list = [0,2,3,4,5,6,7,8,9,7,5,3,32,1,1]
quick_sort(test_list, 0, len(test_list)-1)
print(test_list)

3. 快速排序优化

3.1 单边递归优化
3.1.1 优化思想

主要思想通过减少函数调用减少算法时间,通过在同一个函数内调用同一侧的排序函数,从而减少函数调用时间

3.1.2 优化实现代码
## partition 操作
def partition(lists, left, right):
    refer_value = lists[left]
    while left < right:
            while (left < right and lists[right] >= refer_value):
                right -= 1
            if (left < right):
                lists[left] = lists[right]
                left += 1
            while (left < right and lists[left] <= refer_value):
                left +=  1
            if (left < right):
                lists[right] = lists[left]
                right -= 1
    lists[left] = refer_value
    return left

def quick_sort(lists, start, end):        
    while (start < end):
        idx = partition(lists, start, end)
        ## 3. 对基准值的左右两侧,递归地进行第一步和第二步
        quick_sort(lists, idx+1, end)
        end = idx - 1
    return

test_list = [0,2,3,4,5,6,7,8,9,7,5,3,32,1,1]
quick_sort(test_list, 0, len(test_list)-1)
print(test_list)
3.2 基准值选取优化
3.2.1 优化思想

合适的基准值可以减少赋值,数值转换的操作。换言之,当我们找到列表中的最小值、最大值时,在执行partition操作时,我都将执行n次赋值操作,当我们找的直接越接近列表的中间值时,所需要执行的赋值操作越少

3.2.2 优化策略

1.三点去中法:取列表中最左边,中间,最右边的三个数,选取三个数的中等数当作基准值,后续将基准值与partition的临时基准值进行位置替换后,开始快速排序
在这里插入图片描述

3.2.3 优化实现代码

在测试代码中,数据量越大优化的效果越明显,且基准值选取优化普遍会降低测试时间

import random
import time

def quick_sort(lists, start, end):
    
    if start >= end:
        return 
    
    left = start
    right = end

    left_value = lists[left]
    right_value = lists[right]
    mid = int((left+right)/2)
    mid_value = lists[mid]

    refer_value = left_value + right_value + mid_value \
          - min(left_value, right_value, mid_value)  \
          - max(left_value, right_value, mid_value)

    if refer_value == right_value:
        lists[left], lists[right] = lists[right], lists[left]
    elif  refer_value == mid_value:
        lists[left], lists[mid] = lists[mid], lists[left]

    ## 1. 选择一个排序字段边界值作为基准值
    refer_value = lists[left]
        
    ## 2. partition 操作
    while left < right:
        while (left < right and lists[right] >= refer_value):
            right -= 1
        if (left < right):
            lists[left] = lists[right]
            left += 1
        while (left < right and lists[left] <= refer_value):
            left +=  1
        if (left < right):
            lists[right] = lists[left]
            right -= 1
    
    lists[left] = refer_value

    ## 3. 对基准值的左右两侧,递归地进行第一步和第二步
    quick_sort(lists, start, left - 1)
    quick_sort(lists, left+1, end)
    return

test_list = [random.randint(0, 1000000) for i in range(1000000)]
start_time = time.time()
quick_sort(test_list, 0, len(test_list)-1)
print(time.time() - start_time)
# print(test_list)
3.3 partition 操作优化
3.3.1 优化思想

通过减少程序实现中的比较操作,来减少时间花销,来提高程序的运行效率。不再采用选择值赋值的形式,而且采用选择一个大于基准值与一个小于基准值进行交换

3.3.2 实现代码
import random

def quick_sort(lists, start, end):
    
    if start >= end:
        return 
    
    left = start
    right = end

    left_value = lists[left]
    right_value = lists[right]
    mid = int((left+right)/2)
    mid_value = lists[mid]

    refer_value = left_value + right_value + mid_value \
          - min(left_value, right_value, mid_value)  \
          - max(left_value, right_value, mid_value)

    ## 2. partition 操作
    while left <= right:
        while (lists[right] > refer_value):
            right -= 1
        while (lists[left] < refer_value):
            left +=  1

        if (left <= right):
            lists[right], lists[left] = lists[left],  lists[right]
            left += 1
            right -= 1
    
    ## 3. 对基准值的左右两侧,递归地进行第一步和第二步
    quick_sort(lists, start, right)
    quick_sort(lists, left, end)
    return

test_list = [random.randint(0, 10) for i in range(10)]
print(test_list)
quick_sort(test_list, 0, len(test_list)-1)
print(test_list)
4. 三种优化合并实现方案
import random

## 优化版本
def quick_sort(lists, start, end):
    
    while (start < end):

        left = start
        right = end
        ## 基准值选择优化
        left_value = lists[left]
        right_value = lists[right]
        mid = int((left+right)/2)
        mid_value = lists[mid]
        refer_value = left_value + right_value + mid_value \
              - min(left_value, right_value, mid_value)  \
              - max(left_value, right_value, mid_value)

        ## partition 操作优化
        while left <= right:
            while (lists[right] > refer_value):
                right -= 1
            while (lists[left] < refer_value):
                left +=  1

            if (left <= right):
                lists[right], lists[left] = lists[left],  lists[right]
                left += 1
                right -= 1
        
        ## 单边优化
        ## 对基准值的左右两侧,递归地进行第一步和第二步
        quick_sort(lists, left, end)
        end = right
    return

test_list = [random.randint(0, 10) for i in range(10)]
print(test_list)
quick_sort(test_list, 0, len(test_list)-1)
print(test_list)

5. 参考知识点:

1.【partition 操作】 :也称为分割操作,遍历将小于基准值的元素放在基准值的前面,将大于基准值的元素放在基准值的后面
在这里插入图片描述

  1. 快速排序算法的时间复杂度不稳定,很有可能退化到最坏的时间复杂度 o ( n 2 ) o(n^2) o(n2)
6. 思考疑问&解答
  1. 为什么不采用单边遍历
    最终 L,R将会合并在中间分界索引上值,及refer_value最终应当保留的位置,根据partition操作,左部分应小于refer_value,右部分应大于refer_value。假使采用单边遍历方式,那么会涉及到更多的数据替换操作。
  2. 如何选择从start还是end开始遍历
    从左右开始遍历都可以,取决于refer_value所选择的位置索引。假使选择边索引为start的位置,应当从右边开始遍历;假使选择边索引为end的位置,应当从左边开始遍历。

二. 快速选择(Quick Selection)

解决问题:在一堆无序的数字中查找第k位元素

快速选择算法是基于快速排序算法的一种拓展算法,它可以在不对数据整体进行排序的前提下,快速找到排名第 k 位的元素,而且时间复杂度还能优化到 o ( n ) o(n) o(n)

1. 实现代码

def quick_sort(lists, start, end, k):
    if start >= end:
        return 
    left = start
    right = end
    ## 1. 选择一个排序字段边界值作为基准值
    refer_value = lists[left]

	## 2. partition 操作
    while left < right:
        while (left < right and lists[right] >= refer_value):
            right -= 1
        if (left < right):
            lists[left] = lists[right]
            left += 1
        while (left < right and lists[left] <= refer_value):
            left +=  1
        if (left < right):
            lists[right] = lists[left]
            right -= 1
    
    lists[left] = refer_value	
    
    if left == k-1:
        return 

    ## 3. 对基准值的左右两侧,递归地进行第一步和第二步
    ## 减少函数运行,基准值某一侧数据不需要排序
    if left > k -1 :
        quick_sort(lists, start, left - 1, k)
    else:
        quick_sort(lists, left+1, end, k)
    return 

test_list = [4,8,7,6,9,5,3,2,1]
k = 5
quick_sort(test_list, 0, len(test_list)-1, k)
print(test_list[k-1])

问题可延申至TOP-K问题

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值