目录

前言

一、hoare版本

1.思想

2.代码

二、挖坑版本

1.思想

2.代码

三、前后指针版本

1.思想

2.代码

四、分治思想:

五、两种优化

1.三数取中

2.小区间优化

六、非递归实现快速排序

1.思想

2.实现:


前言

hello~欢迎你能够点进我的文章(*^▽^*)

这里针对快速排序,要实现其递归版本:hoare版本(最开始的)、 挖坑版本、前后指针版本,然后就是针对于其中的问题进行的一些优化。最后有非递归版本的实现。

一、hoare版本

1.思想

首先,这是一开始的快速排序版本。

同样,我们首先针对一个数进行排序,排序好后排两边的。这就利用了分治的思想,即递归版本。

然后我们针对每一轮,它首先确定最左边的为目标值,定为key,下标为keyi,定义一个储存左边的变量left(在目标值的往前一格),储存右边的变量right 我们针对升序排法,所以右边找小,左边找大(如果定义最右边的就刚好相反,右边边找小,左边找大,看个人喜好)

然后,我们需要找小的先走(right),找到小的或者遇到left停下,然后left在往右移动,找到大的停下或者遇到right停下。如果两者相遇即前述任意哪种遇到left/right就和目标值交换,否则就是right和left所在值进行交换,交换完后该哪步走即可。

*在这期间必须严格保证right>left 除非相遇,否则就会发生left和right两者交叉错过的结果。另外的,判断条件也不能只是大于或者小于,也要等于情况写上,否则会死循环的left和right走不动哦~

这就是一轮的走法,然后就可以靠分治的递归做法将我们一轮排好后的两边进行排版好就好啦~

*将这一轮排好打包一下即可。这样后面三种方法均可打包,分治只需要在一个里面就行,并且加上判断当传进来的尾大于小于等于头结束递归即可。

2.代码

//hoare版本
int PartSort1(int* a, int head, int end)
{int target = head;int left = target + 1;int right = end;while (right > left){//右边先找,找到比target处的数字小的停下   下面经过画图分析,如果不加等号的话,就会陷入死循环~while (right > left && a[right] >= a[target]){right--;}while (right > left && a[left] <= a[target]){left++;}if (right > left)  // 最后一次不交换Swap(&a[right], &a[left]);}//判断targe位子是否是正确位置if (arr[right] < arr[targe])  // 满足条件就不是正确位置{Swap(&arr[right], &arr[targe]);targe = right;  // 如果是进入循环调整了的,需要进行修改,否则不可修改}return target;
}

注意: 我们的返回值是目标值排好序之后的下标。如果出现问题,将会导致后续的分治出现问题从而排序错误。上面判断targe那里必须将targe带入更新,不能放在if外,否则本身targe的位置是正确的但是却修改了另外的位置导致错误。

后续版本在分治那里哦~

二、挖坑版本

1.思想

可能是想让hoare版本看起来更加好懂一些,在基本思想不变的情况下,稍微改进了一下将两者交换变成了填坑。left right 和key keyi均不变,即左边移动部分 右边移动部分 目标值 目标下标。

只不过在一开始,保存key值 然后将此时的keyi位置的数组值视为第一个坑,然后右边找小的开始找小,找到小停下或者遇到left停下,左边找也是如此。如果两者相遇,就讲key值填入此时位置。如果没有,一开始的话就是右边找小的位置将其值填入keyi的位置,然后自己形成坑由左边找到大的来填,之后就是左右互填即可,直到两者相遇。但是需要注意如果原本有序的话那么就会存在问题,所以要加上判断条件是否在原本位置上才交换,否则就退出循环:

分治的话没有什么可以说的。大致思路如上。

2.代码

//挖坑版本
int PartSort2(int* a, int head, int end)
{int keyi = head;int left = head + 1;int right = end;int key = a[keyi];while (right > left){//首先右边找到比key小的值,然后把坑填上产生新坑while (right > left && a[right] >= key)right--;if (key > a[right])  // 判断是否原本是正确位置{a[keyi] = a[right];keyi = right;}else  // 是正确位置就不循环了,直接退出break;//左边找到大的然后填坑while (right > left && a[left] <= key)left++;a[keyi] = a[left];keyi = left;}// 当keyi != right存在两种情况:1.进入循环但是有序,break; 2.没有进入循环存在两个数// 如果不区分2的这种情况,那么将不会进行排序,如果顺序不对会出现错误,并且最后的值也会出错if (keyi != right && arr[keyi] > arr[right]){Swap(&arr[keyi], &arr[right]);keyi = right;}a[keyi] = key;return keyi;
}

三、前后指针版本

1.思想

首先,此方法在核心操作步骤上已经和上面两种完全不同。

这里采用的是双指针前后移动,前面的指针从第二个开始,第一个为目标值并且也是后面指针的位置,前指针一直往后移动,遇到比目标值低的时候,后指针往后移动一步,(此时一定是比目标值大的,因为前指针并没有停止)然后此时处于两位置的值进行交换。

直到前指针移出数组的边界后,然后此时后指针的位置处于原位置(出生的位置)或者交换小的后的位置,总之就是此时后指针指向的位置要么就是目标值要么就是比其小的值,所以目标值与其交换,一轮就执行好了,剩下的用分治去解决即可。

*注意,可能会遇到前指针还未移动,然后后指针就移动,此时两者重复,可以不用进行交换,写代码时可以加入减少时间复杂度。

2.代码

//前后指针版本
int PartSort3(int* a, int head, int end)
{int keyi = head;int prev = head;int cur = head + 1;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[cur], &a[prev]);cur++;}Swap(&a[keyi], &a[prev]);keyi = prev;return keyi;
}

