一.快速排序的基本思想

关于快速排序,它的基本思想就是选取一个基准,一趟排序确定两个区间,一个区间全部比基准值小,另一个区间全部比基准值大,接着再选取一个基准值来进行排序,以此类推,最后得到一个有序的数列。

二.快速排序的步骤

  • 1.选取基准值,通过不同的方式挑选出基准值。
  • 2.用分治的思想进行分割,通过该基准值在序列中的位置,将序列分成两个区间,在准值左边的区间里的数都比基准值小(默认以升序排序),在基准值右边的区间里的数都比基准值大。
  • 3.递归调用快速排序的函数对两个区间再进行上两步操作,直到调用的区间为空或是只有一个数。

三.关于选取基准值的方式

1.固定位置选取基准值
基本思想:选取第一个或最后一个元素作为基准值。

如上是以第一个数作为选取基准的方式的第一趟排序的结果,接着就是对分好的两个区间再进行递归的快排。

但是,这种选取基准值的方法在整个数列已经趋于有序的情况下,效率很低。比如有序序列(0,1,2,3,4,5,6,7,8,9),当我们选取0为基准值的时候,需要将后面的元素每个都交换一遍,效率很低。所以这种以固定位置选取基准值的方式,只适用于该序列本身并不是趋于有序的情况下,比如一串随机数列,此时的效率还能够差强人意。

为了避免这种已经有序的情况,于是有了下面两种选取基准值的方式

下面是关于选取固定基准值快排的代码

int SelectPivot(int* a, int left, int right)//选取基准值函数
{return a[left];
}void QuickSort(int* a, int left, int right)
{assert(a);int i, j;int pivot = SelectPivot(a, left, right);//确定基准值 if (left < right){i = left + 1;//以第一个数left作为基准数,从left+1开始作比较j = right;while (i < j){if (a[i] > pivot)//如果比较的数比基准数大{swap(a[i], a[j]);//把该比较数放到数组尾部,并让j--,比较过的数就不再比较了j--;}else{i++;//如果比较的数比基准数小,则让i++,让下一个比较数进行比较}}//跳出while循环后,i==j//此时数组被分成两个部分,a[left+1]-a[i-1]都是小于a[left],a[i+1]-a[right]都是大于a[left]//将a[i]与a[left]比较,确定a[i]的位置在哪//再对两个分割好的部分进行排序,以此类推,直到i==j不满足条件if (a[i] >= a[left]) //这里必须要用>=,否则相同时会出现错误{i--;}swap(a[i], a[left]);QuickSort(a, left, i);QuickSort(a, j, right);}
}//测试函数
int main()
{int a[] = { 2,5,4,9,3,6,8,7,1,0};const size_t n = sizeof(a) / sizeof(a[0]);QuickSort(a, 0, n - 1);Print(a, n);system("pause");return 0;
}

2.随机选取基准
基本思想:选取待排序列中任意一个数作为基准值。
因为快排函数部分的代码是一样的,只是选取基准值部分的函数不相同,下面只附上选取基准值函数的代码

int SelectPivot(int* a, int left, int right)//选取基准值函数
{srand((unsigned)time(NULL));int pivotPos;if (left < right)//这里需要保证传进来的left必须小于left{pivotPos = rand() % (right - left) + left;}else{pivotPos = left;//在递归调用里走到这一步,肯定是left=right,直接让pivotPos=left}swap(a[pivotPos], a[left]);return a[left];
}

引入随机化快速排序的作用,就是当该序列趋于有序时,能够让效率提高,大量的测试结果证明,该方法确实能够提高效率。但在整个序列数全部相等的时候,随机快排的效率依然很低,它的时间复杂度为O(N^2),但出现这种最坏情况的概率非常的低,所以它还是一种效率比较好的方法,一般情况下都能够达到O(N*lgN)。

3.三数取中法选取基准值
基本思想:取第一个数,最后一个数,第(N/2)个数即中间数,三个数中数值中间的那个数作为基准值。举个例子,对于int a[] = { 2,5,4,9,3,6,8,7,1,0};,‘2’、‘3’、‘0’,分别是第一个数,第(N/2)个是数以及最后一个数,三个数中3最大,0最小,2在中间,所以取2为基准值。

下面也是附上三数取中法的代码

