快速排序(quick sort)

news2024/12/23 17:28:43

  欢迎来到@一夜看尽长安花 博客,您的点赞和收藏是我持续发文的动力

对于文章中出现的任何错误请大家批评指出,一定及时修改。有任何想要讨论的问题可联系我:3329759426@qq.com 。发布文章的风格因专栏而异,均自成体系,不足之处请大家指正。

    专栏:

  • java全栈
  • C&C++
  • PythonAI
  • PCB设计

文章概述:ACM算法 ——快排

关键词:ACM 快排

本文目录

快速排序(quick sort)

原理

步骤

示例:

C语言版

C++版

算法特性

快速排序为什么快

基准数优化

示例:

尾递归优化

快速排序(quick sort)

是一种基于分治策略的排序算法,运行高效,应用广泛。

原理

快速排序的核心操作是“哨兵划分”,其目标是:选择数组中的某个元素作为“基准数”,将所有小于基准数的元素移到其左侧,而大于基准数的元素移到其右侧。具体来说,哨兵划分的流程如图 所示。

步骤

  • 确定分界点X:
  • 调整区间:
  1. 选取数组最左端元素作为基准数,初始化两个指针 ij 分别指向数组的两端。
  2. 设置一个循环,在每轮中使用 ij)分别寻找第一个比基准数大(小)的元素,然后交换这两个元 素。
  3. 循环执行步骤 2. ,直到 ij 相遇时停止,最后将基准数交换至两个子数组的分界线。
  • 递归处理左右两端

示例:

C语言版

#include <stdio.h>
#include <stdlib.h>

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

void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int i = l - 1, j = r + 1, x = q[l+r >> 1];
    while (i < j)
    {
        do i ++ ; while (q[i] < x);
        do j -- ; while (q[j] > x);
        if (i < j) swap(&q[i], &q[j]);
    }
    quick_sort(q, l, j), quick_sort(q, j + 1, r);
}



int main(void)
{
    int n;
    
    scanf("%d", &n);
    int *q = (int *)malloc(n * sizeof(int));
    if (q == NULL)
    {
        fprintf(stderr, "Memory allocation failed.\n");
        return 1;
    }

    for (int i = 0; i < n; i++) 
    {
        scanf("%d", &q[i]);
    }

    quick_sort(q, 0, n - 1);

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

    free(q);

    return 0;
}

C++版

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int n;
const int N = 100002;
int nums[N];

void quick_sort(int q[], int l, int r) {
    if (l >= r) {
        return;
    }
    int x = q[ l+r >> 1];
    int i = l - 1, j = r + 1;
    while (i < j) {
        do i++; while (q[i] < x);
        do j--; while (q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j);
    quick_sort(q, j + 1, r);
}

int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &nums[i]);
    }
    quick_sort(nums, 0, n - 1);
    for (int i = 0; i < n; i++) {
        printf("%d ", nums[i]);
    }
    return 0;
}

算法特性

  • 时间复杂度为 𝑂(𝑛log𝑛)、自适应排序:在平均情况下,哨兵划分的递归层数为 log⁡𝑛 ,每层中的总循环数为 𝑛 ,总体使用 𝑂(𝑛log⁡𝑛) 时间。在最差情况下,每轮哨兵划分操作都将长度为 𝑛 的数组划分为长度为 0 和 𝑛−1 的两个子数组,此时递归层数达到 𝑛 ,每层中的循环数为 𝑛 ,总体使用 𝑂(𝑛2) 时间。
  • 空间复杂度为 𝑂(𝑛)、原地排序:在输入数组完全倒序的情况下,达到最差递归深度 𝑛 ,使用 𝑂(𝑛) 栈帧空间。排序操作是在原数组上进行的,未借助额外数组。
  • 非稳定排序:在哨兵划分的最后一步,基准数可能会被交换至相等元素的右侧。

快速排序为什么快

从名称上就能看出,快速排序在效率方面应该具有一定的优势。尽管快速排序的平均时间复杂度与“归并排序”和“堆排序”相同,但通常快速排序的效率更高,主要有以下原因。

  • 出现最差情况的概率很低:虽然快速排序的最差时间复杂度为 𝑂(𝑛2) ,没有归并排序稳定,但在绝大多数情况下,快速排序能在 𝑂(𝑛log⁡𝑛) 的时间复杂度下运行。
  • 缓存使用效率高:在执行哨兵划分操作时,系统可将整个子数组加载到缓存,因此访问元素的效率较高。而像“堆排序”这类算法需要跳跃式访问元素,从而缺乏这一特性。
  • 复杂度的常数系数小:在上述三种算法中,快速排序的比较、赋值、交换等操作的总数量最少。这与“插入排序”比“冒泡排序”更快的原因类似。

