【数据结构】快速排序算法你会写几种?

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:数据结构
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵
希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 一、hoare版本(左右指针法)
      • 1.1 算法思想
      • 1.2 hoare版本代码实现
      • 1.3 hoare版本性能分析
      • 1.4 基准值选取随机值(优化)
      • 1.5 三数取中(优化)
      • 1.6 三路划分
  • 二、挖坑法
      • 2.1 算法思路
      • 2.2 代码实现
  • 三、前后指针法(最优 + 最好写的方法)
      • 3.1 算法思路
      • 3.2 代码实现

一、hoare版本(左右指针法)

1.1 算法思想

【思想】

  1. 任取待排序元素序列中的某元素作为 基准值key。一般是第一个元素和最后一个元素。

  2. 【重点】 将待排序集合 分割成两子序列使得左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值。

做法:定义两个变量ij分别指向开头和最后一个元素。请务必记住次结论:如果选取的基准值是第一个元素,要让j先动,反之让i先动。假设选取的基准值为第一个元素并且要求序列为升序。

j遇到小于等于 key的数,则停下,然后i开始走,直到i遇到一个大于key的数时,将ij的内容交换,如果区间内还有元素,则重复以上操作。最后你会发现:ij最后一定会相遇(可以参考下面动图),此时将相遇点的内容与key交换即可

  1. 最后左右子序列重复该过程,直到所有元素都排列在相应位置上为止。(递归)

以下是一趟排序的动图演示

在这里插入图片描述

在我的往期博客中,我写了一篇快排算法模板,有兴趣的可以来看看:点击跳转

1.2 hoare版本代码实现

#include <stdio.h>

void Swap(int* x, int* y)
{
    int t = *x;
    *x = *y;
    *y = t;
}

// hoare版本
void quick_sort(int a[], int l, int r)
{
    // 如果区间只有一个元素或者没有元素,就没必要排序了
    if (l >= r)
        return;
    // 1. 选取一个基准值
    // 以选取第一个元素为例
    int key = a[l];
    // 2. 定义i和j,i从左向右走,j从右向左走。
    int i = l, j = r;
    while (i < j)
    {
        // 注意:若选择第一个元素作为基准值,则需要j先走;反之让i先走
        while (i < j && a[j] >= key) // 找小
        {
            j--;
        }
        while (i < j && a[i] <= key) // 找大 
        {
            i++;
        }
        // 交换
        if (i < j)
        	Swap(&a[i], &a[j]);
    }
    // 循环结束后,i和j一定会相遇,和基准值交换
    Swap(&a[l], &a[i]);

    // 递归
    quick_sort(a, l, i - 1);
    quick_sort(a, i + 1, r);
}  

int main()
{
    int a[] = { 6, 1, 2, 7, 9, 3, 4, 5, 10, 8 };
    int n = sizeof(a) / sizeof(a[0]);
    quick_sort(a, 0, n - 1);

    for (int i = 0; i < n; i++)
        printf("%d ", a[i]);

    printf("\n");
    return 0;
}

【程序结果】

在这里插入图片描述

注意:这里会有一个越界 + 死循环问题

在这里插入图片描述

  • while (i < j && a[j] >= key)循环中,如果不加i < j,那么假设序列已经是升序了,那么就会越界;while (i < j && a[i] <= key)也同理

在这里插入图片描述

  • 并且如果只写a[i] < key,将序列中出现数据冗余,就会陷入死循环
  • 最后还有一个问题,就是本人初学时犯的(忽略了一个小的知识点qwq)。

与基准值交换Swap(&a[l], &a[i]),不能写成Swap(&key, &a[i])。因为key是一个局部变量,只是存储序列key,虽然交换了,但是序列第一个元素并没有改变。我也是通过调试发现的hh

1.3 hoare版本性能分析

首先可以和堆排序以及希尔排序来比较一下

在这里插入图片描述

我们发现:当数据个数为一百万的时候,快速排序还是非常快的

接下来再来分析时间复杂度

快速排序其实是二叉树结构的交换排序方法

在这里插入图片描述

递归的高度是logN,而单趟排序基本都要遍历一遍序列,大约有N个数,因此时间复杂度是NlogN

那么快排最坏的情况是什么?

最坏的情况即包括逆序,也包括有序。其时间复杂度是O(N2)

在这里插入图片描述

如果数据量大的话,那么栈一定会爆。那如果是这样,快排还叫快排吗?

先说结论:快排的时间复杂度是:O(NlogN)

那么如何解决这个问题呢?我们发现,有序和无序就是因为基准值选取的不好。

因此,有人提出了优化基准值可以选取随机值或者三数取中

1.4 基准值选取随机值(优化)

