算法实现 - 快速排序(Quick Sort) - 理解版

文章目录

  • 算法介绍
  • 算法分析
    • 核心思想
    • 三个版本运行过程
      • 挖坑法
      • Hoare 原版
      • 前后指针法
  • 算法稳定性和复杂度

算法介绍

快速排序是一种高效的排序算法,由英国计算机科学家C. A. R. Hoare在1960年提出,是对冒泡排序算法的一种改进。

该算法采用 分治策略,其基本思想:
选择一个基准元素(pivot),通过基准元素将待排序的序列分成两部分,一部分包含所有小于基准元素的元素,另一部分包含所有大于基准元素的元素,然后递归地对这两部分进行快速排序,最终使整个数组成为一个有序的序列。

算法分析

核心思想

  1. 选择基准:从数组中选择一个元素作为基准(pivot)。通常可以选择第一个元素、最后一个元素、随机元素或中间元素。
  2. 分区操作
    • 遍历数组,将小于基准的元素移动到基准左侧,大于基准的元素移动到右侧。这样基准元素就会被放置到其正确的排序位置。
    • 此步骤会让数组分成了两部分:左侧(小于基准)和右侧(大于基准)。
  3. 递归排序
    • 对于基准位置左侧和右侧的两个子数组,分别递归地运用快速排序。
    • 递归结束的条件是子数组的长度小于或等于 1,此时它们已经有序。
  4. 合并结果:合并已经排序好的子数组与基准,得到一个完整的有序数组。

三个版本运行过程

以数列 a[] = {40, 20, 30, 60, 10, 50} 作为分析案例;

挖坑法

在这里插入图片描述

分析:

x 取索引为0 的最左值 为 40,即x = 40。

  • 从"右 --> 左"查找小于x的数: 找到满足条件的数a[j]=10,此时 j=4,i=0;然后将a[j]赋值a[i],即将 a[4] 赋值给 a[0];接着从左往右遍历。
  • 从"左 --> 右"查找大于x的数: 找到满足条件的数a[i]=60,此时 i=3,j=4;然后将a[i]赋值a[j],即将 a[3] 赋值给 a[4];接着从右往左遍历。
  • 从"右 --> 左"查找小于x的数: 没有找到满足条件的数。当i>=j时,停止查找;然后将x赋值给a[i],即将 40 赋值给 a[3]。此趟遍历结束!

代码:

#include <stdio.h>

/**
 * Quick sort by C Lang.
 *
 * params:
 *   arr -- the data array
 *   left -- the first index of arr
 *   right -- the last index of arr
 *
 */
void quickSort(int arr[], int left, int right)
{
    // the arr is empty or left one element
    if (left >= right)
    {
        return;
    }

    // init data
    int pivot = arr[left];
    int i = left, j = right;

    while (i < j)
    {
        // right -> left: find the first element less than pivot
        // notice: when arr[j] meet the condition `arr[j] < pivot`, it will break the while loop, indicate that current element is less than pivot.
        while (i < j && arr[j] > pivot)
            j--;
        if (i < j)
        {
            arr[i++] = arr[j];
        }

        // left -> right: find the first element great than pivot
        while (i < j && arr[i] < pivot)
            i++;
        if (i < j)
        {
            arr[j--] = arr[i];
        }

        arr[i] = pivot; // The element has benn correctly placed in its corresponding position

        quickSort(arr, left, i - 1);  // Recursive invoke in the left sub array
        quickSort(arr, i + 1, right); // Recursive invoke in the right sub array
    }
}

/**
 * show array
 */
