《算法》C++代码 快速排序

快速排序,简称快排,常称QuickSort、QSort。在排序算法中非常常用,其编程复杂度低,时间复杂度O(logN),空间复杂度O(N),执行效率稳定,而且常数很低。

基本思想就是二分,例如你要将N个数排序,你调用了QSort(1,N)。那么快排会这样做:

1、找出一个数x

2、将N个数分成两部分,左边的都比x小,右边的都比x大

3、分别对两边调用QSort

很显然,这是二分,递归实现。

  先说第二步,代码别写得太难看,时间复杂度就是O(N),扫一遍就可以了。于是,重点便是第一步——我们假设你找x的时间是O(1),那么如果你的x每次找到的都是中位数,那么算法时间就是O(NlogN);如果你的x每次找到的都是最边上的数(以至于你将N个数分成了1个和N-1个),那么算法时间就是O(N²)。因此,只有在优秀的选x方法下,快排才能保证O(NlogN)的复杂度。

  我们来详细讨论一下第一步(下面分析“中位数”的实际含义,以及给出两种常见实现取法,高手可以跳过直接去看代码了~)。

  我们理想情况是找中位数,但是你不可能真正去找中位数,因为那样的时间是O(N)。新手很头疼,“这咋整?”方法很简单:随便选一个就好了。

  新手更头疼了……“你随便选一个,选的时间当然是O(1)了,可你凭什么保证算法复杂度不退化?”其实,我也不能保证算法不退化,但我知道从概率上说,我每次都随机选,大部分时候都没退化多少,结果就只有很低很低的概率退化成O(N²)。新手很鄙视,“如果我完全可以给你构造出一组数据,让你每次都选边上的啊!这样不就退化成O(N²)了吗? ”这点其实是不可能的。因为我不是固定选某个位置的数,而是随机选,所以你根本无法构造,我退化多少,只取决于概率。

  新手要放弃了……“你这快排复杂度直接取决于概率,可我概统没学好,也不知道快排的退化概率是多少,我怎么敢用啊!万一我用的时候正好退化了咋办!”这点,便是我今天要重点和广大新手说的,你们接触到了一个算法中很重要的概念:随机算法。

  算法复杂度,只是对算法一个很粗的描述。你知道一个算法的复杂度是O(N²),其实你只是知道它是两阶而已,根本不知道真正的复杂度。复杂度的常数是多少?是3N²、0.3N²,还是1.3N²?平时我们不分析,是因为我们都按照最大复杂度分析的。题目给你N=1000,你知道算法复杂度O(N²),又知道常数很大时(例如100)程序不到1s可以运行完,于是你便敢写了。可是现在不行了,算法是随机的,好的时候O(NlogN)常数还很小,坏的时候O(N²)常数还很大,你还敢不分析?

  可能有人不敢用,觉得只要是概率就不能保证没问题,万一考试碰上就惨了。这种思想一般都是新手才会有,请你务必说服自己!我的理由很简单,概率太高我也不敢用,我的做法是,把概率降到比你某天出门被花盆意外砸死的概率还要低,我就敢用了,因为我确信我不会某天出门被花盆砸死。

  当然,明确了这一点,现在的问题就是,不会分析怎么办?长远来看,你还是回去好好学学概率,再回来分析得好;短期来看,有没有简单些的方法呢?当然有,就是测试。你随机出很多很多数据,用你写的快排去测,发现他们最惨的也完全可以算作O(NlogN),那基本就没问题了,因为实际考试和实际应用数据情况也基本是这样。

  好了,说了这么多,其实只是因为理解快排的思想是很多新手的一道门槛。我希望能通过自己多说些废话,帮助很多新手顺利迈过去。这样,对很多新手以后的算法之路都是有益无害的。下面让我们讨论第一步实际应当如何做:

  必须明确,如果选择方法过于复杂,那么算法常数会变大;如果方法过于简单,那么算法复杂度会退化。因此综合考虑,加以分析和大量实测,比较常见的既好写又快的写法有两种。假设你有N个数A[1~N]:

1、x=mid(A[1],A[(1+N)/2],A[N]),mid是指取这三个数的中位数。这是最常用的一种方法,如果我没记错的话,这种算法也是C++算法库(algorithm)里面的写法。实际情况表明,这种取法效率很高。

2、x=A[randint(1,N)],也就是下标取1~N中随机一个数。这也是比较常用的一种方法,好处是真正保证了随机性,但坏处是生成随机数耗时比较高,会导致算法常数变大。

说了这么多,新手可能会觉得我还是没说明为什么快排的复杂度是O(NlogN)。我只能说,要分析快排复杂度需要很细的分析和大量的数据,有机会我会单独写一篇文章来分析的,现在我只能从概率上告诉你大部分时候都是O(NlogN),而且快排常数比堆排小不少(时间大概快一倍吧,没实测过,瞎说),能卡快排的数据你也暂时遇不到。我不敢说没有数据能卡快排,但我可以确定,如果不是特意要卡你,这样写快排一定没问题,反正我考试是敢用的。要是真有人死活卡你,那你就写堆排吧,常数大点,但确实不可能被卡。

下面给出我的代码:

 1 inline void swap(int &a,int &b) { int t=a; a=b; b=t; }
 2
 3 inline int mid(int a,int b,int c)
 4 {
 5     if(a>b) swap(a,b);
 6     if(b>c) swap(b,c);
 7     if(a>b) swap(a,b);
 8     return b;
 9 }
10
11 void QSort(int A[],int l,int r) // ??????
12 {
13     if(l>=r) return;
14     int i=l,j=r,x=mid(A[l],A[(l+r)>>1],A[r]);
15     while(true)
16     {
17         while(A[i]<x) ++i;
18         while(A[j]>x) --j;
19         if(i>j) break;
20         swap(A[i],A[j]); ++i; --j;
21     }
22     QSort(A,l,j); QSort(A,i,r);
23 }
时间: 2024-11-06 07:39:00

《算法》C++代码 快速排序的相关文章

Python实现各种排序算法的代码示例总结

Python实现各种排序算法的代码示例总结 作者:Donald Knuth 字体:[增加 减小] 类型:转载 时间:2015-12-11我要评论 这篇文章主要介绍了Python实现各种排序算法的代码示例总结,其实Python是非常好的算法入门学习时的配套高级语言,需要的朋友可以参考下 在Python实践中,我们往往遇到排序问题,比如在对搜索结果打分的排序(没有排序就没有Google等搜索引擎的存在),当然,这样的例子数不胜数.<数据结构>也会花大量篇幅讲解排序.之前一段时间,由于需要,我复习了

算法——基础篇——快速排序

快速排序是一个经常使用的算法,由于每次用的时候,都感觉没有理解清楚,特写一篇文章记录一下. 算法介绍 快速排序有点类似有冒泡排序,冒泡排序从相邻的两个元素比较,小的在左边,大的在右边,这个算法很容易理解.而快速排序它相当于是在一头一尾两边分别排序比较,比较的对象是当前元素值,和一个选定的key值,主题的思想就是通过跟key值比较,把大于key的值放在右边,小于的放在左边这样就完成了一次排序,接着在对key值左边的序列进行同样的操作,右边也是,最后便能将所有的元素给排好序,由于它每次排序,都会分成

iOS算法(一)置快速排序算法

快速排序是当遇到较大数据时,排序快,高效的方法(公司面试时,基本上会被问到...) 该方法的基本思想是: 1.先从数列中取出一个数作为基准数. 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边. 3.再对左右区间重复第二步,直到各区间只有一个数. 简单地理解就是,找一个基准数(待排序的任意数,一般都是选定首元素),把比小于等于基准数的元素放到基准数的左边,把大于基准数的元素放在基准数的右边.排完之后,在把基准数的左边和右边各看成一个整体, 左边:继续选择基准数把小于等

Javascript算法系列之快速排序(Quicksort)

