数据结构和算法13 之快速排序

简介:

 上一节我们学习了一个高级排序算法:希尔排序,这一节我们将讨论另一个高级排序算法:快速排序。

        快速排序算法是最流行的排序算法,因为有充足的理由,在大多数情况下,快速排序都是最快的,执行时间为O(NlogN)级(这只是对内部排序或者说随机存储器内的排序而言,对于在磁盘文件中的数据进行的排序,其他的排序算法可能更好)。快速排序本质上通过一个数组划分为两个子数组,然后递归地调用自身为每一个子数组进行快速排序来实现的,即算法分为三步:

        1. 把数组或者子数组划分为左边(较小的关键字)的一组和右边(较大的关键字)的一组;

        2. 调用自身对左边的一组进行排序;

        3. 调用自身对右边的一组进行排序。

        经过一次划分之后,所有在左边子数组的数据项都小于在右边子数组的数据项,只要对左边子数组和右边子数组分别进行排序,整个数组就是有序的了。下面试一次划分后的示意图:


        快速排序需要划分数组,这就用到了划分算法。划分算法是由两个指针(这里是指数组数据项,非C++中所说的指针)开始工作,两个指针分别指向数组的两头,左边的指针leftPtr向右移动,右边的指针rightPtr向左移动。当leftPtr遇到比枢纽(待比较的数据项,比其小的在其左边,比其大的在其右边,下面均称之为“枢纽”)小的数据项时继续右移,当遇到比枢纽大的数据项时就停下来;类似的rightPtr想反。两边都停下后,leftPtr和rightPtr都指在数组的错误一方的位置的数据项,交换这两个数据项。交换后继续移动这两个指针。

        基于上面的划分算法,可以将数据快速排好序,下面是快速排序的实现代码:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public void quickSort(int[] a) {  
  2.     recQuickSort(a,0, a.length-1);  
  3. }  
  4.   
  5. public void recQuickSort(int[] a, int left, int right) {  
  6.     if(right-left <= 0) {  
  7.         return;  
  8.     }  
  9.     else {  
  10.         int pivot = a[right]; //保存最右边的值,以这个值作为划分点  
  11.         int partition = partitionIt(a,left, right, pivot);//将数组划分两部分,并将划分点的值放在正确位置,并返回该位置  
  12.         recQuickSort(a, left, partition-1);//调用自身对左边进行排序  
  13.         recQuickSort(a, partition+1, right);//调用自身对右边进行排序  
  14.     }  
  15. }  
  16.   
  17. public int partitionIt(int[] a, int left, int right, int pivot) {  
  18.     int leftPtr = left - 1;  
  19.     int rightPtr = right;  
  20.     while(true) {  
  21.         while(a[++leftPtr] < pivot){} //往上找  
  22.         while(rightPtr > 0 && a[--rightPtr] > pivot){} //往下找  
  23.         if(leftPtr >= rightPtr) break;  
  24.         else swap(leftPtr, rightPtr);  
  25.     }  
  26.     swap(leftPtr, right);//将划分放在正确的位置  
  27.     return leftPtr;//返回划分点,用于再次小范围划分  
  28. }  

        算法分析:快速排序是一种不稳定的排序方法,其平均时间复杂度为O(NlogN),最坏的情况下退化成插入排序了,为O(N2)。

        快速排序是不稳定的,当a=b>pivot且a在b前面的时候,由于从后面开始遍历,故b会先于a被替换到pivot的前面,这样,b就变成了在a的前面,也就是说,ab位置对调,故该排序算法不稳定

        空间复杂度平均为O(logN),空间复杂度主要是由于递归造成的。

        在理想状态下应该选择被排序的数据项的中值数据项作为枢纽(上面程序中是用数组的最后一项作为枢纽的)。也就是说,应该有一半的数据项大于枢纽,一般的数据项小于枢纽。这会使数组被划分成两个大小相等的子数组。对于快速排序算法来说,拥有两个大小相等的子数组是最优的情况,最坏的情况就是一个子数组只有一个数据项,另一个子数组含有N-1个数据项。所以上面的算法中如果最右边的数据是最小的或者最大的,那就可能导致最坏的情况出现。为了解决这个问题,我们可以改进上面的算法,使用“三数据项取中”划分:找到数组里的第一个、最后一个以及中间位置数据项的值,将三个中处在中间大小的数据项作为枢纽,且将三个数排好序。下面是改进的快速排序:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public void quickSort2(int[] a) {  
  2.     recQuickSort2(a, 0, a.length-1);  
  3. }  
  4.   
  5. public void recQuickSort2(int[] a, int left, int right) {  
  6.     int size = right - left + 1;  
  7.     if(size <= 3) {  
  8.          manualSort(a, left, right);//数据项小于等于3个,直接排  
  9.     }  
  10.     else {  
  11.         long median = medianOf3(a, left, right);//取左边、右边和中间三个数中中等大小的数作为枢纽  
  12.         int partition = partitionIt2(a, left, right, median);//将枢纽放到正确的位置  
  13.         recQuickSort2(a, left, partition-1);//调用自身对左边进行排序  
  14.         recQuickSort2(a, partition+1, right);//调用自身对右边进行排序  
  15.     }  
  16. }  
  17. private void manualSort(int[] a, int left, int right) {  
  18.     int size = right - left + 1;  
  19.     if(size <= 1) {  
  20.         return//1个不用排  
  21.     }  
  22.     if(size == 2) {  
  23.         if(a[left] > a[right]) { //2个很好排  
  24.             swap(left, right);  
  25.         }  
  26.         return;  
  27.     }  
  28.     else { //3个比较下就可以排好了  
  29.         int center = right - 1;  
  30.         if(a[left] > a[center]) {  
  31.             swap(left, center);  
  32.         }  
  33.         if(a[left] > a[right]) {  
  34.             swap(left, right);  
  35.         }  
  36.         if(a[center] > a[right]) {  
  37.             swap(center, right);  
  38.         }  
  39.     }  
  40. }  
  41.   
  42. private long medianOf3(int[] a, int left, int right) {  
  43.     int center = (left + right) / 2;  
  44.     if(a[left] > a[center]) {  
  45.         swap(left, center);  
  46.     }  
  47.     if(a[left] > a[right]) {  
  48.         swap(left, right);  
  49.     }  
  50.     if(a[center] > a[right]) {  
  51.         swap(center, right);  
  52.     }//已经将三个排好序  
  53.     swap(center, right - 1); //然后将枢纽保存在right-1位置  
  54.     return a[right-1];//这保证了首位置比枢纽值小,最末尾位置比枢纽值大  
  55. }  
  56.   
  57. public int partitionIt2(int[] a, int left, int right, long pivot) {  
  58.      int leftPtr = left;  
  59.     int rightPtr = right - 1;  
  60.     while(true) {  
  61.         while(a[++leftPtr] < pivot){} //往上找  
  62.         while(a[--rightPtr] > pivot){} //往下找  
  63.         if(leftPtr >= rightPtr) break;  
  64.         else swap(leftPtr, rightPtr);  
  65.     }  
  66.     swap(leftPtr, right-1);//把right-1处存放的枢纽放到正确位置  
  67.     return leftPtr;//返回划分点,用于再次小范围划分  
  68. }  

        算法分析:三数据项取中法除了对选择枢纽更为有效外,还有另一个好处:可以对第二个内部while循环中取消rightPtr>left(即rightPtr>0)的测试,以略微提高算法的执行速度。因为在选择的过程中使用三数据项取中法不仅选择了枢纽,而且对这三个数据项进行了排序,所以就可以保证数组最左端的数据项小于或者等于枢纽,最右端的数据项大于或者等于枢纽,所以就可以省去rightPtr<0的检测了,leftPtr和rightPtr不会分别越过数组的最右端或者最左端。

        三数据项取中还有一个小的好处是,对左端、中间以及右端的数据项排序后,划分过程就不需要再考虑这三个数据项了,所以上面的程序中左端真正是从left+1处开始的,右端真正是从right-2处开始的(因为right处存的是比枢纽大的数据项,right-1处存的是枢纽)。

        如果使用三数据项取中划分方法,则必须要遵循快速排序算法不能执行三个或者少于三个项的划分规则。在这种情况下,数字3被称为切割点(cutoff)。在上面的例子中,我们用一段代码手动对两个或三个数据项的子数组来排序的,但是这不是最好的方法。

        处理小划分的另一个选择是使用插入排序。当使用插入排序的时候,不以限制3为切割点,可以把界限定位10、20或者其他任何数,试验不同切割点的值找到最好的执行效率是很有意义的。最好的选择值取决于计算机、操作系统、编译器等。这里使用9作为切割点。也就是说,当待比较的数小于等于9时,我们使用插入排序,大于9时我们使用快速排序法。继续修改上面的程序:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public void quickSort3(int[] a) {  
  2.     recQuickSort3(a, 0, a.length-1);  
  3. }  
  4.   
  5. public void recQuickSort3(int[] a, int left, int right) {  
  6.     int size = right - left + 1;  
  7.     if(size < 10) {  
  8.         insertionSort(a, left, right);//小于10项使用插入排序  
  9.     }  
  10.     else { //大于10项使用快速排序  
  11.         long median = medianOf3(a, left, right);  
  12.         int partition = partitionIt2(a, left, right, median);//上面的partionIt2方法  
  13.         recQuickSort3(a, left, partition-1);  
  14.         recQuickSort3(a, partition+1, right);  
  15.     }  
  16. }  
  17. private void insertionSort(int[] a, int left, int right) {  
  18.     for(int i = left + 1; i <= right; i++) {  
  19.         for(int j = i; (j > left) && (a[j] < a[j-1]); j--) {  
  20.             swap(j, j-1);  
  21.         }  
  22.     }  
  23. }  
        经过两次改进后,这样快速排序便结合了插入排序,三数据项取中法等方法,算是比较好的一个算法了。

        快速排序算法就介绍到这,如有错误之处,欢迎留言指正~