四、分治思想:

这里就是执行每一轮,然后最终完成程序的过程:首先进行第一轮,第一轮排完后,在目标值的位置的左即begin 和目标值下标减一,这一段和目标值加一和end这两段即可。之后的递归同意是这样的原理,直到递归到没有元素或者只要一个的时候就停止了。

代码:

//快速排序:
void QuickSort(int* a, int head, int end)
{//结束标志if (head >= end)return;assert(a);int target = PartSort(a, head, end);//进入递归,分而治之的思想//左边QuickSort(a, head, target - 1);//右边QuickSort(a, target + 1, end);}

五、两种优化

经过上面的几种方法介绍,相信你已经对快排有一定的了解了。那么现在提出一种场景,来看看我们快排的表现。(注 上述三种方法实现的快排均一样的效果)

1.三数取中

比如排   1 2 3 4 5 6 7....n  和   3 2 5 1 4 6 7....

排第一个时快排排了 大概是N*N 而第二种是正常的时间复杂度,这是为什么呢?原因就是根据快速排序的特性,快速排序的快慢取决于目标值的位置 一开始要是就排到中间,那么就可以演变为二分法,但是像这种越接近于有序,那么每次目标值都是最边缘的位置,然后分治里面另一端就会没有用处,永远处理一边,效率自然低下。

所以,这就说明了目标值取得好坏。为了避免这种情况的发生,我们可以针对选目标值做上一点手脚,有随机取值,三数取中等.....但是比较好用的是三数取中,随机值毕竟是有一定的随机性的,说不清楚。下面介绍一下三数取中:

传进原始数组,取头,尾,中三个值,然后三个值分别比较,选出中间值,返回。代码很简答,如下:


//三数取中优化key
int TakeMiddle(int* a, int head, int end)
{int middle = (head + end) / 2;if (a[head] < a[middle]){if (a[end] > a[middle])return middle;else if (a[end] > a[head])return end;elsereturn head;}else{if (a[middle] > a[end])  //head  > middlereturn middle;else if (a[end] > a[head])return head;elsereturn end;}
}

之后操作就是选取目标值,在进行循环之前不变(三种方法均是),然后得出下标后,与其交换即可,就可以不用动下面写好的代码了:

 int keyi = TakeMiddle(a, head, end);Swap(&a[target], &a[keyi]);

2.小区间优化

当递归到要排的数比较少的时候,这个时候在进行递归会发现迭代的层数有点多,此时相较于普通的方法比如(插入排序)效率会有那么一点低下,所以,针对分治里面每次传入的head和end,在一定区间内进行划分即可:

 //小区间使用插入排序缓解最后几层递归过多的问题if (head - end > 10){//进入递归,分而治之的思想//左边QuickSort(a, head, target - 1);//右边QuickSort(a, target + 1, end);}else{//微量数据利用插入排序即可InsertSort(a, end - head + 1);}

六、非递归实现快速排序

1.思想

实际上,非递归实现排序理论上和递归排序没有任何区别,只是在分治或者是在储存区间,在分区间进行每一轮的操作时候发生了变化。

首先,这个问题的提出就是因为递归非常容易造成栈溢出,栈的内存很小。所以,这个时候我们能不能不使用递归来进行区间的划分呢? 其实,我们发现递归实际上也是相当于每次传进去的区间不同,那么我们使用非递归的时候,就是每次循环进去不同的区间就好了。

那么如何实现每次循环的区间不同呢?

这就需要一个储存的机制。储存?回顾之前的基础数据结构知识,我们只带栈,队列,堆等数据结构。那么栈和队列用到这个里面是不是刚刚好呢?

对的,在实现栈和队列的时候,使用的是堆内存,堆内存很大,不用担心溢出问题。然后利用栈的先进后出,队列的先进先出原则,每次循环就把区间存进去,一轮弄完后就进行存储,这样就可以解决我们的问题。

具体实现的时候,首先将头和尾插入栈或者队列,栈的话先插尾,然后在头,队列正好相反,然后进入循环,先将本次的区间元素弹出,进入一轮循环,循环完后,在目标值的左右两区间均按照上述步骤即可。

注意在循环里插入的时候判断入栈或者入列条件,即区间的左右必须右大于左,否则不进。条件就是栈、队列空间的元素有无进行判断就好了。

2.实现:

//快速排序的非递归版本(递归的问题,深度太深,容易栈溢出)
//之前是利用递归的方法使其控制对应的区间,那么现在利用栈来储存要改变的区间
void QuickSortNonR(int* a, int begin, int end)
{ST st;StackInit(&st);StackPush(&st, end);StackPush(&st, begin);//当栈内元素为空时停止循环即可while (!StackEmpty(&st)){int left = StackTop(&st);StackPop(&st);int right = StackTop(&st);StackPop(&st);int keyi = PartSort(a, left, right);//PartSort用上述三种方法任意一种均可if (left < keyi - 1){StackPush(&st, keyi - 1);StackPush(&st, left);}if (keyi + 1 < right){StackPush(&st, right);StackPush(&st, keyi + 1);}}//StackDestroy(&st);
}

上面是用栈来进行实验的,也可以利用队列哦~

【c语言】快速排序的三种实现以及优化细节相关推荐

  1. C语言程序设计有哪几种结构,第章c语言程序设计的三种基本结构.ppt

    第章c语言程序设计的三种基本结构 北京科技大学 计算机系 第2章 C语言程序设计 的三种基本结构 2.1 顺序结构程序设计 2.1.1 C语句 2.1.2 字符数据的输入与输出 2.1.3 格式输入与 ...

  2. Python语言学习:三种随机函数random.seed()、numpy.random.seed()、set_random_seed()及random_normal的简介、使用方法(固定种子)详细攻略

    Python语言学习:三种随机函数random.seed().numpy.random.seed().set_random_seed()及random_normal的简介.使用方法(固定种子)之详细攻 ...

  3. Scala 语言输出的三种方式

    Scala 语言输出的三种方式 字符串通过+号连接(类似 java). printf 用法 (类似 C 语言)字符串通过 % 传值. 字符串通过$引用(类似 PHP).

  4. python选择语句是什么语句_Python语言中的三种选择语句

    本文将详细介绍Python语言中的三种选择语句:if语句,if/else语句和if/elif/else语句.对于每种语句,我们都提供了相应的流程图.与此同时,我们给出许多简单的示例程序,以帮助读者加深 ...

  5. 快速排序的三种实现以及应用场景

    好了,废话不多说,直接上干货. 1.快速排序的概念: 快速排序(Quicksort)是对冒泡排序的一种改进. 快速排序由C. A. R. Horno在1962年提出.它的基本思想是:通过一趟排序将要排 ...

  6. c 语言程序的三种基本结构,C 语言程序的三种基本结构是____ A、顺序结构,选择结构,循环结构 B、递归结构,循环结构,转移结构...

    C 语言程序的三种基本结构是____ A.顺序结构,选择结构,循环结构 B.递归结构,循环结构,转移结构 更多相关问题 [填空题]移动电商,全称 ,是以 为载体的电商模式. [单选题]有关离子选择性电 ...

  7. c语言编程非线性方程求解,c语言计算机编程三种方法求解非线性方程

    c语言计算机编程三种方法求解非线性方程 本 科 专 业 学 年 论 文题 目:非线性方程求解比较姓 名: 何 娟 专 业: 计算机科学技术系 班 级: 08 级本科(2)班 指 导 老 师: 刘 晓 ...

  8. c语言编程非线性方程求解,c语言计算机编程三种方法求解非线性方程.doc

    c语言计算机编程三种方法求解非线性方程.doc 本 科 专 业 学 年 论 文题 目非线性方程求解比较姓 名 何 娟 专 业 计算机科学技术系 班 级 08 级本科(2)班 指 导 老 师 刘 晓 娜 ...

  9. c语言二叉树的遍历菜单系统,C语言二叉树的三种遍历方式的实现及原理

    C语言二叉树的三种遍历方式的实现及原理 发布时间:2020-10-03 19:43:57 来源:脚本之家 阅读:63 作者:看雪. 二叉树遍历分为三种:前序.中序.后序,其中序遍历最为重要.为啥叫这个 ...

最新文章

  1. 团队作业6—《Spring_Four》团队项目系统设计改进与详细设计
  2. 下面不属于python第三方库的安装方法的是-Python第三方库安装和卸载
  3. springcloud系列四 搭建服务模块重点讲解
  4. 前端开发工程师做些什么?
  5. agv matlab应用,简单介绍一下agv调度控制系统常见的软件应用
  6. linux共享内存 pmu,如何使用gator/streamline 收集PMU perf event计数
  7. 修改ftp服务器用户,通过批处理修改FTP账号和密码
  8. vscode 使用技巧(持续更新)
  9. Netty源码分析第3章(客户端接入流程)----第3节: NioSocketChannel的创建
  10. 服务器操作系统使用相关要求,服务器操作系统使用相关要求
  11. 山西最新五大姓氏排名发布,排名第一的是王,第二的竟是……
  12. word中公式和文字不在一条水平线上
  13. android流量卡信息,Android 双卡获取当前使用流量在线卡的信息
  14. 【背包DP】【2018.9.20普及组模拟】T3(WOJ 3975)保护羊村
  15. 强大的Winform Chart图表控件使用说明
  16. SBOM(Software Bill of Materials,软件物料清单)
  17. 罗斯柴尔德家族:“大道无形”世界首富
  18. [转载]快速记忆日语单词,一年考过1级!
  19. php析构函数有什么用,php析构函数的作用
  20. 我在simulink与adams联合仿真中遇到关于Error in Adams(server) simulation startup问题及解决办法。

热门文章

  1. labview呀,可是真有意思
  2. Excel中如何取消自动筛选菜单中日期分组状态
  3. 计算机英语在线学习,英语单词记忆法超强记忆_免费背单词软件电脑版
  4. 验证远程计算机的连接遇到,win7系统进行远程桌面连接出现身份验证错误的图文步骤...
  5. MYSQL ‘单引号转义 \反斜杠转义
  6. JAVA学到什么程度可以(实习)找工作?
  7. NeurIPS'18 | 种群进化随机梯度下降深度神经网络优化算法框架
  8. 图像的正交变换---沃尔什——哈达马变换
  9. 07- 梯度下降优化(Lasso/Ridge/ElasticNet) (数据处理+算法)
  10. 针对“天眼查”选字验证调用超级鹰平台解决方案