int SelectPivot(int* a, int left, int right)//选取基准值函数
{int mid;if (left < right){mid = (right - left) / 2;}else{return a[left];//在递归调用里走到这一步,肯定是left=right,直接让pivotPos=left}if (a[mid] > a[right]){swap(a[mid], a[right]);}if (a[left] > a[right]){swap(a[left], a[right]);}if (a[mid] > a[left]){swap(a[mid], a[left]);}//上面三步完成之后,a[left]就是三个数中最小的那个数return a[left];
}

采用三数取中法很好的解决了很多特殊的问题,但对于很多重复的序列,效果依然不好。于是在这三种选取基准值的方法下,另外地还有三种优化方法。

四.快速排序的优化

优化一:当待排序序列的长度分割到一定大小后,使用插入排序。
优化原因:对于待排序的序列长度很小或是基本趋于有序时,快排的效率还是插排好。

自定义截止范围:序列长度N=10。当待排序的序列长度被分割到10时,采用快排而不是插排。

if (n <= 10)//当整个序列的大小n<=10时,采用插排
{InsertSort(a, n);
}

优化二:在一次排序后,可以将与基准值相等的数放在一起,在下次分割时可以不考虑这些数。

因为这一次改动的代码比较多,所以再继续把所有的代码全部拿出来看一下

int SelectPivot(int* a, int left, int right)//选取基准值函数
{int mid;if (left < right){mid = (right - left) / 2;}else{return a[left];//在递归调用里走到这一步,肯定是left=right,直接让pivotPos=left}if (a[mid] > a[right]){swap(a[mid], a[right]);}if (a[left] > a[right]){swap(a[left], a[right]);}if (a[mid] > a[left]){swap(a[mid], a[left]);}//上面三步完成之后,a[left]就是三个数中最小的那个数return a[left];
}void QuickSort(int* a, int left, int right)
{assert(a);if (n <= 10)//当整个序列的大小n<=10时,采用插排{InsertSort(a, n);return;}int i, j;int pivot = SelectPivot(a, left, right);//确定基准值 if (left < right){i = left + 1;//以第一个数left作为基准数,从left+1开始作比较j = right;while (i < j){if (a[i] == pivot)//处理与基准值相等的数,都放到数组末尾{swap(a[i], a[j]);--j;}else if (a[i] > pivot)//如果比较的数比基准数大{while (1){if (a[j] == pivot)//如果要换的数值等于基准值,让j--,与前一个交换{--j;}else{break;}}swap(a[i], a[j]);//把该比较数放到数组尾部,并让j--,比较过的数就不再比较了--j;}else{++i;//如果比较的数比基准数小,则让i++,让下一个比较数进行比较}}//跳出while循环后,i==j//此时数组被分成两个部分,a[left+1]-a[i-1]都是小于a[left],a[i+1]-a[right]都是大于a[left]//将a[i]与a[left]比较,确定a[i]的位置在哪//再对两个分割好的部分进行排序,以此类推,直到i==j不满足条件if (a[i] >= a[left]) //这里必须要用>=,否则相同时会出现错误{i--;}swap(a[i], a[left]);int tmp = right;//用tmp表示从后往前第一个不是基准值的数while (tmp > i){if (a[tmp] == pivot){--tmp;}else//else表示没有与基准值重复的值{QuickSort(a, left, i - 1);QuickSort(a, i + 1, right);return;}}int pos = tmp;//因为后面要用到tmp,所以运算的话用一个pos来代替tmp进行运算int r = right;//因为后面要保证right还是先前的右边界,所以运算的话用另外一个变量来表示int count = 0;while (pos > i&&r > tmp){swap(a[pos], a[r]);--r;--pos;++count;//换了一次让count++}QuickSort(a, left, i-1);//对左区间快排,i-1是左区间的最后一个数QuickSort(a, right-count+1, right);//对右区间快排,right-count+1是右区间的第一个数}
}

这种聚集与基准值相等的值的优化方法,在解决数据冗余的情况下非常有用,提高的效率也是非常多。

优化三:优化递归操作
快排函数在函数尾部有两次递归操作,我们可以对其使用尾递归优化

优点:如果待排序的序列划分极端不平衡,递归的深度将趋近于n,而栈的大小是很有限的,每次递归调用都会耗费一定的栈空间,函数的参数越多,每次递归耗费的空间也越多。优化后,可以缩减堆栈深度,由原来的O(n)缩减为O(logn),将会提高性能。

只是在尾部的递归调用的时候做了以下改变

while (left < right)
{QuickSort(a, left, i - 1);//对左区间快排,i-1是左区间的最后一个数left = right - count + 1;
}

其实这种优化编译器会自己优化,相比不使用优化的方法,时间几乎没有减少。

所以到这里,总结一下,对于快速排序(一组随机数组),效率最快的优化方案应该是三数取中法+插排+聚集相等元素,对于尾递归可以有也可以没有,对于效率的变化改变不大。

快速排序的三种方式以及快排的优化相关推荐

  1. APS计划排程系统之下的MRPII、JIT、TOC三种方式对比分析

    1.生产物流计划的制订方式对比 ①MRPII采用的是集中式的物料计划方式,建立好产品加工程序,在电脑中确定好准确的订单需求和库存量,对各个生产单元传送生产指令: ②JIT利用的是看板管理控制方式,按照 ...

  2. 快速排序、快排的优化 及Java实现

    一.快速排序的思想 选取一个比较的基准,将待排序数据分为独立的两个部分,左侧都是小于或等于基准,右侧都是大于或等于基准,然后分别对左侧部分和右侧部分重复前面的过程,也就是左侧部分又选择一个基准,又分为 ...

  3. MySQL删除表的三种方式

    文章目录 drop table truncate (table) delete from 三种方式的区别 用法总结 drop table drop 是直接删除表信息,速度最快,但是无法找回数据 例如删 ...

  4. STM32芯片烧录的三种方式介绍,串口、STM32 ST-LINK Utility以及STM32CubeProgrammer

    STM32芯片烧录的三种方式介绍,串口.STM32 ST-LINK Utility以及STM32CubeProgrammer 1 概述 1.1资源概述 1.2 STM32串口烧录方式 2.KEIL软件 ...

  5. 盛大游戏技术总监徐峥:Unity引擎使用的三种方式

    在5月13日Unite 2017 案例分享专场上,盛大游戏技术总监徐峥分享了使用Unity引擎的三种方式,以下为详细内容: 大家好,我先简单介绍一下我自己,我是盛大游戏的技术总监徐峥.我今天想分享的主 ...

  6. mysql表删除回滚_MySQL删除表的三种方式(小结)

    drop table drop 是直接删除表信息,速度最快,但是无法找回数据 例如删除 user 表: drop table user; truncate (table) truncate 是删除表数 ...

  7. discard connection丢失数据_python kafka 生产者发送数据的三种方式

    python kafka 生产者发送数据的三种方式 发送方式 同步发送 发送数据耗时最长 有发送数据的状态,不会丢失数据,数据可靠性高 以同步的方式发送消息时,一条一条的发送,对每条消息返回的结果判断 ...

  8. js学习-DOM之动态创建元素的三种方式、插入元素、onkeydown与onkeyup两个事件整理...

    动态创建元素的三种方式: 第一种: Document.write(); <body> <input type="button" id="btn" ...

  9. Android 使用OpenCV的三种方式(Android Studio)

    from: http://blog.csdn.net/sbsujjbcy/article/details/49520791 其实最早接触OpenCV是很久很久之前的事了,大概在2013年的5,6月份, ...

最新文章

  1. js阿拉伯数字转成汉字
  2. WINCE6.0远程桌面显示修改
  3. 3YAdmin-专注通用权限控制与表单的后台管理系统模板
  4. 图论--关于最长路的探讨
  5. 计算机网络之应用层:3、文件传输协议FTP、简单文件传输协议TFTP
  6. 6-4-2:STL之list——list的模拟实现
  7. Bootstrap 分页导航的对齐方式
  8. quartus仿真系列2:74193功能
  9. 二手房六大产权问题最关键
  10. AI,大数据,复杂系统最精25本大书单(建议收藏)
  11. android+cast+sdk,如何使用Android发现Chromecast设备?
  12. 1.Spring注解01、组件注册-@Configuration@Bean给容器中注册组件
  13. 清华领军计划计算机试题,清华大学2017年自主招生领军计划笔试真题
  14. ios 获取是否静音模式_iOS 判断设备是否静音
  15. 国际高中成绩差但是想读名校怎么办(文末附自救指南)
  16. 西数宣布将绿盘并入蓝盘
  17. php如何打开excel文件,如何使用php获取excel文件数据
  18. 数据结构——树|N叉树之孩子双亲表示法——顺序存储结构+链表
  19. 先验概率、后验概率、似然概率概念
  20. 第七章 中子----中子源、应用、能量分类、探测四个基本过程

热门文章

  1. rtsp 和 rtmp 推流(一)
  2. Python爬虫JD杜蕾斯源码
  3. 更改java和javac的默认输出语言为英文
  4. NameNode HA配置详解
  5. 第一次社招笔试题回顾(全基础题)
  6. 最全-python教程示例大全 同步学习
  7. 豆瓣读书top250数据爬取与可视化
  8. BZOJ 2565 最长回文串
  9. python案例——体脂率项目
  10. 黑客消失在互联网时代