转载:http://blog.csdn.net/eson_15/article/details/51169679

目录
相关文章
|
2月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
49 1
|
2月前
|
机器学习/深度学习 算法 数据挖掘
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构。本文介绍了K-means算法的基本原理,包括初始化、数据点分配与簇中心更新等步骤,以及如何在Python中实现该算法,最后讨论了其优缺点及应用场景。
117 4
|
2月前
|
存储 搜索推荐 Python
用 Python 实现快速排序算法。
快速排序的平均时间复杂度为$O(nlogn)$,空间复杂度为$O(logn)$。它在大多数情况下表现良好,但在某些特殊情况下可能会退化为最坏情况,时间复杂度为$O(n^2)$。你可以根据实际需求对代码进行调整和修改,或者尝试使用其他优化策略来提高快速排序的性能
128 61
|
11天前
|
存储 运维 监控
探索局域网电脑监控软件:Python算法与数据结构的巧妙结合
在数字化时代,局域网电脑监控软件成为企业管理和IT运维的重要工具,确保数据安全和网络稳定。本文探讨其背后的关键技术——Python中的算法与数据结构,如字典用于高效存储设备信息,以及数据收集、异常检测和聚合算法提升监控效率。通过Python代码示例,展示了如何实现基本监控功能,帮助读者理解其工作原理并激发技术兴趣。
48 20
|
2月前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
2月前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
2月前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
110 23
|
2月前
|
算法
数据结构之蜜蜂算法
蜜蜂算法是一种受蜜蜂觅食行为启发的优化算法,通过模拟蜜蜂的群体智能来解决优化问题。本文介绍了蜜蜂算法的基本原理、数据结构设计、核心代码实现及算法优缺点。算法通过迭代更新蜜蜂位置,逐步优化适应度,最终找到问题的最优解。代码实现了单链表结构,用于管理蜜蜂节点,并通过适应度计算、节点移动等操作实现算法的核心功能。蜜蜂算法具有全局寻优能力强、参数设置简单等优点,但也存在对初始化参数敏感、计算复杂度高等缺点。
62 20
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
65 1
|
2月前
|
机器学习/深度学习 算法 C++
数据结构之鲸鱼算法
鲸鱼算法(Whale Optimization Algorithm,WOA)是由伊朗研究员Seyedali Mirjalili于2016年提出的一种基于群体智能的全局优化算法,灵感源自鲸鱼捕食时的群体协作行为。该算法通过模拟鲸鱼的围捕猎物和喷出气泡网的行为,结合全局搜索和局部搜索策略,有效解决了复杂问题的优化需求。其应用广泛,涵盖函数优化、机器学习、图像处理等领域。鲸鱼算法以其简单直观的特点,成为初学者友好型的优化工具,但同时也存在参数敏感、可能陷入局部最优等问题。提供的C++代码示例展示了算法的基本实现和运行过程。
58 0