前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【从0到1学算法】快速排序

【从0到1学算法】快速排序

作者头像
KEN DO EVERTHING
发布于 2020-07-24 01:53:20
发布于 2020-07-24 01:53:20
51200
代码可运行
举报
文章被收录于专栏:KEN DO EVERTHINGKEN DO EVERTHING
运行总次数:0
代码可运行

今天我们将学习快速排序,是最快的排序算法之一,速度比选择排序快得多!

一、分而治之

在学习快速排序前,先上开胃菜,快速排序中用到的算法--分而治之(divide and conquer, D&C,分治法)。

只能解决一种问题的算法毕竟用处有限,而D&C提供了解决问题的思路。当面对新问题束手无策时,不妨自问:使用分而治之能解决吗?

来个示例:

假设你是农场主,有一块土地。

你要将这块土地均匀分成方块(正方形),且分出的方块要尽可能大。显然,下面的分法都不符合要求。

使用D&C解决问题分为两个步骤:

  1. 找出基线条件,这个条件必须尽可能简单。
  2. 不断将问题分解(或者说缩小规模),直到符合基线条件。

下面就来使用D&C找出解决方案。

首先,找出基线条件。最容易处理的情况是,一条边的长度是另一条的整数倍。

比如下图,一边是50m,另一边是25m,那么最大方块为25mx25m。

接下来是缩小问题规模,首先找出这块地可容纳的最大方块。

划出了两块640mx640m的方块,同时余下一小块地。接下来我们将继续对余下的小块地使用相同的算法。

适用于这小块地的最大方块,也适用于整块地的最大方块(可参阅欧几里算法)。问题规模缩小了,最初问题是均匀划分1680mx640m土地的问题,缩小为均匀划分640mx400m土地的问题。

接着继续缩小规模,直到满足基线条件。

余下的土地满足基线条件,160是80的整数倍,刚好将土地平分为两个方块。

因此,对于最初的土地,适用的最大方块为80mx80m。

这便是分治法,重申一下它的原理:

  1. 找出基线条件。(最简单的条件)
  2. 缩小规模,使其符合基线条件。
二、快速排序

快速排序是最快的排序算法之一,也是D&C的典范。

对排序算法来说,最简单的数组是什么样子的呢?就是根本不需要排序的数组。

因此,我们的基线条件为数组为空或只包含一个元素。

快速排序的步骤如下:

  1. 选择基准值。(可随机选择)
  2. 将数组分成两个子数组:小于基准值的元素和大于基准值的元素。(缩小问题规模,运用D&C)
  3. 对这两个子元素进行快速排序。(递归)
  4. 重复步骤2~3,直至子数组元素数量小于2,将子数组与基准合并(基线条件)。

换个思维想想,其实就是每轮都将基准放到正确的位置上,直至排序完成。

这里有个数组为[3,5,2,1,4]

假设选择3为基准,对子数组快速排序。

可能这里会有点懵,qsort([2,1])怎么操作?具体操作为如下(其实就是重复第一次的操作):

  1. 选择2为基准(随机)
  2. 分为两个子数组,[1],[]
  3. 然后将子数组与基准合并,[1]+[2]+[] = [1,2] 得到有序数组

代码实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def quick_sort(arr):
    # 基线条件
    if len(arr) < 2:
        return arr
    else:
        # 随机选择基准
        rd = random.randint(0, len(arr) - 1)
        pivot = arr.pop(rd)
        # 比基准小的数组
        less = []
        # 比基准大的数组
        greater = []
        for i in range(len(arr)):
            # 比基准小或等于的数放到less
            if arr[i] <= pivot:
                less.append(arr[i])
            # 比基准小或等于的数放到greater
            else:
                greater.append(arr[i])              
        # 递归,并与基准合并
        return quick_sort(less) + [pivot] + quick_sort(greater)

但我们在网上看到的,一般都不是这种写法,而是另一种,依然是开源节流的优化,不用新数组而是在数组中不断交换元素位置。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def quick_sort(arr, start, end):
    # 基线条件
    if start > end:
        return
    # 设置一个基准
    pivot = arr[start]
    left = start
    right = end
    # 递归条件
    while left != right:
        # 从右边开始遍历,小于等于基准时,准备将它与左侧大于基准值的值交换
        while arr[right] > pivot and left < right:
            right -= 1
        # 从右左开始遍历,大于基准时,准备将它与右侧小于基准值的值交换
        while arr[left] <= pivot and left < right:
            left += 1
        if left < right:
            # 将左侧大于基准值的值与右侧小于基准值的值交换
            temp = arr[left]
            arr[left] = arr[right]
            arr[right] = temp
    # 将基准放到正确位置上
    arr[start] = arr[left]
    arr[left] = pivot
    # 左边数组与右边数组继续快排
    quick_sort(arr, start, left - 1)
    quick_sort(arr, left + 1, end)

还是同样的配方:每轮都将基准放到正确的位置上,直至排序完成