基准数优化

快速排序在某些输入下的时间效率可能降低。举一个极端例子,假设输入数组是完全倒序的,由于我们选择最左端元素作为基准数,那么在哨兵划分完成后,基准数被交换至数组最右端,导致左子数组长度为 𝑛−1、右子数组长度为 0 。如此递归下去,每轮哨兵划分后都有一个子数组的长度为 0 ,分治策略失效,快速排序退化为“冒泡排序”的近似形式。

为了尽量避免这种情况发生,我们可以优化哨兵划分中的基准数的选取策略。例如,我们可以随机选取一个元素作为基准数。然而,如果运气不佳,每次都选到不理想的基准数,效率仍然不尽如人意。

需要注意的是,编程语言通常生成的是“伪随机数”。如果我们针对伪随机数序列构建一个特定的测试样例,那么快速排序的效率仍然可能劣化。

为了进一步改进,我们可以在数组中选取三个候选元素(通常为数组的首、尾、中点元素),并将这三个候选元素的中位数作为基准数。这样一来,基准数“既不太小也不太大”的概率将大幅提升。当然,我们还可以选取更多候选元素,以进一步提高算法的稳健性。采用这种方法后,时间复杂度劣化至 𝑂(𝑛2) 的概率大大降低。

示例:

/* 选取三个候选元素的中位数 */
int medianThree(int nums[], int left, int mid, int right) {
    int l = nums[left], m = nums[mid], r = nums[right];
    if ((l <= m && m <= r) || (r <= m && m <= l))
        return mid; // m 在 l 和 r 之间
    if ((m <= l && l <= r) || (r <= l && l <= m))
        return left; // l 在 m 和 r 之间
    return right;
}

/* 哨兵划分(三数取中值) */
int partitionMedian(int nums[], int left, int right) {
    // 选取三个候选元素的中位数
    int med = medianThree(nums, left, (left + right) / 2, right);
    // 将中位数交换至数组最左端
    swap(nums, left, med);
    // 以 nums[left] 为基准数
    int i = left, j = right;
    while (i < j) {
        while (i < j && nums[j] >= nums[left])
            j--; // 从右向左找首个小于基准数的元素
        while (i < j && nums[i] <= nums[left])
            i++;          // 从左向右找首个大于基准数的元素
        swap(nums, i, j); // 交换这两个元素
    }
    swap(nums, i, left); // 将基准数交换至两子数组的分界线
    return i;            // 返回基准数的索引
}

尾递归优化

在某些输入下,快速排序可能占用空间较多。以完全有序的输入数组为例,设递归中的子数组长度为 𝑚 ,每轮哨兵划分操作都将产生长度为 0 的左子数组和长度为 𝑚−1 的右子数组,这意味着每一层递归调用减少的问题规模非常小(只减少一个元素),递归树的高度会达到 𝑛−1 ,此时需要占用 𝑂(𝑛) 大小的栈帧空间。

为了防止栈帧空间的累积,我们可以在每轮哨兵排序完成后,比较两个子数组的长度,仅对较短的子数组进行递归。由于较短子数组的长度不会超过 𝑛/2 ,因此这种方法能确保递归深度不超过 log⁡𝑛 ,从而将最差空间复杂度优化至 𝑂(log⁡𝑛)

C++版

#include <iostream>
#include <algorithm>
#include <cstdlib> // for rand() and srand()
#include <ctime>   // for time()

using namespace std;

const int N = 100002;
int nums[N];

// 随机选择基准数并进行交换
int randomPartition(int nums[], int left, int right) {
    int randomIndex = left + rand() % (right - left + 1);
    swap(nums[left], nums[randomIndex]); // 将随机选择的基准数交换至数组最左端
    int pivot = nums[left]; // 以 nums[left] 为基准数
    int i = left + 1, j = right;
    while (true) {
        while (i <= right && nums[i] < pivot) i++;
        while (j >= left && nums[j] > pivot) j--;
        if (i >= j) break;
        swap(nums[i], nums[j]);
        i++;
        j--;
    }
    swap(nums[left], nums[j]); // 将基准数交换至两子数组的分界线
    return j; // 返回基准数的索引
}

// 快速排序,使用尾递归优化
void quick_sort(int nums[], int left, int right) {
    while (left < right) {
        int mid = randomPartition(nums, left, right);
        if (mid - left < right - mid) {
            quick_sort(nums, left, mid - 1);
            left = mid + 1;
        } else {
            quick_sort(nums, mid + 1, right);
            right = mid - 1;
        }
    }
}

