数据结构——排序(3):交换排序(续)

news2024/11/26 18:42:29

目录

一、快速排序

(1)hoare版本

①思路

②过程图示

③思考

④代码实现

⑤代码解释 

(2)挖坑法

①思路

②过程图示

③思考

④代码实现

⑤代码解释

(3)lomuto前后指针

①思路

②过程图示

③思考

④代码实现

⑤代码解释

(4)快速排序的复杂度

(5)非递归版本

①代码实现

②代码解释

二、写在最后


一、快速排序

我们上次学到了快速排序的递归方式,但是找基准值的函数(_QuickSort)如何实现呢?

(1)hoare版本

①思路

首先创建左右指针来寻找基准值。具体步骤为

一个指针从左到右找出比基准值大的数据,另一个指针从右到左找出比基准值小的数据,交换两者的位置,进入下一次循环。

②过程图示

1. 首先key指向第一个数据,left指向第二个数据,right指向最后一个数据;

2.right从右向左遍历找比key小的数据(3),left从左向右遍历找比key大的数据(7);

3.将两者交换位置;

4..right往后走一步,left往前走一步;(此时两者相遇)

5.right向左找比key小的数据(3),left向右找比key大的数据(9);(此时,left走到了right右边,跳出循环);

6.将right位置和key位置的数据交换位置,此时right位置的数据就是我们要找的key值(基准值);

7.基准值为6,此时基准值左侧的数据比其小,右侧的数据比其大。

③思考

1.若left==right是否继续循环?

 场景1:过程图示中的例子,此处不再赘述。

假设left==right时不再进行循环,则:

此时得到基准值为6,但是不满足“基准值左边都是小于它的数据,基准值右边都是大于它的数据”。 


场景2:

1. 首先key指向第一个数据,left指向第二个数据,right指向最后一个数据;

2.right从右向左遍历找比key小的数据(3),left从左向右遍历找比key大的数据(7);

3.将两者交换位置;

4.right往后走一步,left往前走一步;(此时两者相遇)

5.right找到了比基准值<=的数据(6),left找到了比基准值>=的数据(6);(此时,left走到了right右边,跳出循环);

6.将right位置和key位置的数据交换位置,此时right位置的数据就是我们要找的key值(基准值);

7.基准值为6,此时基准值左侧的数据比其小,右侧的数据比其大(或等于)。


 场景3:

1. 首先key指向第一个数据,left指向第二个数据,right指向最后一个数据;

2.right从右向左遍历找比key小的数据(3),left从左向右遍历找比key大的数据(7);

3.将两者交换位置;

4.right往后走一步,left往前走一步;(此时两者相遇)

5.right找到了比基准值小的数据(4),left找到了比基准值大的数据(7);(此时,left走到了right右边,跳出循环);

6.将right位置和key位置的数据交换位置,此时right位置的数据就是我们要找的key值(基准值);

7.基准值为6,此时基准值左侧的数据比其小,右侧的数据比其大。


通过场景1我们得出left==right仍继续循环,否则在“相遇值大于key值”的情况下会出错!


2.当left或right指向的值与key相等时,是否进行交换?

我们以数组{6,6,6,8,6,6}为例:

(1)假设left或right指向的值与key相等时交换:

在第三次交换中,left和right相遇,我们不跳出循环(思考1中已经解释)。因此得到基准值为6。 


 (2)假设left或right指向的值与key相等时不交换: 

1. 首先key指向第一个数据,left指向第二个数据,right指向最后一个数据;

2.right从右向左遍历找比key小的数据,没找到且越界了(此时left在right右边,跳出循环);

3.将right位置与key交换(right和key在同一个位置,相当于没交换);

4.基准值为6,那么没有左子树。

如果left或right指向的值与key相等时不交换,对于这种情况,分割后数据的个数为:n、n-1、n-2……并不是我们理想的类似于二叉树的分割,效率较低。


我们得出left或right指向的值与key相等时应该交换,否则效率会降低!


④代码实现

int _QuickSort(int* arr, int begin, int end)
{
    int left = begin;
    int right = end;
    int key = left;
    left++;

    while(left <= right)
    {
        while(left <= right && arr[right] > arr[key])
        {
            right--;
        }
        while(left <= right && arr[left] < arr[key])
        {
            left++;
        }
        if(left <= right)
        {
            swap(&arr[left++], &arr[right--]);
        }
    }
    
    swap(&arr[right], &arr[key]);
    return right;

}

⑤代码解释 

首先我们让left位于第二个数据位置,right位于最后一个数据的位置,假设key为第一个数据。在left<=right的情况下,right从右向左找小于基准值的数据,left从左向右找大于基准值的数据,当两者均找到时交换位置,(不要忘记让两者继续遍历),直至left>right跳出循环。