扩展:基准的选择

快速排序的性能高度依赖于选择的基准值。

最坏情况下,每次划分成两个数组分别包含n-1个元素和1个元素,其时间复杂度为O(n2)。

在最好的情况下,每次划分所取的基准都恰好是中值,即每次划分都产生两个大小为n/2的数组。此时,快排的时间复杂度为O(nlogn)。

下面有3中基准选择方式

(1)固定基准(不推荐)

当待排数组有序或基本有序的情况下,很容易出现最坏情况,导致性能低下。

(2)随机基准(未知待排数组有序性时,推荐)

随机数算法随机选择一个元素作为划分基准,算法的平均性能较好,从而避免了最坏情况的多次发生。此时,它的平均运行时间为O(nlogn)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 在start和end间随机选择一元素作为划分的基准
def random(arr, start, end):  
    rd = random.randint(0, len(arr) - 1)
    pivot = arr[rd]
    # 把随机基准位置的元素和low位置元素互换
    # swap交换两个元素位置的函数,这里就忽略不写了
    swap(a[pivot],a[start])
    return a[low]

(3)3分取值(待排数组基本有序时,推荐)

选取数组开头,中间和结尾的元素,通过比较,选择中间的值作为快排的基准。

这种方式能很好的解决待排数组基本有序的情况,而且选取的基准没有随机性。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
def number_of_three(arr,start,end):
    # 右移相当于除以2
    mid = end + ((start - end) >> 1)
    if arr[mid] > arr[start]:
        # 把随机基准位置的元素和low位置元素互换
        swap(arr[mid], arr[start])
    if arr[end] > arr[start]:
        swap(arr[end], arr[start])
    if arr[mid] > arr[end]:
        swap(arr[mid], arr[end])
    # 此时,arr[mid] <= arr[end] <= arr[start]
    return arr[end]
总结
  • D&C(分治法)将问题逐步分解。对问题无头绪时,可尝试使用。
  • 快速排序是最快的排序算法之一,也是D&C的典范。
  • 未知待排数组有序性时,推荐使用随机基准; 待排数组基本有序时,推荐使用3分取值选取基准

THANDKS

