C++算法之快速排序

这篇具有很好参考价值的文章主要介绍了C++算法之快速排序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C++算法之快速排序



一、快速排序引出

我们知道,给一个长度为n的序列排序,有三种很简单的算法:选择排序、冒泡排序、插入排序。这三种算法的复杂度均为O(n^2)

如果按照计算机1秒钟可以进行10^8次计算作为参照,那么它1秒之内可以排序的序列长度大概为10^4这个数量级。

然而,在实际生活中,10^4级别并不是一个很大的数字,比如说山东每年会有超过50万人参加高考。如果我们想将山东省内所有学生按照高考成绩排序的话,使用O(n^2)将会运行:
快速排序c++,C++算法,c++,算法,数据结构
不得不说,这样的算法并不算高效。那么,我们能不能设计出更高效的算法呢?

  • 一种优化方法
    假设现在,我们只想对1~n的数字排序。并且现在我们有个排序算法,运行复杂度刚好是n^2

如果:

先把该序列分成两部分,一部分是1~(n/2),另一部分是(n/2+1)~n

然后对两个序列进行分别排序 ;

最后再将两部分贴在一起,这个算法的复杂度是多少呢?

1、首先,我们将序列分为长度相等的大小两部分。

此时我们需要将所有<=n/2的数字调出来放到一边;

将所有>n/2的数字挑出来放到另一边;

这个步骤相当于将所有数字看一遍,所以复杂度是O(n)

2、然后,我们使用原来掌握的排序算法,分别给分好的两个序列排序。

因为两个序列的长度都是n/2,所以两边排序的复杂度都是(n/2)2 = n2 / 4。两部分排序的总复杂度是(n2 / 4) * 2 = n2 / 2。

3、最后,我们需要把两部分的排序结果贴在一起。

这个操作几乎不费时间。

所以,我们最终复杂度将是n^2 / 2 + n。和原来直接运行排序算法得到复杂度为n^2 相/比,我们节省了近一半的时间!

由此,我们只需要将原序列划分一下,两边分别排序,最后将该序列合并,就能节省一半的时间(此时因为复杂度仍为平方级别,所以,我们只是在原算法的基础上优化一个常数)。

那么,我们能不能进一步优化该算法呢?

答案是可以的,只要我们按照上述步骤继续分下去就可以了,如下图:
快速排序c++,C++算法,c++,算法,数据结构


二、快排步骤

当然快速排序也可用来给任意n个数的序列排序。但是与和1~n排序不同的是,对于任意n个数的序列,我们在划分子段的时候并不能很容易找到整个序列的“中位数”。所以只能在序列中任意取一个数。比如

  • 取整个序列中最左边的数。
  • 取整个序列中最右边的数。
  • 在整个序列中随机一个位置并取该位置上的数。

都是常见的取数策略。

但由于不能保证每次取的数字都刚好是中位数,所以每次划分时也不能保证左边子段长度和右边子段长度非常平均。如果“不幸”选到不合适的数(比如整个子段中最小的数或最大的数),整个算法的效率会降低很多。

在此,我们详细描述一下给任意n个数排序的快速排序算法:

1、假设我们要对数组a[1..n]排序。初始化区间[1..n]

2、令l和r分别为当前区间的左右端点。下面假设我们对l到r子段内的数字进行划分。取pivot = a[l]为分界线,将<pivot的数字移到左边,>pivot的数字移到右边,然后将pivot放在中间。假设pivot的位置是k

3、如果左边区间[l..k-1]长度大于1,则对于新的区间[l..k-1],重复调用上面的过程。

4、如果右边区间[k+1..r]长度大于1,则设置新的区间[k+1, r],重复调用上面的过程。

当整个过程结束以后,整个序列排序完毕。


三、代码实现

代码如下(示例):

// 该代码参考 https://www.geeksforgeeks.org/quick-sort/
#include <bits/stdc++.h>
#define N 100010 
using namespace std; 
int n; 
int a[N]; 
 