int main() {
    srand(time(0)); // 设置随机数种子
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> nums[i];
    }
    quick_sort(nums, 0, n - 1);
    for (int i = 0; i < n; i++) {
        cout << nums[i] << " ";
    }
    return 0;
}

C语言版:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define N 100002

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

int randomPartition(int nums[], int left, int right) {
    int randomIndex = left + rand() % (right - left + 1);
    swap(&nums[left], &nums[randomIndex]);
    int pivot = nums[left];
    int i = left + 1, j = right;
    while (1) {
        while (i <= right && nums[i] < pivot) i++;
        while (j >= left && nums[j] > pivot) j--;
        if (i >= j) break;
        swap(&nums[i], &nums[j]);
        i++;
        j--;
    }
    swap(&nums[left], &nums[j]);
    return j;
}

void quick_sort(int nums[], int left, int right) {
    if (left >= right) return;
    int mid = randomPartition(nums, left, right);
    quick_sort(nums, left, mid - 1);
    quick_sort(nums, mid + 1, right);
}

int main() {
    srand(time(0));
    int nums[N];
    int n, i;
    
    scanf("%d", &n);
   
    for (i = 0; i < n; i++) {
        scanf("%d", &nums[i]);
    }
    
    quick_sort(nums, 0, n - 1);
    
  
    for (i = 0; i < n; i++) {
        printf("%d ", nums[i]);
    }
    printf("\n");
    
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nftables(9)NAT、FLOWTABLES

NAT NAT简介 我们在iptables、firewalld中都介绍过有关NAT的相关部分。那么在nftables中&#xff0c;我们继续介绍nftables中NAT的功能实现方式&#xff0c;配置方法和与前两者的区别。 我们先简单回顾一下NAT的类型和其功能&#xff1a; 这些是不同的网络地址转换&#xf…

在 Windows 上开发.NET MAUI 应用_1.安装开发环境

开发跨平台的本机 .NET Multi-platform App UI (.NET MAUI) 应用需要 Visual Studio 2022 17.8 或更高版本&#xff0c;或者具有 .NET MAUI 扩展的最新 Visual Studio Code。要开始在 Windows 上开发本机跨平台 .NET MAUI 应用&#xff0c;请按照安装步骤安装 Visual Studio 20…

leetcode94. 二叉树的中序遍历,递归法+迭代法。附带前序遍历方法

leetcode94. 二叉树的中序遍历 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[] 示例 3&#xff1a; …

高职综合布线实训室

一、高职综合布线实训室建设背景 随着《国民经济和社会发展第十四个五年规划和2035年远景目标纲要》的深入实施&#xff0c;数字化转型已成为国家发展的核心战略之一&#xff0c;计算机网络技术作为数字化建设的基石&#xff0c;其重要性日益凸显。然而&#xff0c;面对数字时代…

【手撕RLHF-DPO(1)】不是PPO训不起,而是DPO更有性价比!

Introduction Direct Preference Optimization: Your Language Model is Secretly a Reward Model 在LLM对齐问题上&#xff0c;OpenAI提出的RLHF训练范式最为人熟知&#xff0c;同时也是ChatGPT行之有效的对齐方案。 RLHF通常包含三个步骤&#xff1a;SFT, Reward Model, PPO…

【STM32】RTT-Studio中HAL库开发教程三:IIC通信--AHT20

文章目录 一、I2C总线通信协议二、AHT20传感器介绍三、STM32CubeMX配置硬件IIC四、RTT中初始化配置五、具体实现代码六、实验现象 一、I2C总线通信协议 使用奥松的AHT20温湿度传感器&#xff0c;对环境温湿度进行采集。AHT20采用的是IIC进行通信&#xff0c;可以使用硬件IIC或…

Visual Studio使用——在vs中给vb.net项目添加新的窗口:新建的方式、添加已有窗口的方式

目录 引出Visual Studio使用vb添加新的窗体自定义代码片段vs显示所有文件 总结Idea安装和使用0.Java下载 和 IDEA工具1.首次新建项目2.隐藏文件不必要显示文件3.目录层级设置4.Settings设置选择idea的场景提示代码不区分大小写 取消git的代码作者显示 引出 Visual Studio使用—…

trl - 微调、对齐大模型的全栈工具

文章目录 一、关于 TRL亮点 二、安装1、Python包2、从源码安装3、存储库 三、命令行界面&#xff08;CLI&#xff09;四、如何使用1、SFTTrainer2、RewardTrainer3、PPOTrainer4、DPOTrainer 五、其它开发 & 贡献参考文献最近策略优化 PPO直接偏好优化 DPO 一、关于 TRL T…

安全防御,防火墙配置NAT转换智能选举综合实验

目录&#xff1a; 一、实验拓扑图 二、实验需求 三、实验大致思路 四、实验步骤 1、防火墙的相关配置 2、ISP的配置 2.1 接口ip地址配置&#xff1a; 3、新增设备地址配置 4、多对多的NAT策略配置&#xff0c;但是要保存一个公网ip不能用来转换&#xff0c;使得办公区的…

c++入门----类与对象(上)

大家好啊&#xff0c;好久没有更新了。因为本人的愚笨&#xff0c;想与大家分享的话肯定还得自己明白了才能给大家分享吧。所以这几天都在内部消化。好给大家优质的文章。当然我写的肯定还是很有问题的&#xff0c;希望大家可以在评论区里面指出来。好&#xff0c;废话不多说&a…

LabVIEW 与 PLC 通讯方式

在工业自动化中&#xff0c;LabVIEW 与 PLC&#xff08;可编程逻辑控制器&#xff09;的通信至关重要&#xff0c;常见的通信方式包括 OPC、Modbus、EtherNet/IP、Profibus/Profinet 和 Serial&#xff08;RS232/RS485&#xff09;。这些通信协议各有特点和应用场景&#xff0c…

从图表访问Data Store Memory

Simulink模型将全局变量实现为数据存储&#xff0c;可以是数据存储内存块&#xff0c;也可以是Simulink.Signal的实例。您可以使用数据存储在多个Simulink块之间共享数据&#xff0c;而无需显式的输入或输出连接来将数据从一个块传递到另一个块。Stateflow图表通过符号化地读取…

警惕预言成真!3本预警、On Hold已被剔除,新增8本SCI/SSCI被除名!7月WOS更新(附下载)

本周投稿推荐 SCI • 能源科学类&#xff0c;1.5-2.0&#xff08;25天来稿即录&#xff09; • IEEE计算机类&#xff0c;4.0-5.0&#xff08;48天录用&#xff09; • 生物医学制药类&#xff08;2天逢投必中&#xff09; EI • 各领域沾边均可&#xff08;2天录用&…

精益思维在数字工厂建设中的具体应用

在数字化浪潮席卷全球的今天&#xff0c;数字工厂建设已成为企业转型升级的必由之路。然而&#xff0c;如何确保数字工厂的高效运行和持续创新&#xff0c;成为摆在众多企业面前的难题。精益思维&#xff0c;作为一种追求持续改进和卓越绩效的管理理念&#xff0c;正成为助力数…

iPhone手机怎么识别藏文?藏语翻译通App功能介绍:藏文OCR识别提取文字

在工作学习的过程中&#xff0c;遇到不会的藏文&#xff0c;也不知道怎么把文字打出来&#xff0c;这个时候可以试试《藏语翻译通》App的图片识别功能&#xff0c;支持拍照识别和图片识别&#xff0c;拍一拍就能提取藏文文字&#xff0c;并支持一键翻译和复制分享。 跟着小编的…

汽车免拆诊断案例 | 2017 款林肯大陆车发动机偶尔无法起动

故障现象 一辆2017款林肯大陆车&#xff0c;搭载2.0T发动机&#xff0c;累计行驶里程约为7.5万km。车主进厂反映&#xff0c;有时按下起动按钮&#xff0c;起动机不工作&#xff0c;发动机无法起动&#xff0c;组合仪表点亮正常&#xff1b;多次按下起动按钮&#xff0c;发动机…

01大学物理电磁篇 静电场

5-6 静电场的环路定理 电势能 5-7电势 5-8电场强度与电势梯度

背部筋膜炎最有效的治疗方法

背部筋膜炎症状&#xff1a;背部筋膜炎引起的疼痛通常是钝痛或酸痛&#xff0c;且这种疼痛是无菌性炎症产生的炎症因子、疼痛因子刺激局部神经引起的。疼痛主要发生在腰背部&#xff0c;特别是两侧腰肌和髂嵴上方可能会更加明显。长时间不活动或活动过度都可能诱发疼痛。疼痛可…

使用element UI Cascader 级联选择器实现省/市/区选择

<template><div><label>位置</label><el-cascader:options"pcaTextArr"v-model"selectedOptions"change"handleChangeAddress":props"{expandTrigger: hover,multiple: true,checkStrictly: true,emitPath: fal…

windows 打包pyd文件

1.新建一个py文件&#xff0c;myunit.py&#xff0c;里面的代码是: class Adder: def __init__(self, a, b): self.a a self.b b def add(self): return self.a self.b 2.新建一个py文件&#xff0c;setup.py&#xff0c;里面的代码是: from setuptools import setup fro…