(2)挖坑法

①思路

创建两个指针left和right,第一个数据的位置为“坑”。right从右向左找出比基准值小的数据,放入“坑”中,当前位置变成新的“坑”;接着lleft从左向右找出比基准值大的数据,放入“坑”中,当前位置又变成新的“坑”。以此循环……最后将最开始“坑”位置的值放入当前位置的坑中,当前位置即基准值。

②过程图示

我们以数组{6,1,2,7,9,3}为例:

1.首先left、hole指向第一个数据,right指向最后一个数据;

2.right找到比基准值小的数据(3),将该位置的数据放入原来的坑中,该位置变成新的“坑” ;

3.left找到比新基准值大的数据(7),将该位置的数据放入原来的坑中,该位置变成新的“坑”;

4..right找到比新基准值<=的数据(7),将该位置的数据放入原来的坑中,该位置变成新的“坑” ;

5.将最开始坑位置的数据放在当前坑中,当前数据为基准值。

③思考

1.若left==right是否继续循环?

假设left==right继续循环:

我们得到基准值为6,不满足“基准值左边都是小于它的数据,基准值右边都是大于它的数据”。


因此若left==right时不继续循环 !


 2.当left或right指向的值与key相等时,是否将其设为新的“坑”?

假设当left或right指向的值与key相等时,将其设为新的“坑”:

我们可以看到,如果将其设为新的坑,会降低效率。


因此,当left或right指向的值与key相等时,不必将其设为新的“坑”!


④代码实现

int _QuickSort(int* arr, int left, int right)
{
    int mid = arr[left];
    int hole = left;
    int key = arr[hole];
    while(left < right)
    {
        while(left < right && arr[right] >= key)
        {
            right--;
        }
        arr[hole] = arr[right];
        hole = right;

        while(left < right && arr[left] <= key)
        {
            left++;
        }
        arr[hole] = arr[left];
        hole = left;
    }
    arr[hole] = key;
    return hole;
}

⑤代码解释

创建两个指针left和right,第一个数据的位置为“坑”。在left<right的情况下,right从右向左找出比基准值小的数据,放入“坑”中,当前位置变成新的“坑”;接着lleft从左向右找出比基准值大的数据,放入“坑”中,当前位置又变成新的“坑”。以此循环……最后将最开始“坑”位置的值放入当前位置的坑中,返回当前位置(即基准值)。

(3)lomuto前后指针

①思路

创建前后两个指针,从左向右找比基准值小的数据进行交换,使小的数据都排在基准值的左边。

②过程图示

我们以数组{6,1,2,7,9,3}为例:

1. prev指向第一个数据,cur指向第二个数据位置,key指向第一个数据位置;

2.此时cur指向的数据小于key,但是prev的下一个数据为cur;

3.让cur往后走一步;

4.此时cur指向的数据也小于key,但是prev的下一个数据仍为cur;

5.让cur继续往后走一步;

6.此时cur指向的数据小于key且prev的下一个数据不为cur,我们让prev和cur位置的数据交换位置;

7.cur继续往后走,此时越界,跳出循环;

8.让key和prev位置的数据交换;

9.此时prev位置的数据就是我们要找的基准值(prev左边的数据都比它小,prev右边的数据都比它大)。

③思考

1.当cur==right是否继续循环?

当然继续循环,在这个代码中,cur相当于在前面遍历,需要遍历到每个元素,而right位置的元素就是最后一个元素。


2.如果cur和key指向的元素相同,cur和prev是否交换?

其实两者情况的结果是一样的。


解释:

我们以数组{6,6,6,6,6}为例:

如果相等不交换:

 如果相等交换:

因此,相等交不交换都是一样的,要么递归左区间,要么递归右区间(二叉树理想下为递归左右区间)。对于全部相等的数组,复杂度都很高。 

④代码实现

int _QuickSort(int* arr, int left, int right)
{
    int prev = left;
    int cur = left + 1;
    int key = left;
    while(cur <= right)
    {
        if(arr[cur] < arr[key] && ++prev != cur)//如果prev和cur相同就不进行交换
        {
            swap(&arr[cur], &arr[prev]);
        }
        cur++;
    }
    swap(&arr[key], &arr[prev]);

    return prev;
}

⑤代码解释

我们先假定第一个元素是基准值,用cur来递归数组的每一个元素,如果小于(或小于等于)基准值,我们设法将它放在前面,即将其放在cur前面的prev位置(prev是一直在++的),循环进行。最后将key和prev交换位置,返回的prev即为基准值。

(4)快速排序的复杂度

1.时间复杂度:O(N*logN);