void quick_sort(int l, int r) { 
    // 设置最右边的数为分界线
    int pivot = a[r];
    
    // 元素移动
    int k = l - 1;
    for (int j = l; j < r; ++j)
        if (a[j] < pivot) swap(a[j], a[++k]); 
    swap(a[r], a[++k]); 
    
    if (l < k - 1) quick_sort(l, k - 1); // 如果序列的分界线左边的子段长度>1,排序
    if (k + 1 < r) quick_sort(k + 1, r); // 如果序列的分界线右边的子段长度>1,排序
    // 上面的过程结束后,到这里左子段和右子段已经分别排好序。又因为确定分界线以后的移动操作
    // 保证了左子段中的元素都小于等于分界线,右子段中的元素都大于分界线。所以整个序列也是有序的。
} 
 
int main() { 
    // 输入
    scanf("%d", &n); 
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); 
     
    // 快速排序
    quick_sort(1, n); 
    
    // 输出
    for (int i = 1; i <= n; ++i) printf("%d ", a[i]);  
    return 0; 
} 

四、复杂度分析

空间复杂度

首先该算法的空间复杂度是O(n),具体来说,在整个排序过程中,元素的移动都在原始数组中进行。所以快速排序是一种原地排序算法。

时间复杂度

可以看出,在「详细算法描述」中,我们的算法分为若干层。每一层中都是分治法的三个步骤:我们首先进行问题拆分,然后进入下一层,下一层的问题解决后,我们返回这一层进行子问题解的合并。
快速排序c++,C++算法,c++,算法,数据结构
我们首先分析对1~nn个数字进行快速排序的情况。

在每一层中,问题拆分的复杂度是O(n),因为我们移动数组元素的时候,需要将每个子段扫一遍。那么把所有层的子段一起看,就相当于在每一层都把整个序列完整扫了一遍。对于子段解的合并,其复杂度是O(1),因为有分界线的存在,当我们把左边和右边都排好序后,它们和分界线元素一起天然形成了原序列的完整排序。

那么一共有多少层呢?因为每次我们都知道当前子段的中位数,所以可以保证每次划分,两个字段长度比较平衡,所以下一层子段的长度都比上一层减少了一半,直到长度为1算法停止。所以整个算法有logn层。

那么我们分析出在这种情况下,算法的复杂度是O(n * logn)。这样,在1秒之内,计算机能非常轻松地排序10^6 及以上的数据。

但对于任意n个数的排序,每次划分情况取决于选取的分界线情况。如果每次分界线刚好取到最小值或者最大值,会导致划分时所有数字都会移动到同一边,整个算法的复杂度也会下降为O(n^2)。如下图:
快速排序c++,C++算法,c++,算法,数据结构
我们很容易想到两种尽量避免出现这种情况的方法:

  • 在排序之前,先把整个数组随机打乱顺序。
  • 在选取分界线时,与之前固定选取某个位置的方法相比,我们换成随机选择分界线的位置。

这两种方法都能极大概率避免上面提到的极端情况的发生。文章来源地址https://www.toymoban.com/news/detail-685628.html