Javascript算法系列之快速排序(Quicksort) 原文出自: http://www.nczonline.net/blog/2012/11/27/computer-science-in-javascript-quicksort/ https://gist.github.com/paullewis/1981455#file-gistfile1-js 快速排序(Quicksort)是对冒泡排序的一种改进,是一种分而治之算法归并排序的风格 核心的思想就是通过一趟排序将要排序的数据分割成独立的

算法笔记之快速排序

1.1 算法思路-- 该算法在数组中选定一个元素作为主元(一般选第一个),然后以这个主元为参考对象将数组分为两个部分,第一部分都是小于或者等于主元,第二部分都是大于或者等于主元.然后对第一和第二部分递归地使用快速排序算法,直到分到最小的小组为止. 1.2 时间复杂度-- 在最差的情况下,要把n个元素的数组划分,需要n次比较和n次移动.假设用T(n) 来表示使用快速排序算法来排序n个元素的数组所耗费的时间.那么 T(n) = T(n/2)+ T(n/2) +2n 所以,快速排序的时间复杂度应该是

一步一步写算法(之快速排序)

原文:一步一步写算法(之快速排序) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 快速排序是编程中经常使用到的一种排序方法.可是很多朋友对快速排序有畏难情绪,认为快速排序使用到了递归,是一种非常复杂的程序,其实未必如此.只要我们使用好了方法,就可以自己实现快速排序. 首先,我们复习一下,快速排序的基本步骤是什么: 1. 判断输入参数的合法性 2.把数组的第一个数据作为比较的原点,比该数据小的数据排列在左边,比该数据大的数据排列在右边 3

常用算法之排序算法三【快速排序】

快速排序是东尼·霍尔在1962提出的划分交换排序,并采用一种分治的策略.在这,我们先总结一下:快速排序 = 划分交换排序 + 分治.然后我们在一一介绍他. 划分交换排序 在讨论它时,感觉有种看了崔颢<黄鹤楼>之后,再去写黄鹤楼的感觉,因为MoreWindows写 得白话经典算法系列之六 快速排序 快速搞定已经足够出色了.我在这只是进行简单的复述,你需要了解更多请看他的博文. 先来看看划分交换排序的具体算法描述: 1.从数列中选出一个数作为基准 2.从数组中选出比它大的数放右边,比它小的数放左边

各种排序算法的代码

1 // ALLKindsOfSorts.cpp : 定义控制台应用程序的入口点. 2 // 3 4 #include "stdafx.h" 5 #include<iostream> 6 #include<vector> 7 #include<bitset> 8 9 using namespace std; 10 11 ////////////////////////////////////////所有的排序总结///////////////////

&lt;算法笔记&gt;关于快速排序的算法优化排序(顺便给百度百科纠个错)

快速排序是排序算法之中的基本中的基本,虽然越来越多的接口函数将快速排序“完美的封装了起来”,比如C++中的qsort或者<algorithm>中的sort(与stable_sort相对应),但是深入思考,关于快速排序的优化你可曾想过?:-P (一)经典快速排序 首先我们来看一下这个百度百科之中的快速排序版本 在上面这张图中,我们一边纠错一边复习下基本的快速排序,基本快速排序的函数体可以明确分为三个块: 1.调整块,根据对称轴pivot(一般选取第一个)从数组的前后两端向中间扫描,pivot作对

算法整理(二)---快速排序的两种实现方式:双边扫描和单边扫描

首先简单谈下快速排序的特点,时间复杂度O(nLog n),最差时间复杂度O(n^2),平均时间O(nLog n).因为用到了函数栈,空间复杂度为O(lg n),最差为O(n).是一种不稳定的排序方法.基本思想是分治法,这位大大的http://blog.csdn.net/morewindows/article/details/6684558 讲的非常清楚了,分治法+挖坑法,我就不多说了.就是以某个数为参照,使得左边的都小于他,右边的数都大于他.然后对他的左右两个区间采取同样的方法进行递归. 就其整