分享好友 最新动态首页 最新动态分类 切换频道
图解精讲快速排序(C语言实现)
2024-12-26 09:30
图解精讲快速排序(C语言实现

「 快速排序 」是利用了「 分而治之 」的思想,进行递归计算的排序算法,效率在众多排序算法中的佼佼者,一般也会出现在各种「数据结构」的教科书上。于是,我也来简单讲一讲。

首先我们可能对于这种的分而治之的思想是很不习惯的,我们刚刚去接触这个快速排序这个算法的时候,我们可能是不太可以去真正的理解这种的思想的,而且我们在之前所学的比如说冒泡排序,插入排序,选择排序这样的相对简单的排序算法,这个快速排序我们是比较难入门的,但是没关系,听我细细道来

一、简单释义

1、算法目的

将原本乱序的数组变成有序,可以是「升序」或者「降序」(为了描述统一,本文一律只讨论「 升序」的情况)。

2、算法思想

随机找到一个位置,将比它小的数都放到它「 左边 」,比它大的数都放到它「 右边 」,然后分别「 递归 」求解「 左边 」和「 右边 」使得两边分别有序。

3、命名由来

由于这个排序,相比其它的排序速度较快,故此命名「 快速排序 」。

「递归」:函数通过改变参数,自己调用自己。

「比较」:关系运算符 小于(<) 的运用。

「分治」:意为分而治之,先分,再治。将问题拆分成两个小问题,分别去解决。

1、样例

初始情况下的数据如图所示,基本属于乱序,纯随机出来的数据。

2、算法演示

这个地方我们使用动图的形式进行展示也是比较不方便的,所以这里我分步骤给大家进行相关的讲解

1.首先我们可以去在这个数组里面先去选择一个基数作为这个标准,当然我们去选择这个基数的时候,在本质上这个是可以是一个随机的选择,当然依照我的习惯,我喜欢去选择这个里面的中间的这个数,虽然说这个是没有区别的,这个基数的选择是具有这个随机性的

2.之后,我们要去利用这个分而治之的思想,将原本没有关联的相关的数据产生相对的关系,“我们要去秉持着一个原则:就是我们要把这个基准数作为这个分割值,在基数左边的数值都要比这个基数的数值要去小,而在这个基数右边的数值都要比这个基数的数值要去大”,这样才可以方便我们的进一步进行一个排序

3.在这个基数的左右的两边,我们就去使用了这个递归的思想进行进一步的排序

4.随着在这之间的不同的多个数值的位置进行了一个相应的确定,这样我们的快速排序也变的越来越清晰明了

  • 时间复杂度是比较低的O(nlogn

这个地方因为markdown是不方便我们去使用这个动图去进行展示的

所以我给大家一个链接,带着大家一起去观看这个快速排序的过程

排序——快速排序(Quick sort)-CSDN博客^v100^control&utm_term=%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F&spm=1018.2226.3001.4187

1、递归的实现

这个算法本身需要做一些「 递归 」计算,所以你至少需要知道「 递归 」的含义,这里以「 C语言 」为例,来看下一个简单的「 递归 」是怎么写的。代码如下

int sum(int n){
  if(n<=0) return 0;
  return sum(n-1)+n;
}

这就是一个经典的递归函数,求的是从 1 到 n 的和,那么我们把它想象成 1 到 n-1 的和再加 n,而 1 到 n-1 的和为 sum(n-1),所以整个函数体就是两者之和,这里 sum(n) 调用 sum(n-1) 的过程就被称为「 递归 」。

2、比较的实现

「比较」两个元素的大小,可以采用关系运算符,本文我们需要排序的数组是按照「升序」排列的,所以用到的关系运算符是「小于运算符(即 <)」。

我们可以将两个数的「比较」写成一个函数 smallerThan,以「 C语言 」为例,实现如下

#define Type int
bool smallerThan(Type a, Type b) {
    return a < b;
}

其中 Type 代表数组元素的类型,可以是整数,也可以是浮点数,也可以是一个类的实例,这里我们统一用int 来讲解,即 32位有符号整型。

3、分治的实现

所谓「分治」,就是把一个复杂的问题分成两个(或更多的相同或相似的)「 子问题 」,再把子问题分成更小的「 子问题 」……,直到最后子问题可以简单的直接求解,原问题的解即「 子问题 」的解的合并。

对于「 快速排序 」来说,我们选择一个基准数,将小于它的数都放到左边,大于它的数都放到它的右边,这个过程其实就是天然隔离了 左边的数右边的数,使得两边的数 “分开”,这样就可以分开治理了。

1、问题描述

给定一个 n 个元素的数组,数组下标从 0 开始,采用「 快速排序 」将数组按照「升序」排列。

2、算法过程

整个算法的执行过程用 quickSort(a[], l, r) 描述,代表 当前待排序数组 a,左区间下标 l,右区间下标 r,分以下几步

1) 随机生成基准点 pivox = Partition(l, r)

2)递归调用 quickSort(a[], l, pivox - 1) 和 quickSort(a[], pivox +1, r)