做法:使用rand函数随机选取区间中的下标rand() % (r - l),但是这样远远不够,因为递归的时候,左区间会随之改变。因此正确下标取法rand() % (r - l) + l

void quick_sort(int a[], int l, int r)
{
    if (l >= r)
        return;
    // 随机选key
    // 区间下标范围
    int randIdx = (rand() % (r - l)) + l; 
    Swap(&a[l], &a[randIdx]);
    
    // 以下都和hoare版本一样
    int key = a[l];
    int i = l, j = r;
    while (i < j)
    {
        while (i < j && a[j] >= key) 
        {
            j--;
        }
        while (i < j && a[i] <= key)
        {
            i++;
        }
        Swap(&a[i], &a[j]);
    }
    Swap(&a[l], &a[i]);

    quick_sort(a, l, i - 1);
    quick_sort(a, i + 1, r);
}

【程序结果】

在这里插入图片描述

1.5 三数取中(优化)

  • 三数取中是指:第一个元素、最后一个元素和中间元素,选出不是最小也不是最大的那一个(找的是下标)
int GetMinNum(int a[], int l, int r)
{
    int mid = (l + r) >> 1;
    // 选出不是最大也不是最小的
    // 两两比较
    if (a[l] < a[mid])
    {
        if (a[mid] < a[r])
        {
            return mid;
        }
        else if (a[r] < a[l])
        {
            return l;
        }
        else
        {
            return r;
        }
    }
    else // a[l] >= a[mid]
    {
        if (a[l] < a[r])
        {
            return l;
        }
        else if (a[r] < a[mid])
        {
            return mid;
        }
        else
        {
            return r;
        }
    }
}

void quick_sort(int a[], int l, int r)
{
    if (l >= r)
        return;
    // 三数取中

    int mid = GetMinNum(a, l, r);
    Swap(&a[mid], &a[l]);
    int key = a[l];

    int i = l, j = r;

    while (i < j)
    {
        while (i < j && a[j] >= key) 
        {
            j--;
        }
        while (i < j && a[i] <= key)
        {
            i++;
        }
        Swap(&a[i], &a[j]);
    }
    Swap(&a[l], &a[i]);

    quick_sort(a, l, i - 1);
    quick_sort(a, i + 1, r);
}

【程序结果】

在这里插入图片描述

1.6 三路划分

这个优化是为了解决大量元素重复的问题,这个博主还未学到。暂且先放放hh

二、挖坑法

2.1 算法思路

在这里插入图片描述

【算法思路】

  1. 选取一个基准值并用一个变量保存(这个基值一般是第一个元素或者是最后一个元素),然后序列中基准值这个位置相当于是一个坑等待下一个元素填入
  2. 如果选取的基准值是第一个元素,老样子j要从右边向左开始找小,如果找到小,就将j指向的元素填入到坑中,而此时 j这个位置是一个坑等待填入;接下来就是i从左向右找大,如果找到了大,就将i指向的元素填入到坑中,同理的,i这个位置是一个坑等待填入
  3. 最后ij相遇,并且一起站着一个坑位hole,然后就把基准值key填入即可
  4. 递归重复区间[l, hole - 1]和区间[hole + 1, r]

本质上来说,填坑法和hoare版本类似,相比其更加容易理解

2.2 代码实现

void QuickSort5(int a[], int l, int r)
{
	if (l >= r) return;
	int x = a[l];
	// 如果选择左端点为基准值
	// 那么坑位一开始是以基准值为下标
	int hole = l;

	int i = l, j = r;
	while (i < j)
	{
		while (i < j && a[j] >= x) // 找小
		{
			j--;
		}
		// 循环结束后,来到此处说明找到小了
		// 将小的填入上一个坑位
		// 再更新坑位
		a[hole] = a[j];
		hole = j;

		while (i < j && a[i] <= x)
		{
			i++;
		}
		// 和上同理
		a[hole] = a[i];
		hole = i;
	}
	// 最后i和j相遇一定会同站一个坑位
	// 将基准值填入坑位即可
	a[hole] = x;
	// 递归
	QuickSort5(a, l, hole - 1);
	QuickSort5(a, hole + 1, r);
}

三、前后指针法(最优 + 最好写的方法)

3.1 算法思路

在这里插入图片描述

  1. 选出一个基准值key,一般是最左边或是最右边的。
  2. 起始时,prev指针指向序列开头,cur指针指向prev的下一个位置。
  3. cur指向的内容小于keyprev先向后移动一位,然后交换prevcur指针指向的内容,然后cur指针继续向后遍历
  4. cur指向的内容大于key,则cur指针直接向后遍历。因此可以得出结论,cur本质就是在找小,然后让小的往前靠
  5. cur超出序列,此时将keyprev指针指向的内容交换即可。

