算法导论第七章快速排序

简介: 一、快速排序概述 关于快速排序,我之前写过两篇文章,一篇是写VC库中的快排函数,另一篇是写了快排的三种实现方法。现在再一次看算法导论,发现对快速排序又有了些新的认识,总结如下: (1)、快速排序最坏情况下的时间复杂度为O(n^2),虽然最坏情况下性能较差,但快排在实际应用中是最佳选择。

一、快速排序概述

关于快速排序,我之前写过两篇文章,一篇是写VC库中的快排函数,另一篇是写了快排的三种实现方法。现在再一次看算法导论,发现对快速排序又有了些新的认识,总结如下:

(1)、快速排序最坏情况下的时间复杂度为O(n^2),虽然最坏情况下性能较差,但快排在实际应用中是最佳选择。原因在于:其平均性能较好,为O(nlgn),且O(nlgn)记号中的常数因子较小,而且是稳定排序。

(2)、快速排序的思想和合并排序一样,即分治。快排排序的分治思想体现在:

a、首先从待排序的数中选择一个作为基数,基数的选择对于排序的性能有很大的影响,也是快排改进的关键所在

b、分治,将比基数小的数放在左边,比基数大的数放在右边。

c、对分出来的两个分区分别执行上一步,直到区间只有一个数为止。

二、Hoare(霍尔)排序

快速排序首先由 C. A. R. Hoare(东尼霍尔,Charles Antony Richard Hoare)在1960年提出,之后又有许多人做了进一步的优化。见书本习题7-1。

霍尔排序思路:采用数列第一个数作为基数,然后在数列的收尾两端分别设置两个“哨兵”,两个哨兵分别向中间探测比基数大、小的数,然后进行交换。如下图展示:

下面是霍尔排序的代码:

 1 int Hoare_Partition(int arr[], int left, int right)
 2 {
 3     int temp = arr[left];
 4     int i = left;
 5     int j = right;
 6 
 7     while(i < j) {
 8         while (arr[j] >= temp && i < j) //from right to left
 9             j --;
10         while (arr[i] <= temp && i < j) //from left to right
11             i ++;
12         Swap(arr[i], arr[j]);
13     } 
14     Swap(arr[left], arr[i]);
15     return i;
16 }
17 
18 void Hoare_QuickSort(int arr[], int left, int right)
19 {
20     if (left < right) {
21         int mid = Hoare_Partition(arr, left, right);
22         Hoare_QuickSort(arr, left, mid-1);
23         Hoare_QuickSort(arr, mid+1, right);
24     }
25 }

三、算法导论讲述的快排

和霍尔排序不同的是,算法导论上实现的快排选取待排序数列的最后一个数作为基数,然后也设置两个哨兵,但这两个哨兵是从头到尾一起前进探测的。如果探测到一个数比基数小,就把该数移到左边,自然右边就成了最大的数了。代码如下:

 1 int Partition(int arr[], int left, int right)
 2 {
 3     int temp = arr[right];
 4     int i = left - 1;
 5     
 6     for (int j = left; j <= right-1; j ++) {
 7         if (arr[j] <= temp) {
 8             i ++;
 9             Swap(arr[i], arr[j]);
10         }
11     }
12     Swap(arr[right], arr[i+1]); //!!!note: can't use temp:local variable
13     return i+1;
14 }
15 
16 void QuickSort(int arr[], int left, int right)
17 {
18     if (left < right) {
19         int mid = Partition(arr, left, right);
20         QuickSort(arr, left, mid-1);
21         QuickSort(arr, mid+1, right);
22     }
23 }

四、快排的优化版本

如前所述,影响快排性能最大的因素在于基数的选取,虽然不管基数如何选取,算法最坏情况下时间复杂度都还存在,但能够减少常数项因子,从而优化了算法性能。下面引述下书上介绍的几种优化机制:

1、随机优化:

因为快排中Partition所产生的划分中可能会有”差的“,而划分的关键在于主元A[r]的选择。我们可以采用一种不同的、称为随机取样的随机化技术,把主元A[r]和A[p..r]中随机选出一个元素交换,这样相当于,我们的主元不在是固定是最后一个A[r],而是随机从p,...,r这一范围随机取样。这样可以使得期望平均情况下,Partition的划分能够比较对称。

2、中位数优化法:

所谓“三数取中”是指,从子数组中随机选出三个元素,取其中间数作为主元,这算是前面随机化版本的升级版。虽然是升级版,但是也只能影响快速排序时间复杂度O(nlgn)的常数因子。见习题7-5.
3、递归栈的优化:

QUICKSORT算法包含两个对其自身的递归调用,即调用PARTITION后,左边的子数组和右边的子数组分别被递归排序。QUICKSORT中的第二次递归调用并不是必须的,可以用迭代控制结构来代替它,这种技术叫做“尾递归”,大多数的编译器也使用了这项技术。
模拟的尾递归:

代码实现:

 1 //随机优化版本
 2 //get random num between m and n;
 3 int Random(int m, int n)
 4 {
 5     srand((unsigned int)time(0));
 6     int ret = m + rand() % (n-m+1);
 7     return ret;
 8 }
 9 
10 
11 void Random_QuickSort(int arr[], int left, int right)
12 {
13     int index = Random(left, right);
14 
15     Swap(arr[index], arr[right]);
16     QuickSort(arr, left, right);
17 }
 1 //中位数优化,下面一个获取中位数的函数
 2 //get mid num of a,b,c;
 3 int MidNum(int a, int b, int c)
 4 {
 5     if ((a-b)*(a-c) <= 0)
 6         return a;
 7     else if ((b-a)*(b-c) <= 0)
 8         return b;
 9     else if ((c-a)*(c-b) <= 0)
10         return c;
11 }
 1 //模拟尾递归
 2 void Tail_Recursive_QuickSort(int arr[], int left, int right)
 3 {
 4     while (left < right) { //use while not if
 5         int mid = Partition(arr, left, right);
 6         Tail_Recursive_QuickSort(arr, left, mid-1);
 7         left = mid + 1;
 8     }
 9 }
10 
11 //尾递归优化
12 void Tail_Recursive_QuickSort_Optimize(int arr[], int left, int right)
13 {
14     while(left < right) {
15         int mid = Partition(arr, left, right);
16         if (mid-left < right-mid) {
17             Tail_Recursive_QuickSort_Optimize(arr, left, mid-1);
18             left = mid + 1;
19         }
20         else {
21             Tail_Recursive_QuickSort_Optimize(arr, mid+1, right);
22             right = mid - 1;
23         }
24     }
25 }

此外,还有一些其他的方法,比如,将递归的方式改成非递归,还有习题7-6提出的区间模糊排序法:我们无法准确知道待排序的数字是什么,但知道它属于实数轴上的某个区间,也就是知道形如[ai, bi]的闭区间。我们可以对这些区间进行排序,感兴趣的可以自己实现下。

目录
相关文章
|
10天前
|
搜索推荐 C语言
【排序算法】快速排序升级版--三路快排详解 + 实现(c语言)
本文介绍了快速排序的升级版——三路快排。传统快速排序在处理大量相同元素时效率较低,而三路快排通过将数组分为三部分(小于、等于、大于基准值)来优化这一问题。文章详细讲解了三路快排的实现步骤,并提供了完整的代码示例。
37 4
|
1月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
21 1
|
1月前
|
搜索推荐 Java Go
深入了解快速排序算法
深入了解快速排序算法
32 2
|
1月前
|
存储 搜索推荐 算法
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
|
1月前
|
算法 Python
Python算法编程:冒泡排序、选择排序、快速排序
Python算法编程:冒泡排序、选择排序、快速排序
|
1月前
|
搜索推荐 C语言 C++
【C语言】指针篇-精通库中的快速排序算法:巧妙掌握技巧(4/5)
【C语言】指针篇-精通库中的快速排序算法:巧妙掌握技巧(4/5)
|
3月前
|
搜索推荐 算法 Java
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
该博客文章通过UML类图和Java源码示例,展示了如何使用适配器模式将QuickSort类和BinarySearch类的排序和查找功能适配到DataOperation接口中,实现算法的解耦和复用。
40 1
现有一个接口DataOperation定义了排序方法sort(int[])和查找方法search(int[],int),已知类QuickSort的quickSort(int[])方法实现了快速排序算法
|
3月前
|
算法 搜索推荐
算法设计 (分治法应用实验报告)基于分治法的合并排序、快速排序、最近对问题
这篇文章是关于分治法应用的实验报告,详细介绍了如何利用分治法实现合并排序和快速排序算法,并探讨了使用分治法解决二维平面上的最近对问题的方法,包括伪代码、源代码实现及时间效率分析,并附有运行结果和小结。
|
4月前
|
算法 搜索推荐 编译器
算法高手养成记:Python快速排序的深度优化与实战案例分析
【7月更文挑战第11天】快速排序是编程基础,以O(n log n)时间复杂度和原址排序著称。其核心是“分而治之”,通过选择基准元素分割数组并递归排序两部分。优化包括:选择中位数作基准、尾递归优化、小数组用简单排序。以下是一个考虑优化的Python实现片段,展示了随机基准选择。通过实践和优化,能提升算法技能。**
56 3
|
5月前
|
搜索推荐 算法 Java
Java中的快速排序、归并排序和堆排序是常见的排序算法。
【6月更文挑战第21天】Java中的快速排序、归并排序和堆排序是常见的排序算法。快速排序采用分治,以基准元素划分数组并递归排序;归并排序同样分治,先分割再合并有序子数组;堆排序通过构建堆来排序,保持堆性质并交换堆顶元素。每种算法各有优劣:快排平均高效,最坏O(n²);归并稳定O(n log n)但需额外空间;堆排序O(n log n)且原地排序,但不稳定。
47 3