3)Partition(l, r) 返回一个基准点,并且保证基准点左边的数都比它小,右边的数都比它大;Partition(l, r)称为分区。

1、时间复杂度

首先,我们分析跑一次分区的成本。

在实现分区 Partition(l, r) 中,只有一个 for 循环遍历 (r - l) 次。 由于 r 可以和 n-1 一样大,i 可以低至 0,所以分区的时间复杂度是 O(n)。

类似于归并排序分析,快速排序的时间复杂度取决于分区被调用的次数。

当数组已经按照升序排列时,快速排序将达到最坏时间复杂度。总共 n 次分区,分区的时间计算如下:(n-1) + ... + 2 + 1 = n*(n-1) / 2。

总的时间复杂度为:O(n^2)。

当分区总是将数组分成两个相等的一半时,就会发生快速排序的最佳情况,如归并排序。当发生这种情况时,递归的深度只有 O(logn)。总的时间复杂度为 O(nlogn)。

「 快速排序 」在众多排序算法中效率较高,平均时间复杂度为 O(nlogn)。但当完全有序时,最坏时间复杂度达到最坏情况 O(n^2)。

所以每次在选择基准数的时候,我们可以尝试用随机的方式选取,这就是「 随机快速排序 」。

想象一下在随机化版本的快速排序中,随机化数据透视选择,我们不会总是得到 0,1 和 n-1 这种非常差的分割。所以不会出现上文提到的问题。

1、快速排序实现1

#include <stdio.h>
#include <malloc.h>
 
#define maxn 1000001
​
int a[maxn];
​
void Input(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
    }
}
​
void Output(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        if(i)
            printf(" ");
        printf("%d", a[i]);
    }
    puts("");
}
​
void Swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
​
int Partition(int a[], int l, int r){
    int i, j, pivox; 
    int idx = l + rand() % (r - l + 1);        // (1) 
    pivox = a[idx];                            // (2) 
    Swap(&a[l], &a[idx]);                      // (3) 
    i = j = l + 1;                             // (4) 
                                               // 
    while( i <= r ) {                          // (5) 
        if(a[i] < pivox) {                     // (6) 
            Swap(&a[i], &a[j]);                
            ++j;                               
        }
        ++i;                                   // (7) 
    }
    Swap(&a[l], &a[j-1]);                      // (8) 
    return j-1;
}
​
​
//递归进行划分
void QuickSort(int a[], int l, int r){
    if(l < r){
        int mid = Partition(a, l, r);
        QuickSort(a, l, mid-1);
        QuickSort(a, mid+1, r);
    }
}
​
int main() {
    int n;
    while(scanf("%d", &n) != EOF) {
        Input(n, a);
        QuickSort(a, 0, n-1);
        Output(n, a);
    }
    return 0;
} 

(1) 随机选择一个基准

(2) pivox 代表基准值

(3) 将基准和最左边的值交换

(4) i 和 j 是两个同步指针,都从 l+1 开始;j-1 以后的数都是大于等于 基准值 的

(5) 开始遍历整个排序区间,i 一定比 j 走的快,当 i 到达最右边的位置时,遍历结束

(6) 如果比基准值小的,交换 a[i] 和 a[j],并且自增 j

(7) 每次遍历 i 都需要自增

(8) 第 j 个元素以后都是不比基准值小的元素

2、快速排序实现2

#include <stdio.h>
#include <malloc.h>
 
#define maxn 1000001
​
int a[maxn];
​
void Input(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        scanf("%d", &a[i]);
    }
}
​
void Output(int n, int *a) {
    for(int i = 0; i < n; ++i) {
        if(i)
            printf(" ");
        printf("%d", a[i]);
    }
    puts("");
}
​
void Swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
​
int Partition(int a[], int l, int r){
    int i, j; 
    int idx = l + rand() % (r - l + 1);        // (1) 
    Swap(&a[l], &a[idx]);                      // (2) 
    i = l, j = r;                              // (3) 
    int x = a[i];                              // (4) 
    while(i < j){
        while(i < j && a[j] > x)               // (5) 
            j--;
        if(i < j) 
             Swap(&a[i], &a[j]), ++i;          // (6) 
                                               // 
                                            
        while(i < j && a[i] < x)               // (7) 
            i++;
        if(i < j)
            Swap(&a[i], &a[j]), --j;           // (8) 
    }
    a[i] = x;
    return i;
}
//递归进行划分
void QuickSort(int a[], int l, int r){
    if(l < r){
        int mid = Partition(a, l, r);
        QuickSort(a, l, mid-1);
        QuickSort(a, mid+1, r);
    }
}
​
int main() {
    int n;
    while(scanf("%d", &n) != EOF) {
        Input(n, a);
        QuickSort(a, 0, n-1);
        Output(n, a);
    }
    return 0;
} 