到了这里,关于C++算法之快速排序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】排序算法(二)—>冒泡排序、快速排序、归并排序、计数排序

    👀 樊梓慕: 个人主页  🎥 个人专栏: 《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》 🌝 每一个不曾起舞的日子,都是对生命的辜负 目录 前言 1.冒泡排序 2.快速排序 2.1Hoare版 2.2占坑版 2.3前后指针版 2.4三数取中对快速排序的优化 2.5非递归版 3.归

    2024年02月08日
    浏览(35)
  • 【数据结构与算法】:非递归实现快速排序、归并排序

    🔥 个人主页 : Quitecoder 🔥 专栏 :数据结构与算法 上篇文章我们详细讲解了递归版本的快速排序,本篇我们来探究非递归实现快速排序和归并排序 快速排序的非递归实现主要依赖于栈(stack)来模拟递归过程中的函数调用栈。递归版本的快速排序通过递归调用自身来处理子

    2024年03月24日
    浏览(44)
  • 【数据结构与算法】如何对快速排序进行细节优化以及实现非递归版本的快速排序?

    君兮_的个人主页 即使走的再远,也勿忘启程时的初心 C/C++ 游戏开发 Hello,米娜桑们,这里是君兮_,国庆长假结束了,无论是工作还是学习都该回到正轨上来了,从今天开始恢复正常的更新频率,今天为大家带来的内容是快速排序的两大优化和非递归实现 好了废话不多说,开

    2024年02月08日
    浏览(37)
  • 【数据结构与算法】快速排序的三种实现方法

      目录 一.基本思想 二.Hoare法 动态演示 三.挖坑法 动态演示 四.前后指针法 动态演示 五.快速排序优化 随机下标交换法 三路取中法 六.快速排序的特性 任取待排序元素序列中的某元素作为 基准值 ,按照该排序码将待排序集合 分割成两子序列 , 左子序列中所有元素均小于基

    2023年04月09日
    浏览(48)
  • 【数据结构与算法】快速排序的非递归实现方法

      目录 一.前言 二.非递归实现 如果数据量过大的话,不断递归就会出现 栈溢出 的现象,这个时候你的代码是没问题的,但就是跑不起来,这个时候就要 把递归改成非递归 。 一般有两种改法: 1.直接改,利用循环等; 2.借助栈的辅助。 而快速排序的非递归实现方法就需要

    2023年04月17日
    浏览(41)
  • 【数据结构】详解七大排序算法(直接插入排序、希尔排序、直接选择排序、堆排序、冒泡排序、快速排序)

    1、基本思想    把待排序的数按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所以的记录插入完为止,得到一个新的有序序列。    实际中我们玩扑克牌时,就用到了插入排序的思想 基本步骤:    当插入第i个元素时,前面的arr[0]、arr[2]…arr

    2024年02月04日
    浏览(51)
  • 【C++】数据结构与算法:常用排序算法

    😏 ★,° :.☆( ̄▽ ̄)/$: .°★ 😏 这篇文章主要介绍常用排序算法。 学其所用,用其所学。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下次更新不迷路🥞 排序算法是计算机科学中常见的一类算法,用于将一组数据按照特定的顺序进行排

    2024年02月14日
    浏览(34)
  • 数据结构和算法——快速排序(算法概述、选主元、子集划分、小规模数据的处理、算法实现)

    目录 算法概述 图示 伪代码 选主元 子集划分 小规模数据的处理 算法实现 快速排序和归并排序有一些相似,都是用到了分而治之的思想:   通过初步的认识,我们能够知道快速排序算法最好的情况应该是: 每次都正好中分 ,即每次选主元都为元素的中位数的位置。 最好情

    2024年02月15日
    浏览(31)
  • 【数据结构与算法C++实现】3、排序算法

    原视频为左程云的B站教学 外层循环 :n个数需要冒n-1个泡上去,剩下的一个必然是最小的。所以外层循环执行n-1轮 内层循环 :比大小,第1个泡需要比n-1次,第2个泡,比较n-2次… 选择: 每次从待排序序列中选择 最小的一个 放在已排序序列的后一个位置 原理类似于对扑克牌

    2024年02月11日
    浏览(45)
  • C++基础-介绍·数据结构·排序·算法

    C++是一门风格严谨又不失自由的开发语言,提供了完整的内存管理、支持函数式编程和面向对象编程,支持模板、多继承、多实现、重载、重写等多态特性。 优势在于目前90%的操作系统、数据库、应用基础架构、硬件嵌入式等都是使用C/C++制作的,而C++是对C的标准扩展,掌握

    2024年02月03日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包