2.空间复杂度:O(logN)。

(5)非递归版本

需要借助数据结构——栈 。

①代码实现

void QuickSortNonR(int* arr, int left, int right)
{
    Stack st;
    //初始化
    StackInit(&st);
    //入栈
    StackPush(&st, right);
    StackPush(&st, left);
    while(!StackEmpty(&st))
    {
        int begin = StackTop(&st);
        StackPop(&st);
        int end = StackTop(&st);
        StackPop(&st);

        //新数组
        int key = begin;
        int prev = begin;
        int cur = begin + 1;

        //找基准值
        while(cur <= end)
        {
            if(arr[cur] < arr[key] && ++prev != cur)
            {
                swap(&arr[prev], &arr[cur]);
            }
            cur++;
        }
        swap(&arr[key], &arr[prev]);
        key = prev;

        //右子树
        if(key + 1 < end)
        {
            StackPush(&st, end);
            StackPush(&st, key + 1);
        }

        //左子树
        if(begin < key -1)
        {
            StackPush(&st, key - 1);
            StackPush(&st, begin);
        }
    }
    StackDestroy(&st);
}

②代码解释

首先非递归实现排序,我们用到栈,也就是说用栈来模拟递归。

二、写在最后

至此,我们已经学习了插入排序、选择排序、交换排序,还剩下最后一个归并排序,我们下期见~

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

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

相关文章

在写PWM风扇驱动之前:如何透过FG信号,获取风扇转速?

文章目录 一、前言二、风扇规格2.1 线序2.2 FG 信号说明 三、FG公式推导四、软件算法解析4.1 HZ 的概念4.2 算法 五、篇尾 一、前言 PWM风扇也是日常SOC智能设备开发中常见的外围小设备&#xff0c;而对于驱动工程师而言&#xff0c;主要工作就是实现风扇的控制驱动&#xff0…

一文了解什么是全息光学元件?

全息光学元件&#xff08;holographic optical elements&#xff1b;HOE&#xff09;是根据全息术原理制成的光学元件。通常做在感光薄膜材料上。作用基于衍射原理&#xff0c;是一种衍射光学元件。不像普通光学元件&#xff0c;用透明的光学玻璃、晶体或有机玻璃制成&#xff…

Leetcode JAVA刷刷站(34)排序数组中查找元素的首尾位置

一、题目概述 二、思路方向 为了设计一个时间复杂度为 O(log n) 的算法来找到目标值在已排序数组中的开始位置和结束位置&#xff0c;我们可以使用二分查找的变种。首先&#xff0c;我们可以使用二分查找来找到目标值在数组中的任意一个位置&#xff08;如果存在的话&#xf…

linux下对目录文件进行操作(打开目录,读取目录项,关闭目录),进入目录的函数chdir,七篇文件I/O文章小结

目录文件介绍 目录也是一种文件&#xff0c;因此操作流程与普通文件类似&#xff0c;有诸如打开、关闭、定位等概念&#xff0c;但目录是一种特殊的文件&#xff0c;目录存储的数据的最小单位并不是字符&#xff0c;而是目录项。这使得目录跟普通文件又有区别。目录项指的是结…

基于resttemplate实现微服务调用

子工程搭建与管理 新建一个cloud模块来作为公共模块&#xff0c;cloud模块中将管理用于微服务使用各个组件 euerka中的配置 package com.lingyang.euerka.config;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configu…

【Python快速入门和实践018】Python常用脚本-图片合成视频

一、功能介绍 这段代码定义了一个名为 create_video_from_images 的函数&#xff0c;用于将一个文件夹中的图像序列合并成一个视频文件。以下是该函数的主要功能和组成部分的分析&#xff1a; 函数参数 input_folder: 包含图像文件的文件夹路径。output_file: 输出视频文件的…

马斯克发布Grok-2:实时获取X资讯、多模态支持,性能追平GPT-4o

在LLM&#xff08;大型语言模型&#xff09;竞争中&#xff0c;马斯克的XAI无疑是一个强劲的对手。继宣布斥资数十亿美元购买英伟达H100 GPU用于训练自家模型后&#xff0c;XAI迅速推出了其最新的大模型——Grok-2。这一模型不仅在技术性能上对标OpenAI、Anthropic等主流大模型…

Hystrix——服务容错保护库

熔断机制是解决微服务架构中因等待出现故障的依赖方响应而形成任务挤压&#xff0c;最终导致自身服务瘫痪的一种机制&#xff0c;它的功能类似电路的保险丝&#xff0c;其目的是为了阻断故障&#xff0c;从而保护系统稳定性。Hystrix作为Spring Cloud中实现了熔断机制的组件&am…