(1) 随机选择一个基准

(2) 将基准和最左边的值交换

(3) 初始区间 [l, r]

(4) 这里的 x 是一开始随机出来那个基准值

(5) 从右往左找,第一个满足 a[j] <= x 基准值 的,退出循环

(6) 如果 a[j] <= x 基准值 ,则 a[j] 必须和 x 交换,才能满足 a[j] 在基准值左边;且此时基准值是 a[i] ,交换完,基准值变成原先的 a[j] ,且 i 需要自增一次

(7) 从左往右找,第一个满足 a[i] >= x 基准值 的,退出循环

最新文章
移动搜索引擎SEO优化策略与实践
我们先来讲一下为什么要对微网站、进行SEO的优化,以及优化的原则有哪些吧。首先,大家都知道,传统的PC网站做了SEO优化之后,所获得的流量和转化的效果比起没有做的要强一些。那么,同样的在移动搜索方面也
银行卡多长时间逾期会有什么后果
在日常生活中采用银行卡实施消费和借贷已经成为了一种普遍现象。在享受便利的同时也必须留意按期还款否则可能将会带来一系列严重的影响。本文将详细介绍银行卡逾期的不同情况及其可能产生的结果。当银行卡逾期在一个月内时,这类情况被称为
做游戏网站需要多少钱/平台推广公众平台营销
硬盘里面有很多磁头,每个磁头有很多环,类似跑道。称为柱面:0柱面 1柱面;跑道里面有很多块称为扇区:扇区0  扇区1。硬盘操作的耗时主要在磁盘转换的时候,如果我们一会读1磁头,一会写2磁头
百度百万蜘蛛池,探索搜索引擎优化中的神秘力量,百度百万蜘蛛池搭建
百度百万蜘蛛池是一种用于搜索引擎优化的技术,通过搭建蜘蛛池,可以吸引更多的搜索引擎爬虫访问网站,提高网站权重和排名。这种技术通过模拟大量用户访问网站,增加网站的流量和曝光率,从而提升网站的知名度和商业价值。需要注意的是,蜘
辽宁网络网站搭建与优化策略解析,企业品牌影响力提升之道
本文深入探讨了辽宁地区网络网站搭建与优化策略,旨在帮助企业提升品牌影响力。通过分析优化关键词、提升用户体验、增强搜索引擎排名等方面,提供了一系列实操建议,助力企业打造高效、专业的网络平台。辽宁网络网站搭建策略辽宁网络网站优
湖南关键词推广革新,助力区域品牌引擎升级
湖南省通过优化关键词推广策略,致力于提升区域品牌影响力,打造全新品牌发展引擎,以创新推广手段推动地方经济和文化品牌的崛起。随着互联网技术的飞速发展,网络营销已经成为企业推广产品和服务的重要手段,在众多营销策略中,关键词推广
天猫抽检被处罚怎么办?提交复检申诉的条件
2019年11月20日陈菲100天猫各大类目代入驻绿通,天猫超市 ,猫享自营代入驻,天猫大药房,京东POP,京东自营代入驻,天猫店添加品牌类目,天猫店铺考核不达标处理,天猫店铺主体变更,抖音,得物入驻,天猫,京东,抖音,得物,快手等网络
小米手机获取 Root 权限教程(详细图文) – MIUI历史版本
常说的 Root 手机指的是获取 Android 系统超级用户权限,目的大多为了卸载 OEM 厂商预装软件,或者运行需要 Root 权限的软件,例如聊天防撤回、虚拟定位、权限控制等。在获取 Root 权限后如果平时授权管理不当,会增加手机安全风险,因此只
新浪微博转发关注推广外包,找X传播平台
新浪微博转发关注推广外包,找X传播平台新浪微博转发关注推广外包,找X传播平台X传播,国内最全的社会化媒体营销平台,帮助您在百科,论坛,新闻,问答,博客,微博,微信,美丽说,豆瓣,开心,人人等社会化媒体渠道进行全面的推广。X传播
游戏排行榜第一名手游推荐 2024受欢迎的手游合集
今天小编要给大家聊一下游戏排行榜第一名,咱们知道,现在游戏界挺流行搞排名的,那些排在前面的游戏,不只是画面做得像电影一样吸引人,它们的故事讲得动听,玩法也特别有创意,有的甚至火到出了好多好玩的周边产品。所以,小编就来给大家
相关文章
推荐文章
发表评论
0评