经过一次单趟排序,最终也能使得key左边的数据全部都小于keykey右边的数据全部都大于key

最后再重复以上操作,直至序列只有一个数据或者序列没有数据时。(递归区间[l, prev - 1][prev + 1, r]

3.2 代码实现

void quick_sort(int a[], int l, int r)
{
    if (l >= r)
        return;
        
    // 1. 选出一个key
    int key = a[l];
    // 2. 起始时,prev指针指向序列开头,cur指针指向prev+1。
    int prev = l, cur = prev + 1;
    
    while (cur <= r)
    {
        // 3. 若cur指向的内容小于key
        // 则prev先向后移动一位,
        // 然后交换prev和cur指针指向的内容,
        // 然后cur指针++(可以归到第四点)
        if (a[cur] < key)
        {
            ++prev;
            Swap(&a[prev], &a[cur]);
        }
        // 4. 若cur指向的内容大于key,则cur指针直接++
        ++cur;
    }
    // 若cur到达r + 1位置,此时将key和prev指针指向的内容交换即可。
    Swap(&a[l], &a[prev]);
    // 递归
    quick_sort(a, l, prev - 1);
    quick_sort(a, prev + 1, r);
}

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

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

相关文章

论文技巧2

目录 1 找基准模型2 找模块小论文的三个实验怎么做对比试验Sota的挑选对⽐论⽂结果的获取3 消融实验什么是消融实验怎么做消融实验4 实例分析怎么做实例分析小论文必备三张图1 找基准模型 2 找模块 小论文的三个实验 怎么做对比试验

调整Windows键盘上只能看到拼音而无法看到实际的文本以及关闭输入法悬浮窗方法

一、输入法设置 如果您在键盘上只能看到拼音而无法看到实际的文本&#xff0c;这可能是因为您的输入法设置为中文拼音输入法或其他仅显示拼音的输入法。 要解决这个问题&#xff0c;您可以尝试以下方法&#xff1a; 1. 切换输入法&#xff1a;按下 Shift Alt 组合键或 Wind…

μC/OS-II---消息队列管理1(os_q.c)

目录 消息队列的主要优点消息队列和消息邮箱消息队列相关操作消息队列创建消息队列删除在消息队列等待消息 消息队列的主要优点 消息队列的主要优点是解耦和异步通信。发送者和接收者之间不需要直接建立连接&#xff0c;它们只需要知道消息队列的名称或标识符即可。发送者将消…

分布式教程从0到1【1】分布式基础

1 分布式基础概念 1.1 微服务 微服务架构风格&#xff0c;就像是把一个单独的应用程序开发为一套小服务&#xff0c;每个小服务运行在自己的进程中&#xff0c;并使用轻量级机制通信&#xff0c;通常是 HTTP API。这些服务围绕业务能力来构建&#xff0c;并通过完全自动化部署…

全链路压测:确保系统稳定性的关键一环

在当今高度数字化的业务环境中&#xff0c;系统的稳定性和性能至关重要。全链路压测作为确保系统在各种负载下依然可靠运行的关键工具&#xff0c;越来越受到企业的关注。本文将深入探讨全链路压测的概念及目的。 一、全链路压测概念 全链路压测是一种模拟真实用户行为、模拟全…

Python与ArcGIS系列(八)通过python执行地理处理工具

目录 0 简述1 脚本执行地理处理工具2 在地理处理工具间建立联系0 简述 arcgis包含数百种可以通过python脚本执行的地理处理工具,这样就通过python可以处理复杂的工作和批处理。本篇将介绍如何利用arcpy实现执行地理处理工具以及在地理处理工具间建立联系。 1 脚本执行地理处理…

ai剪辑矩阵系统源码+无人直播系统源码技术开发

开发AI剪辑矩阵系统和无人直播系统源码&#xff0c;需要以下步骤&#xff1a; 1. 市场调研&#xff1a;了解市场需求和竞品情况&#xff0c;明确系统的功能和特点。 2. 系统设计&#xff1a;设计系统的整体架构和功能模块&#xff0c;包括视频剪辑、直播推流、实时互动、数据分…

亚马逊EC2服务器搭建Linux系统宝塔环境

目录 &#x1f4dd;摘要 &#x1f4a1;引言 一. 购买亚马逊服务器EC2 二. 安装Linux系统 三. 在终端安装宝塔 3.1 安装宝塔 3.2安装成功 四. 配置宝塔 五 应用场景 六 代码案例演示 七 为什么选择亚马逊EC2服务器部署&#xff1f; &#x1f4aa; 可靠性和高可用性 灵…

Django部署时静态文件配置的坑

Django部署时静态文件配置配置的坑 近期有个需求是用django进行开发部署&#xff0c;结果发现静态文件配置的坑是真的多&#xff0c;另外网上很多的内容也讲不清楚原理&#xff0c;就是这样这样&#xff0c;又那样那样&#xff0c;进了不少坑&#xff0c;这里记录一下关于css,…

软件工程理论与实践 (吕云翔) 第六章 面向对象分析课后习题及其解析

第六章 面向对象分析 知识点: 一个典型的软件系统通常包括的内容为&#xff1a;它使用数据结构&#xff08;对象模型&#xff09;&#xff0c;执行操作&#xff08;动态模型&#xff09;&#xff0c;并且完成数据值的变化&#xff08;功能模型&#xff09;。 3种模型之间的关…

python科研绘图:面积图

目录 1、面积图 2、堆积面积图 1、面积图 面积图是一种数据可视化图表&#xff0c;用于展示数据随时间或其他有序类别的变化趋势。它与折线图相似&#xff0c;但在展示数据变化的同时&#xff0c;面积图还强调了各个数据点之间的累积关系。这种图表通常通过在折线下方填充颜…

二叉树(进阶)

文章目录 1.内容安排说明2. 二叉搜索树2.1二叉搜索树的概念2.2二叉搜索树的实现2.3二叉树的性能&#xff1a; 搜索二叉树的应用k 模型kv模型 1.内容安排说明 二叉树在前面c数据结构阶段&#xff1b;已经讲过了&#xff1b;本节取名二叉树进阶的原因是&#xff1a; 1.map和set特…

04-学成在线之系统管理服务模块之查询数据字典表中的内容,前后端联调测试

前后端联调 配置前端环境 实际开发中先由后端工程师将接口设计好并编写接口文档并交给前端工程师&#xff0c;前后端的工程师就开始并行开发 前端开发人员先自己mock数据即使用假数据进行开发,当后端代码完成后前端工程师尝试请求后端接口获取数据然后渲染到页面 第一步: 首…

k8s-部署Redis-cluster(TLS)

helm pull bitnami/redis-cluster v8.3.8拉取源码生成证书 git clone https://github.com/redis/redis.git #文档 https://redis.io/docs/management/security/encryption/#getting-started生成你的TLS证书用官网的工具生成 1 Run ./utils/gen-test-certs.sh 生成根CA和服务…

java 工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发

工程项目管理软件&#xff08;工程项目管理系统&#xff09;对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营&#xff0c;全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&am…

latex简单使用

​​文章目录 公式详解 普通公式公式居中带标号公式上标下标根号分式括号运算符列表 无序列表有序列表插入图片 单图多图排版表格脚注与定理子标题目录与附录 目录附录参考文献字体设置 字体样式 加粗斜体字母大写等线自定义字体字体大小 第一种设置第二种设置第三种设置 页面…

使用Spring Boot实现大文件断点续传及文件校验

一、简介 随着互联网的快速发展&#xff0c;大文件的传输成为了互联网应用的重要组成部分。然而&#xff0c;由于网络不稳定等因素的影响&#xff0c;大文件的传输经常会出现中断的情况&#xff0c;这时需要重新传输&#xff0c;导致传输效率低下。 为了解决这个问题&#xff…

第四代智能井盖传感器:万宾科技助力城市安全

在繁华喧嚣的城市里人来人往&#xff0c;井盖作为基础设施的一个组成部分在路面上分布范围广。然而这些看似普通的井盖却存在着位移、水浸的风险&#xff0c;可能给我们的生活带来诸多不便&#xff0c;更会威胁到我们的人身安全。如何有效监测和管理井盖的状态&#xff0c;成为…

leetcode刷题日记:160. Intersection of Two Linked Lists(相交链表)

给出两个单链表的头结点headA与headB&#xff0c;让我们找出两个链表相接的起始节点&#xff0c;如果两个链表不存在相交结点返回null。 我们就先假设存在这样两个链表&#xff0c;链表1与链表2&#xff0c;假设链表1的长度为 L 1 L_1 L1​和 L 2 L_2 L2​,假设对于两个链表&am…

MatrixOne完成与欧拉、麒麟信安的兼容互认

近日&#xff0c;超融合异构云原生数据库MatrixOne企业版软件V1.0完成了与欧拉开源操作系统&#xff08;openEuler简称“欧拉”&#xff09;、麒麟信安操作系统系列产品和虚拟化平台的相互兼容认证&#xff0c;通过了欧拉兼容性测评&#xff0c;获得了《openEuler技术测评证书》…