void printArray(int array[], int n)
{
    for (size_t i = 0; i < n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n\n");
}

int main()
{
    int arr[] = {40, 20, 30, 60, 10, 50};

    // calculate the length of a array
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("before sort: \n");
    printArray(arr, n);

	// The third param must be `n -1`
    quickSort(arr, 0, n - 1);

    printf("after sort: \n");
    printArray(arr, n);

    return 0;
}

代码详见:GitHub 快速排序法 挖坑版

Hoare 原版

分析:

  • 选择基准值:快速排序的第一步是选择一个基准值(pivot)。通常可以选择数组的第一个元素,但为了提高排序效率,有时也会选择随机元素或使用其他策略。
  • 划分过程:使用两个指针,左指针和右指针,分别从数组的两端开始向中间移动。
  • 具体步骤:
    1. 左指针移动:左指针从左向右移动,直到找到一个大于或等于基准值的元素。
    2. 右指针移动:右指针从右向左移动,直到找到一个小于或等于基准值的元素。
    3. 交换元素:如果左指针指向的元素大于基准值,且右指针指向的元素小于基准值,则交换这两个元素。
    4. 继续移动指针:继续移动左右指针,直到它们相遇。
    5. 交换基准值:最后,将基准值与右指针指向的元素交换,确保基准值左侧的所有元素都不大于基准值,右侧的所有元素都不小于基准值。

代码:

#include <stdio.h>

/**
 * swap two number
 */
void swap(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

/**
 * Quick sort by C Lang.
 *
 * params:
 *   arr -- the data array
 *   left -- the first index of arr
 *   right -- the last index of arr
 *
 */
int partition(int arr[], int left, int right)
{
    // init data
    int pivotIndex = left;
    int i = left, j = right;

    while (i < j)
    {
        // right -> left: find the first element less than pivot
        while (i < j && arr[j] > arr[pivotIndex])
        {
            j--;
        }

        // left -> right: find the first element great than pivot
        while (i < j && arr[i] < arr[pivotIndex])
        {
            i++;
        }

        // after upper two loop, indicate that it is able to swap
        if (i < j)
            swap(&arr[i], &arr[j]);
    }

    // Swap the pivot with the element pointed by the right pointer
    swap(&arr[pivotIndex], &arr[j]);

    return j;
}

void quickSort(int arr[], int left, int right)
{
    if (left < right)
    {
        int pivotIndex = partition(arr, left, right);
        quickSort(arr, left, pivotIndex - 1);
        quickSort(arr, pivotIndex + 1, right);
    }
}

/**
 * show array
 */
void printArray(int array[], int n)
{
    for (size_t i = 0; i < n; i++)
    {
        printf("%d ", array[i]);
    }
    printf("\n\n");
}

int main()
{
    int arr[] = {40, 20, 30, 60, 10, 50};

    // calculate the length of a array
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("before sort: \n");
    printArray(arr, n);

    // The third param must be `n -1`
    quickSort(arr, 0, n - 1);

    printf("after sort: \n");
    printArray(arr, n);

    return 0;
}

代码详见:GitHub 快速排序法 hoare版

前后指针法

前后指针法(也称为荷兰国旗问题解法)是快速排序中的一种高效实现方式。在这个方法中,通常将数组划分为三个部分:

  • 小于基准值的元素
  • 等于基准值的元素
  • 大于基准值的元素。

分析:

  • 变量初始化:

    • key 是基准元素的索引,初始为 left。
    • prev 是逻辑分区点之前的最后一个小于基准的元素的索引,开始等于 left。
    • cur 是当前遍历的元素索引,从 left + 1 开始。
  • 分区逻辑:

    • 使用 while 循环遍历数组,从 cur = left + 1 到 right。
    • 如果 a[cur] < a[key],则 prev 增加(指向下一个元素),并交换 a[prev] 和 a[cur],确保小于基准的元素移动到前面。
    • 即使 prev 和 cur 相等,也进行交换,这一步是为了简化逻辑,即不需要特判 prev 和 cur 相等的情况。实际上在交换时什么也不会变。
  • 基准交换:
    循环结束后,将基准值与 a[prev] 交换,把基准放置在正确的位置。

代码

#include <stdio.h>

/**
 * swap two number
 */
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

/**
 * Quick sort by C Lang.
 *
 * params:
 *   arr -- the data array
 *   left -- the first index of arr
 *   right -- the last index of arr
 *
 */
int partition(int arr[], int left, int right) {
    int pivotIndex = left;
    int prev = left;
    int cur = left + 1;

    while (cur <= right) {
        if (arr[cur] < arr[pivotIndex] && ++prev != cur) {
            swap(&arr[prev], &arr[cur]);
        }
        cur++;
    }
    swap(&arr[prev], &arr[pivotIndex]);
    return prev;
}

void quickSort(int arr[], int left, int right) {
    if (left < right) {
        int pivot_index = partition(arr, left, right);
        quickSort(arr, left, pivot_index - 1);
        quickSort(arr, pivot_index + 1, right);
    }
}

void print_array(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {30, 40, 60, 10, 20, 50};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("Before sorting:\n");
    print_array(arr, n);

    quickSort(arr, 0, n - 1);

    printf("After sorting:\n");
    print_array(arr, n);

    return 0;
}

代码详见:GitHub 快速排序法 前后指针版

算法稳定性和复杂度

稳定性

这一点是因为在分区过程中,元素的相对顺序可能会改变。

具体来说,当我们交换元素以确保小于基准的元素在前,大于基准的在后时,相同元素的相对位置可能改变。

例如,如果两个相等的元素分别在基准的两侧,交换后它们的位置关系就被打破了。

举个例子:

示例数组:[ 3 a 3_a 3a, 1, 2, 3 b 3_b 3b]
这里的 3 a 3_a 3a, 3 b 3_b 3b 是两个不同位置的相同元素(相同的值但用下标区分,用于说明其在数组中的初始位置)。
根据实现可能不交换,但假设实现中等于基准的情况下它也移动,这里交换 3 b 3_b 3b 3 a 3_a 3a得到数组 [ 3 b 3_b 3b, 1, 2, 3 a 3_a 3a],顺序已经发生了变更。

时间复杂度

平均情况O(nlogn)

基本操作
快速排序的基本操作是划分和递归调用。

操作次数

  • 划分操作:每次划分操作将数组分为两部分。在平均情况下,我们假设每次划分都能将数组大致分为两个相等的部分。这意味着每次划分操作都会遍历整个数组一次,操作次数为 n。
  • 递归操作:由于每次划分都将数组分为两个大致相等的部分,所以每次递归调用处理的数组大小大约是前一次的一半。这个过程会一直持续到子数组的大小为1,此时不需要再进行划分。

对于大小为 n 的数组,快速排序的递归关系可以表示为:
T ( n ) = 2 T ( n 2 ) + n T(n) = 2T\left(\frac{n}{2}\right) + n T(n)=2T(2n)+n

  • T(n)是对大小为 n 的数组进行排序所需的总操作次数。
  • T( n 2 \frac{n}{2} 2n)表示对两个大小为 n 2 \frac{n}{2} 2n的子数组进行排序所需的操作次数。
  • n 是当前划分操作所需的总比较次数。

大O表示法
这种形式可以用主定理求解,符合主定理的标准形式,所以复杂度为:O(n log n)

分治算法主定理,点击看我的另外一篇文章

最差情况O( n 2 n^2 n2)

基本操作
快速排序的基本操作是划分和递归调用。

操作次数

在最坏情况下,每次划分操作只能将数组分成一个大小为 n−1 的部分和一个大小为 0 的部分(例如,当数组已经有序或每次都选择最大或最小元素作为基准时)。在这种情况下,递归关系式变为:

T ( n ) = T ( n − 1 ) + n T(n) = T(n - 1) + n T(n)=T(n1)+n

  • T(n - 1)是对大小为 n−1 的子数组进行排序所需的操作次数。
  • n 是当前划分操作所需的总比较次数。

上述等价于
T ( n ) = n + ( n − 1 ) + ( n − 2 ) + ( n − 3 ) + . . . + 2 + 1 = ( n + 1 ) n 2 = n 2 2 + n 2 T(n)= n + (n-1) + (n-2) + (n-3) + ... + 2 + 1 = \frac{(n + 1)n}{2} = \frac{n^2}{2} + \frac{n}{2} T(n)=n+(n1)+(n2)+(n3)+...+2+1=2(n+1)n=2n2+2n

大O表示法
上述用大O表示法为:O( n 2 n^2 n2)

空间复杂度

快速排序是一种递归算法。每次递归调用时,程序会在栈上保存当前调用的状态信息:

  • 平均情况下:
    • 快速排序以较好的概率实现了平均分区,递归调用的深度大约是树的高度,即 O(logn)。
    • 每次递归在栈上保存的信息量是常数级别的,例如数组的起始和结束索引、基准元素信息等。
  • 最坏情况下:
    • 如果每次选择的基准是最小或最大元素,导致极端不平衡的分区,递归深度达到 O(n)。
    • 这种情况会在已经排序的数组或逆序数组上发生。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/904007.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言指针的介绍

零.导言 在日常生活中&#xff0c;我们常常在外出时居住酒店&#xff0c;细心的你一定能发现酒店不同的房间上有着不同的门牌号&#xff0c;上面写着像308&#xff0c;512之类的数字。当你定了酒店之后&#xff0c;你就会拿到一个写有门牌号的钥匙&#xff0c;凭着钥匙就能进入…

聊聊解构的那些事

#我们都知道es6出了个新特性&#xff0c;支持解构&#xff0c;使用过的人可能都觉得挺简单的&#xff0c;但有一些小点&#xff0c;只有使用中留意了或者踩坑了才发现我们认识的还很浅# 解构定义 允许按照一定模式&#xff0c;从数组和对象中提取值&#xff0c;对变量进行赋值…

Qt Designer客户端安装和插件集(pyqt5和pyside2)

GitHub - PyQt5/QtDesignerPlugins: Qt Designer PluginsQt Designer Plugins. Contribute to PyQt5/QtDesignerPlugins development by creating an account on GitHub.https://github.com/PyQt5/QtDesignerPlugins 一、下载客户端 https://github.com/PyQt5/QtDesigner/rel…

Idea常见插件(超级实用)

文章目录 Idea好用的插件推荐Idea插件安装Chinese(中文版)Alibaba Java Coding Guidelines&#xff08;代码规范&#xff09;Auto Filling Java Arguments&#xff08;自动补全参数&#xff09;CamelCase&#xff08;变量名称格式转换&#xff09;CodeGeeX&#xff08;智能&…

Windows 下实验视频降噪算法 MeshFlow 详细教程

MeshFlow视频降噪算法 Meshflow 视频降噪算法来自于 2017 年电子科技大学一篇高质量论文。 该论文提出了一个新的运动模型MeshFlow&#xff0c;它是一个空间平滑的稀疏运动场 (spatially smooth sparse motion field)&#xff0c;其运动矢量 (motion vectors) 仅在网格顶点 (m…

WPF+MVVM案例实战(三)- 动态数字卡片效果实现

1、创建项目 打开 VS2022 &#xff0c;新建项目 Wpf_Examples&#xff0c;创建各层级文件夹&#xff0c;安装 CommunityToolkit.Mvvm 和 Microsoft.Extensions.DependencyInjectio NuGet包,完成MVVM框架搭建。搭建完成后项目层次如下图所示&#xff1a; 这里如何实现 MVVM 框…

5G+智慧园区:引领城市现代化建设新篇章

5G智慧园区&#xff0c;作为新时代城市发展的创新引擎&#xff0c;正引领着现代城市建设的新趋势。依托5G通信技术&#xff0c;结合物联网、大数据、人工智能等前沿信息技术&#xff0c;5G智慧园区实现了园区内人员、建筑、产业等核心要素的数字化、智能化管理与服务&#xff0…

【NOIP提高组】 关押罪犯

【NOIP提高组】 关押罪犯 C语言C语言实现Python语言实现 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; S城现有两座监狱&#xff0c;一共关押着N名罪犯&#xff0c;编号分别为1-N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久…

Chrome和Firefox哪款浏览器的密码管理更安全

在当今数字化时代&#xff0c;浏览器已成为我们日常生活中不可或缺的工具。其中&#xff0c;谷歌Chrome和Mozilla Firefox是两款广受欢迎的浏览器。除了浏览网页外&#xff0c;它们还提供了密码管理功能&#xff0c;帮助用户保存和管理登录凭证。然而&#xff0c;关于哪款浏览器…

预览 PDF 文档

引言 在现代Web应用中&#xff0c;文件预览功能是非常常见的需求之一。特别是在企业级应用中&#xff0c;用户经常需要查看各种类型的文件&#xff0c;如 PDF、Word、Excel 等。本文将详细介绍如何在Vue项目中实现 PDF 文档的预览功能。 实现原理 后端API 后端需要提供一个…

web前端多媒体标签设置(图片,视频,音频)以及图片热区(usemap)的设置

多媒体标签运用 在HTML中有以下常见多媒体标签&#xff1a; <img> &#xff08;图像标签&#xff09; - 作用&#xff1a;用于在网页中嵌入图像。 - 示例&#xff1a; <img src"image.jpg" alt"这是一张图片"> 。其中 src 属性指定图像的…

结合无监督表示学习与伪标签监督的自蒸馏方法,用于稀有疾病影像表型分类的分散感知失衡校正|文献速递-基于生成模型的数据增强与疾病监测应用

Title 题目 Hybrid unsupervised representation learning and pseudo-label supervisedself-distillation for rare disease imaging phenotype classification with dispersion-aware imbalance correction 结合无监督表示学习与伪标签监督的自蒸馏方法&#xff0c;用于稀…

【C++】入门C++

1.C的第一个程序 之前写的C语言文件都是后缀为.c的文件&#xff0c;进入C后就要把后缀改为.c了&#xff0c;vs编译器看到是.cpp就会调⽤C编译器编译。C兼容C语言的绝大多数语法&#xff0c;所以C语言的 hallo word 依旧可以在C下使用。 //test.cpp //c语言的hallo world #inc…

紫光同创——盘古 50KN 网口板

本原创文章由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处&#xff08;www.meyesemi.com) 一、开发系统介绍 盘古 50KN 网口板开发板&#xff08;MES50H-Ethernet&#xff09;采用了核心板扩展板的结 构&#…

本篇文章来介绍下dockerfile

我开始玩儿docker的时候&#xff0c;都是通过docker pull命令把基础镜像拉取到本地&#xff0c;然后在跑成容器&#xff0c;在操作容器&#xff0c;做一些自己的事情&#xff0c;比如安装个java环境什么的&#xff0c;直到我接触到了dockerfile&#xff0c;我发现dockerfile真是…

一款基于.NET8开源且免费的中小型酒店管理系统

项目介绍 TopskyHotelManagerSystem是一款基于.NET8开源、免费&#xff08;MIT License&#xff09;的中小型酒店管理系统&#xff0c;为中小型酒店提供全面的酒店管理系统解决方案&#xff0c;帮助酒店提高运营效率&#xff0c;优化客户体验。 开发目的 在现如今发展迅速的酒…

【本科毕业设计】基于单片机的智能家居防火防盗报警系统

基于单片机的智能家居防火防盗报警系统 源码下载摘要Abstract第1章 绪论1.1课题的背景1.2 研究的目的和意义 第2章 系统总体方案设计2.1 设计要求2.2 方案选择和论证2.2.1 单片机的选择2.2.2 显示方案的选择 第3章 系统硬件设计3.1 整体方案设计3.1.1 系统概述3.1.2 系统框图 3…

【测试平台】打包 子节点ios环境配置

主要记录如何配置ios打包机环境&#xff0c;ios环境相对来说比较简单的&#xff0c;研发配置好证书可以本地打包&#xff0c;接入流程比较简单了。 打包机系统升级 1.升级mac OS系统 一般升级好几个小时&#xff0c;可以晚上下载好 2.下载xcode并安装 Appstroe 下载安装xco…

分布式光伏是什么意思?如何高效管理?

分布式光伏系统是指在用户现场或靠近用电现场配置较小的光伏发电供电系统&#xff0c;以满足特定用户的需求。根据通知&#xff0c;分布式光伏系统主要有以下几类定义&#xff1a; 10kV以下电压等级接入&#xff0c;且单个并网点总装机容量不超过6MW的分布式电源&#xff1a;这…

初识WebGL

思路&#xff1a; 构建<canvas>画布节点&#xff0c;获取其的实例。使用getWebGLContext() 拿到画布上下文。拿到上下文用clearColor() 设置背景颜色。最后清空canvas画布,是为了清除颜色缓冲区。 html结构&#xff1a; <!DOCTYPE html> <html lang"en&…