- End -

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-03-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 KEN DO EVERTHING 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
【刷题】一篇文章搞定“位运算”
如果涉及到数组或者运算,经常就需要使用位运算来巧妙的解决这些问题。那么接下来我们来学习这个算法
叫我龙翔
2024/05/26
940
【刷题】一篇文章搞定“位运算”
「面试基础小册」数据类型及其延伸
「面试基础小册」系列正式开写。主要是对一些基础相关的知识进行归纳整理与拓展。后续还有更多,敬请期待
小皮咖
2020/07/03
7030
Java入门基础知识点总结(详细篇)
定义:被Java语言赋予了特殊含义,用做专门用途的字符串(单词) 特点:关键字中所有字母都为小写
全栈程序员站长
2022/09/08
4.8K0
Java入门基础知识点总结(详细篇)
暑假冲刺2023年信息学CSP-J/S(专项突破):精选8道【进制转换】历年真题并附带视频讲解
CSP-J/S的认证者报名就是参赛者报名于7月17号就要开始,离9月16号的第一轮认证考试只有2个月的时间,看似还有2个月时间,其实还是非常紧张的。
小码匠
2023/08/31
7780
Python0基础(中)——期末不挂科
虽然我不知道你是谁,但是还是感谢,今天熬个夜来再肝一篇,秋名山路很长,也希望我们能一起走下去!
秋名山码神
2022/12/13
5240
Python0基础(中)——期末不挂科
Python基础知识——(005)
“左移位”运算(<<)是将一个二进制数向左移动指定的位数,左边(高位端)溢出的位被丢弃,右边(低位端)的空位用0补充。
JOYCE_Leo16
2024/07/25
1170
Python基础知识——(005)
Java 语言基础 (初识Java语言, 变量和数据类型, 运算符, 流程控制语句, 数组)
ArrayIndexOutOfBoundsException 数组下标越界异常ArithmeticException 算术异常
RendaZhang
2020/09/08
4610
操作符详解(这么详细的操作符介绍你确定不看一看?)【C语言】【附试题详解】
操作符的分类:算数操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式、(下标引用、函数调用和结构成员)。
see.
2024/06/04
1210
操作符详解(这么详细的操作符介绍你确定不看一看?)【C语言】【附试题详解】
Java语言位运算符详解
很多编程语言都有位运算符,Java语言也不例外。在Java语言中,提供了7种位运算符,分别是按位与(&)、按位或(|)、按位异或(^)、取反(~)、左移(<<)、带符号右移(>>)和无符号右移(>>>)。这些运算符当中,仅有~是单目运算符,其他运算符均为双目运算符。在讲解这些运算符的使用之前,必须了解一个常识,那就是:位运算符是对long、int、short、byte和char这5种类型的数据进行运算的,我们不能对double、float和boolean进行位运算操作。下面就来详细讲解这7种位运算符的使用方法。
科技新语
2025/03/07
1440
Java语言位运算符详解
LeetCode通关:求次数有妙招,位运算三连
大家好,我是刷题困难户老三,这一节我们来刷几道很有意思的求次数问题,它们都有同一类非常巧妙的解法。
三分恶
2021/08/06
3740
Python全网最全基础课程笔记(四)——基本数据类型
基本数据类型是Python中最基础的数据类型,它们用于存储单个值。Python中的基本数据类型包括:
小白的大数据之旅
2024/11/20
2340
Python全网最全基础课程笔记(四)——基本数据类型
原生JS | 当兔子遇到鸡
HTML5学堂-码匠:当兔子遇到鸡,会怎样呢?先别急,看个小视频~ 视频内容 当兔子遇到鸡 —— 不要害怕和别人不一样,在这个世界上,你就是独一无二的自己! 不知道有多少程序在看到这个小视频的时候,想到的不是“复活节”彩蛋,而是“鸡兔同笼问题”…… 如果你想到的是“鸡兔同笼”,那么恭喜你,至少你不是一个人……(表示看到兔子从蛋里钻出来的时候,竟然完全没有怀疑 )。 鸡兔同笼问题 鸡兔同笼-起源 “鸡兔同笼问题”是我国古算书《孙子算经》中著名的数学问题,其内容是:“今有雉(鸡)兔同笼,上有三十五头,下有九十四
HTML5学堂
2018/03/13
2.1K0
原生JS | 当兔子遇到鸡
详解7类Python运算符及代码举例
运算符是运算法则的具体体现。Python提供了算术运算符、赋值运算符、比较运算符、逻辑运算符、位运算符、身份运算符和成员运算符7类运算符,从而实现了丰富多样的运算功能。
IT阅读排行榜
2019/10/11
1.2K0
每日面试题推送及讲解-20190409
第一题是对于Java运算符的考核,位运算符主要是针对二进制,整型(byte、char、short、int、long)数据类型的二进制进行的移位操作。运算符其中有以下几种:
每天学Java
2020/06/02
3310
【C语言系列】操作符的详解
我们可以看出C语言中大概有12种常见的操作符,而部分操作符是我们前面介绍过的,忘了的可以用以下链接来复习以下:https://blog.csdn.net/2301_80179750/article/details/141575238?fromshare=blogdetail&amp;sharetype=blogdetail&amp;sharerId=141575238&amp;sharerefer=PC&amp;sharesource=2301_80179750&amp;sharefrom=from_link,而我们今天要学的部分操作符与二进制有关,下面我们先介绍一下与二进制有关的知识,为后面要学的操作符铺垫。
四念处茫茫
2025/02/07
1290
【C语言系列】操作符的详解
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
计算机流程是将按位取反的数转换为2进制数,这个2进制数按位取反,然后再转换回原来的进制
小徐在进步
2024/10/07
1130
C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
窥探Swift之需要注意的基本运算符和高级运算符
  之前更新了一段时间有关Swift语言的博客,连续更新了有6、7篇的样子。期间间更新了一些iOS开发中SQLite、CollectionViewController以及ReactiveCocoa的一些东西。时隔两月,还得继续更新Swift语言的东西不是。在去年翻译《Swift编程入门经典》(Swift1.0版本,基于Xcode6)这本书时,系统的搞了搞Swift语言,接下来的一段时间内打算持续更新一下相关Swift语言的一些东西, 不过现在已经是Swift2.0版本了,区别还是不小的。并且目前在工作中正重
lizelu
2018/01/11
1.1K0
窥探Swift之需要注意的基本运算符和高级运算符
python基础-变量运算符(3)
注释就是对代码的解释和说明。目的是为了让别人和自己很容易看懂。为了让别人一看就知道这段代码是做什么用的。正确的程序注释一般包括序言性注释和功能性注释。序言性注释的主要内容包括模块的接口、数据的描述和模块的功能。模块的功能性注释的主要内容包括程序段的功能、语句的功能和数据的状态。–来自百度百科
Se7eN_HOU
2019/06/28
6570
python基础-变量运算符(3)
Python全网最全基础课程笔记(三)——所有运算符+运算符优先级
Python中的运算符优先级决定了在包含多个运算符的表达式中,各个运算符的执行顺序。优先级高的运算符会先于优先级低的运算符执行。以下是Python中所有运算符的优先级列表,按照从高到低的顺序排列。
小白的大数据之旅
2024/11/20
3440
Python全网最全基础课程笔记(三)——所有运算符+运算符优先级
4、Python运算符
Python语言支持逻辑运算符,但是没有其它语言中的&&和||语法。 取而代之的是更加人性化的英文单词and or not (全部都是小写字母)
度假的小鱼
2023/11/04
2830
4、Python运算符
推荐阅读
相关推荐
【刷题】一篇文章搞定“位运算”
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档