(亲测有效)SpringBoot项目集成腾讯云COS对象存储(2)

接上文&#xff08;亲测有效&#xff09;SpringBoot项目集成腾讯云COS对象存储&#xff08;1&#xff09;-CSDN博客 目录 3、通用能力类 文件下载 测试 3、通用能力类 文件下载 官方文档介绍了2种文件下载方式。一种是直接下载 COS 的文件到后端服务器&#xff08;适合服务…

前端Mac解决localhost冲突问题!!!

1、报错提示&#xff1a; 运行项目的时候遇到了如下的问题&#xff0c;导致我项目运行不起来&#xff0c;查阅了一下资料发现是loaclhost有误 2、ping一下localhost 先打开终端&#xff0c;在终端输入命令&#xff1a;&#xff08;我输入这个命令的时候能ping通&#xff0c;当…

Android 使用`layer-list`打造精美的背景

引言 在 Android 开发中&#xff0c;layer-list 是一种强大的工具&#xff0c;可以帮助我们创建复杂的背景图形&#xff0c;&#x1f3a8; 这种方法可以大大提升应用的视觉效果。作为一名Android开发&#xff0c;理解和掌握 layer-list 的使用不仅可以让我们在 UI 设计上更加灵…

C++竞赛初阶L1-11-第五单元-for循环(25~26课)519: T454430 人口增长问题

题目内容 假设目前的世界人口有 x 亿&#xff0c;按照每年 0.1% 的增长速度&#xff0c;n 年后将有多少人&#xff1f; 输入格式 一行两个正整数 x 和 n&#xff0c;之间有一个空格。其中&#xff0c;1≤x≤100,1≤n≤100。 输出格式 一行一个数&#xff0c;表示答案。以亿…

【C++ 面试 - 基础题】每日 3 题(十八)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

对象的初始化和清理(构造和析构)

目录 一. 前言 二. 构造函数和析构函数的语法 三. 构造函数的分类和调用 四. 构造函数的调用规则 五. 深拷贝和浅拷贝&#xff08;面试常考&#xff09; 六. 初始化列表 一. 前言 任何事物都需要有一个初始化的过程&#xff0c;例如手机&#xff0c;我们在买来使用的时候手…

win10 上安装部署WSA, 在win10上运行安卓程序

windows上跑安卓程序&#xff0c; 多年前用过蓝叠bluestacks安卓模拟器 甚至还在上面跑过微信 后来听说在bluestacks上用微信&#xff0c; 可能会被封&#xff0c; 就没有后来了 最近折腾在windows上如何投屏&#xff0c;主要是将ios无线投到win10电脑上&#xff08;安卓win10直…

免费文档翻译导出怎么弄?5个文档翻译器替你解决

在日常的工作与学习中&#xff0c;我们时常会遇到需要查阅或撰写跨语言文档的情况。然而面对这些专业性强、词汇量大的文件&#xff0c;逐字逐句地手动翻译不仅耗时费力&#xff0c;还可能因理解偏差导致信息失真。 幸运的是&#xff0c;如今有不少免费文档翻译软件横空出世&a…

LDR6020双盲插便携显示器方案:重塑连接体验的新标杆

在数字化高速发展的今天&#xff0c;便携显示器已成为商务人士、游戏爱好者及远程教育学习者的得力助手。然而&#xff0c;传统便携显示器在接口兼容性与易用性方面常存在诸多不便&#xff0c;如接口方向区分困难、信号类型不匹配等问题。为了彻底解决这些痛点&#xff0c;LDR6…

基于springboot物流管理系统

TOC springboot208基于springboot物流管理系统 第1章 绪论 1.1 研究背景 互联网时代不仅仅是通过各种各样的电脑进行网络连接的时代&#xff0c;也包含了移动终端连接互联网进行复杂处理的一些事情。传统的互联网时代一般泛指就是PC端&#xff0c;也就是电脑互联网时代&…

记录一次 Redis 优化发送数据(使用管道批量传送)

一 项目背景 此前的项目中&#xff0c;鉴于客户方服务器的安全配置对 MQ 中间件有所限制&#xff0c;我们只得采用 Redis 的 list 作为简易的 MQ 来传送报文数据。然而&#xff0c;近段时间客户关闭了相关端口&#xff0c;导致大量数据积压&#xff0c;需要进行补发。在补发过程…

Unity Obfuscator 使用说明

一、Assembly - Settings 1. 核心Unity程序集&#xff08;Assembly-CSharp&#xff09; Obfuscate Assembly-CSharp: 开启 这是Unity的核心程序集&#xff0c;所有没有存储在程序集定义文件&#xff08;assembly definition file&#xff09;中的代码都会被存储在这里。大多数…