`
king_tt
  • 浏览: 2259353 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

【算法导论】排序 (三):快速排序 深入分析

 
阅读更多

五、快速排序


快速排序是最常用的一种排序算法,包括C的qsort,C++和Java的sort,都采用了快排(C++和Java的sort经过了优化,还混合了其他排序算法)。

快排最坏情况O( n^2 ),但平均效率O(n lg n),而且这个O(n lg n)几号中隐含的常数因子很小,快排可以说是最快的排序算法,并非浪得虚名。另外它还是就地排序。


快速排序是基于分治模式的:

分解:数组A【p..r】被划分成两个(可能空)子数组A【p..q-1】和A【q+1..r】,使得A【p..q-1】中的每个元素都小于等于A(q),而且,小于等于A【q+1..r】中的元素。下 标q 也在返个划分过程中迕行计算。

解决:通过递归调用快速排序,对子数组A【p..q-1】A【q+1..r】排序。

合并:因为两个子数组使就地排序的,将它们的合并不需要操作:整个数组A【p..r】已排序。


下面过程实现快速排序:



数组划分:Partition(关键,它对子数组A【p..r】进行就地重排)



C++实现:

//*  算法导论 第七章 快速排序 *//
#include<cstdio>
#include<ctime>
#include<cstdlib>
  
 
void Swap(int &a,int &b){ if(a!=b){a^=b;b^=a;a^=b;} }  //如果两个数相等,就不执行位运算交换。因为如果两数相等,结果会是0
 
 
int Partition(int *A,int p,int r){
    int x, i;
    x=A[r];
    i=p-1;
    for(int j=p; j<=r-1; ++j){
        if(A[j]<=x) {
            Swap(A[++i], A[j]);    
        }
    }
    Swap(A[++i],A[r]);
    return i;
}
 
void QuickSort(int *A,int p,int r){
    if(p<r){
        int q=Partition(A,p,r);
        QuickSort(A,p,q-1);
        QuickSort(A,q+1,r);
    }
}
 
 
int main()
{
    int arr[12]={2,7,4,9,8,5,7,8,2,0,7,-4};
    QuickSort(arr,0,11);
    for(int i=0; i<12; ++i)
        printf("%d ",arr[i]);
    putchar('\n');
    return 0;
}

快排的性能分析:

当数据量很小的时候,大概就十来个元素的小型序列,快排的优势并不明显,甚至比插入排序慢。但是一旦数据多,它的优势就充分发挥出来了。


举一个例子,C++ STL 中的sort函数,就充分发挥了快排的优势,并且取长补短,在数据量大时采用QuickSort,分段递归排序。一旦分段后的数据量小于某个门槛,为避免QuickSort递归调用带来过大的额外负荷,就改用插入排序。如果递归层次过深,还会改用HeapSort(堆排序)。所以说,C++的“混合兵种”sort的性能肯定会比C的qsort好。


快排的运行时间与Partition的划分有关

最坏情况是输入的数组已经完全排好序,那么每次划分的左、右两个区域分别为n-1和0,效率为O( n^2 ).

而对于其他常数比例划分,哪怕是左右按9:1的比例划分,效果都是和在正中间划分一样快的(算法导论上有详细分析)

即,任何一种按照常数比例进行划分,总运行时间都是O(n lg n).


快排的随机化版本:

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


伪代码的实现 :



C++实现:

int Random(int m,int n){  
    srand((unsigned)time(NULL));  // 包含头文件<cstdlib>
    return m+(rand()%(n-m+1));
}
 
int Random_Partition(int *A,int p,int r){
    int i=Random(p,r);
    Swap(A[r],A[i]);
    return Partition(A,p,r);
}
 
void Random_QuickSort(int *A,int p,int r){
    if(p<r){
        int q=Random_Partition(A,p,r);
        Random_QuickSort(A,p,q-1);
        Random_QuickSort(A,q+1,r);
    }
}


快排的进一步优化的讨论:

1、尾递归:

传统的递归算法在很多时候被视为洪水猛兽. 它的名声狼籍, 好像永远和低效联系在一起尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。

快排中的堆栈深度:

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

下面这个版本模拟了尾递归:

QUICKSORT'(A, p, r)

1 while p < r

2 doPartition and sort left subarray.

3 q ← PARTITION(A, p, r)

4 QUICKSORT'(A, p, q - 1)

5 p ← q + 1


需要注意第一行是 while而不是if

但是这个版本在最坏的情况下,就是划分不好的时候,递归深度为O(n),可以再进一步优化使栈深度为O(lg n)吗?

用二分的思想,为了使最坏情况下栈的深度为Θ(lgn),我们必须是PARTITION后左边的子数组为原来数组的一半大小,这样递归的深度最多为Θ(lgn)。

一种可能的算法是:首先求得(A, p, r)的中位数,作为PARTITION的枢轴元素,这样可以保证左右两边的元素的个数尽可能的均衡。

因为求中位数的过程MEDIAN的时间复杂度为Θ(n),因此可以保证算法的期望的时间复杂度O(nlgn)不变。


优化版的尾递归快排:



2. "三数取中"划分

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


下面将给出综合了”优化的尾递归“+”三数取中“版本的Final 快排版本:

//  优化的尾递归 + 三数取中 版本快排 
#include<cstdio>
#include<ctime>
#include<cstdlib>
  
 
void Swap(int &a,int &b){ if(a!=b){a^=b;b^=a;a^=b;} }  //如果两个数相等,就不执行位运算交换。因为如果两数相等,结果会是0
 
 
int Partition(int *A,int p,int r){
    int x, i;
    x=A[r];
    i=p-1;
    for(int j=p; j<=r-1; ++j){
        if(A[j]<=x) {
            Swap(A[++i], A[j]);    
        }
    }
    Swap(A[++i],A[r]);
    return i;
}

inline int Random(int m,int n){
    srand((unsigned)time(NULL));
    return m+(rand()%(n-m+1));
}

// 取出三个数的中间数(第二大的数)的函数
inline int MidNum(int a,int b,int c){
	if(c<b) Swap(c,b);
	if(b<a) Swap(b,a); // 经过这两个交换,a变成三数最小的
	return b<c?b:c;
}

int ThreeOne_Partition(int *A,int p,int r){
	int i,j,k,mid;
	
	// 随机选择三个数
	i=Random(p,r);
	j=Random(p,r);
	k=Random(p,r);

	// 取出“中间数”
	mid=MidNum(A[i],A[j],A[k]);
	
	// 将“中间数”和A【r】交换
	if(A[i]==mid) Swap(A[i],A[r]);
	else if(A[j]==mid) Swap(A[j],A[r]);
	else if(A[k]==mid) Swap(A[k],A[r]);

	return Partition(A,p,r);
}

void Final_QuickSort(int *A,int p,int r){
	while(p<r){
		int q=ThreeOne_Partition(A,p,r);
		if(q-p<r-q){
			Final_QuickSort(A,p,q-1);
			p=q+1;
		}
		else{
			Final_QuickSort(A,q+1,r);
			r=q-1;
		}
	}
}

int main()
{
    int arr[12]={2,7,4,9,8,5,7,8,2,0,7,-4};
    Final_QuickSort(arr,0,11);
    for(int i=0; i<12; ++i)
        printf("%d ",arr[i]);
    putchar('\n');
    return 0;
}

除此之外,快排还可以有优化:

非递归的方法:即模拟递归,这样可以完全消去递归的调用。

三划分快速排序:基本思想是,在划分阶段以V=A[r]为基准,将带排序数组A【p..r】划分为左、中、右三段A【p,j】,

A【j+1..q-1】,A【q..r】,其中左段数组元素值小于V,中断数组等于V,有段数组元素大于V。其后,算法对左右

两段数组递归排序。 这个方法对于有大量相同数据的数组排序效率有很大的提高,即使没有大量相同元素,也不

降低原快排算法的效率。


以上两种以后有机会再把代码实现一遍吧。


快速排序的总结到这里结束。


目前主要总结了五大排序,都是基于比较的排序,最快也只有O(n lg n)。有没有更快的?

下一篇将总结线性时间排序,其速度将会突破这个瓶颈。



本人只是个小菜鸟,如果错误,希望指出,谢谢



—— 生命的意义,在于赋予它意义。

原创http://blog.csdn.net/shuangde800By D_Double





分享到:
评论

相关推荐

    算法导论 中文 第三版 高清

    《算法导论》是计算机科学领域的一本经典著作,它为读者提供了全面而深入的算法理论与实践知识。中文第三版的出版,使得更多中国读者能够无障碍地学习这本权威教材。英文版第四版虽然在描述中提及,但主要讨论的重点...

    算法导论第三版及2-25章部分答案

    前者很显然是《算法导论》第三版的电子版,读者可以通过这份PDF文档深入学习书中涵盖的各种算法,如排序、搜索、图算法、动态规划、贪心算法、分治策略等。这些算法是计算机科学的基础,对于提升编程能力和解决复杂...

    算法导论中文第三版习题答案

    《算法导论》是计算机科学领域的一本经典著作,它深入浅出地介绍了算法的设计、分析和实现。中文第三版的出版,使得更多的中文读者能够接触到这本权威教材。本书覆盖了从排序和搜索到图算法,再到动态规划和贪心算法...

    算法导论第三版答案(完整版)

    《算法导论》是计算机科学领域的一本经典著作,它深入浅出地介绍了算法的设计、分析和实现。这本书的第三版更是集大成者,涵盖了众多重要的算法和数据结构,对于学习和研究计算机科学的人来说是不可或缺的参考书。...

    算法导论第三版英文版

    综上所述,《算法导论》(第三版)不仅是一本理论性很强的教材,也包含了大量的实践案例和算法实现细节,非常适合那些希望深入了解算法设计与分析的学生和专业人员。此外,这本书还涵盖了广泛的算法主题,从基础到...

    算法导论_完整课件:算法导论_完整课件

    《算法导论》是计算机科学领域的一本经典教材,它深入浅出地讲解了各种算法的设计、分析和实现。这组完整的课件包含了多讲内容,涵盖了算法的基础和高级主题,旨在帮助学习者掌握编程中不可或缺的算法知识。 1. **...

    算法导论第三版课后习题答案

    《算法导论第三版》是计算机...综上所述,“算法导论第三版课后习题答案”不仅提供了具体的习题解答,还深入解析了算法的设计原理、复杂性分析以及实际应用场景,对于深入理解算法、提高编程能力具有重要的指导意义。

    算法导论(第三版)英文原版

    总体来说,《算法导论》(第三版)英文原版是计算机科学领域不可或缺的参考资料,它不仅为读者提供了算法知识的广泛覆盖,而且深入浅出地介绍了算法理论与实践的结合,使读者能够深入理解算法的原理和应用。...

    算法导论中文版

    7. 图算法:介绍图的基本概念,如路径、连通性、有向图和无向图等,深入分析图的遍历算法(深度优先搜索和广度优先搜索),以及最短路径算法(如迪杰斯特拉算法和贝尔曼-福特算法)。 8. 动态规划:讲解动态规划的...

    算法导论第三版答案_算法导论_

    《算法导论》是计算机科学领域的一本经典之作,它深入浅出地介绍了算法的设计、分析和实现。这本书对于Python编程者来说,尤其有价值,因为它不仅涵盖了基础算法,还涉及了高级算法技巧,有助于提升编程者的问题解决...

    算法导论第三版 教师版

    **《算法导论第三版 教师版》**是一本在计算机科学领域内被广泛使用的教材,由四位知名的计算机科学家Thomas H. Cormen、Charles E. Leiserson、Ronald L. Rivest以及Clifford Stein共同编著。这本书不仅详尽地介绍...

    算法导论(第2版)参考答案

    - **快速排序算法**:讲解了快速排序算法的具体实现和性能分析。 #### 第八章:线性时间中的排序 - **基数排序**:介绍了一种基于数字位的排序方法——基数排序。 - **桶排序**:讨论了桶排序的基本思想及其应用...

    算法导论试题及答案

    《算法导论》是计算机科学领域的一本经典教材,由Thomas H. Cormen、Charles E. Leiserson、Ronald L. Rivest和Clifford Stein四位作者共同编写,广泛应用于全球各大高校的教学中,包括知名的麻省理工学院(MIT)。...

    算法导论.rar

    《算法导论》第三版还引入了最新的研究成果,如近似算法、线性规划和动态规划的更深入探讨,以及新加入的计算几何和网络流等内容,使得这本教材更加全面和实用。无论是初学者还是专业人士,都能从中受益匪浅,提升...

    算法导论(原书第3版) 中文完整版带目录

    《算法导论》是计算机科学领域的一本经典著作,它为读者提供了全面而深入的算法理论与实践知识。原书的第三版包含了丰富的更新和扩展,尤其适合对算法有深入研究的学生和专业人士。这本书涵盖了从基础到高级的各种...

    算法导论章节答案(31~35章)

    《算法导论》是计算机科学领域的一本经典教材,涵盖了广泛的算法分析和设计技术。这本书的第31至35章包含了许多关键的算法概念,这些章节深入探讨了不同的算法主题,对于理解和掌握算法有着至关重要的作用。以下是这...

    算法导论习题解. \算法导论习题解.

    综上所述,《算法导论习题解》这本书深入浅出地讲解了算法的基础知识、排序算法及其实现,并且提供了详细的习题解答,非常适合学习和复习算法相关知识。通过对这些内容的学习,读者可以更好地理解和掌握算法设计与...

Global site tag (gtag.js) - Google Analytics