您的位置:首页 > 其它

排序算法学习——快速排序

2018-02-26 19:57 281 查看

前言

上一篇我们已经实现了一个O(NlogN)级别的归并排序,这一篇的话,我们就来实现另一个O(NlogN)级别的排序算法,鼎鼎大名的快速排序

快速排序

原理

我们先在数组中找到一个数,然后以这个数为基准,分成两部分,一部分小于这个数,另一部分大于这个数。完成之后,这个数就位于了正确的位置,接下来采用递归的方法对左右两边的两个数组继续进行快速排序。



代码实现

代码的图片解释:



/**
对arrp[L...R]部分进行partition操作
返回整型p,使得arr[L...p-1] < arr[p] && arr[p+1...R] > arr[p]
*/
template <typename T>
int __partition(T arr[], int L, int R)
{

//取第一个元素为标准
T v = arr[L];

//arr[L+1...j] < v && arr[j+1...i) > v
int j = L;
for(int i = L + 1; i <= R; i++)
{
if(arr[i] < v)
{
swap(arr[j+1], arr[i]);
j++;
}
}

//最后将标准元素放置到中间
swap(arr[L], arr[j]);

return j;
}

/**
对arr[L...R]部分进行快速排序
*/
template <typename T>
void __quickSort(T arr[], int L, int R)
{

//跳出条件
if(L >= R)
return;

int p = __partition(arr, L, R);
__quickSort(arr, L, p-1);
__quickSort(arr, p+1, R);
}

template <typename T>
void quickSort(T arr[], int n)
{
__quickSort(arr, 0, n-1);
}


优化

优化1.0

我们可以看到我们是以第一个数字作为基准的,这样带来的坏处是:当数组几乎有有序的情况下,快速排序的时间复杂度将会下降到O(n^2)。

其实快速排序归并排序有一点是相似的,他们都是使用递归将过程不断地分层,不同点在于归并排序是很稳定地将数组进行二分,所以最后的层数应该是:log2N

快速排序的切割方法并不稳定,所以导致了近乎有序的情况下效率较低。

有什么办法解决这个问题呢?答案当然是有的。如果我们将参考的数字从第一个改成完全随机的,那么是不是就解决有序的情况下效率低的问题,当然你说随机之后还是有可能,这是当然的,但是完全随机之后在有大量数据的情况下是几乎不可能有序的。

优化1.0修改代码

首先设定随机种子,修改quickSort函数:

template <typename T>
void quickSort(T arr[], int n)
{

//改进1.0,随机选择标准而不是第一个,解决近乎有序情况下的问题
srand(time(NULL));

__quickSort(arr, 0, n-1);
}


接着修改随机取的数字,只需要添加一行代码,随机取一个数和第一个数字进行交换:

/**
对arrp[L...R]部分进行partition操作
返回整型p,使得arr[L...p-1] < arr[p] && arr[p+1...R] > arr[p]
*/
template <typename T>
int __partition(T arr[], int L, int R)
{

//改进1.0,随机选择一个数
swap(arr[L], arr[rand()%(R-L+1) + L]);

//取第一个元素为标准
T v = arr[L];

//arr[L+1...j] < v && arr[j+1...i) > v
int j = L;
for(int i = L + 1; i <= R; i++)
{
if(arr[i] < v)
{
swap(arr[j+1], arr[i]);
j++;
}
}

//最后将标准元素放置到中间
swap(arr[L], arr[j]);

return j;
}


优化2.0

递归的跳出条件同样可以做一些修改,比如在数组较小的时候可以进行插入排序。这里我就不重复解释了,有兴趣可以看一下我之前归并排序是如何修改的。排序算法学习——归并排序

优化3.0

刚刚解决了近乎有序的情况,但是还有一种情况,那就是当存在大量相同大小的数据时,会导致我们的排序层数不平衡,我们的算法就又会退化到O(N^2)级别的算法。

为了解决这个问题我们提出一种新的partition方式,从数组的两端同时开始扫描,左边扫描到大于v停止,右边扫描到小于v停止,两者进行一次交换,然后继续。因为我们在相等时仍然选择了交换,因此不会产生一端过长的情况。

原理演示如图:





代码修改如下:

/**
对arrp[L...R]部分进行partition操作
返回整型p,使得arr[L...p-1] < arr[p] && arr[p+1...R] > arr[p]
*/
template <typename T>
int __partition2(T arr[], int L, int R)
{

//改进1.0,随机选择一个数
swap(arr[L], arr[rand()%(R-L+1) + L]);

//取第一个元素为标准
T v = arr[L];

//arr[L+1...i) < v && arr(j...r] > v i,j初始值保证一开始两个集合都不存在
int i = L + 1, j = R;

while(true)
{
//扫描
while(i <= R && arr[i] < v) i++;
while(j >= L + 1 && arr[j] > v) j--;
//跳出条件
if(i > j) break;
//交换
swap(arr[i], arr[j]);
i++;
j--;
}

//最后将标准元素放置到中间
//最后完成j位于<=v的最后一个,i位于>=v的第一个
swap(arr[L], arr[j]);

return j;
}

/**
对arr[L...R]部分进行快速排序
*/
template <typename T>
void __quickSort2(T arr[], int L, int R)
{

//跳出条件,可以改进
if(L >= R)
return;

int p = __partition2(arr, L, R);
__quickSort2(arr, L, p-1);
__quickSort2(arr, p+1, R);
}

template <typename T>
void quickSort2(T arr[], int n)
{

//改进1.0,随机选择标准而不是第一个,解决近乎有序情况下的问题
srand(time(NULL));

__quickSort2(arr, 0, n-1);
}


三路快排

原理

其实这可以说是优化4.0,因为第二种快速排序方法优化3.0已经很完善了。但是三路快排还是值得单独拿出来讲一下的,因为它在处理具有大量相同大小的数据时更加迅速。同时,比如一些语言(比如Java)的内置排序算法实现也是三路快排。

三路快排,哪三路比较快??当然是上路(小于v),中路(等于v),下路(大于v)一起才比较快啦。

我们最初的快速排序其实讲道理是两条路,一条小于v,另一条其实是大于等于 v,或者等号的位置换一下。这样的问题在于我们经常会重复处理相同大小的数据,这样降低了整个算法的效率。三路快排将等于v的数据单独拿出来,这样我们只要处理大于和小于v这两种数据了。

这就是中间状态,索引i不断向后推进,同时维护三个索引lt、gt、i



最后状态,进行交换之后完成排序:



代码实现

/**
三路快排处理arr[L...R]
将arr[L...R]分为<v; ==v; >v三部分
之后递归对<v ; >v两部分继续进行三路快速排序
*/
template <typename T>
void __quickSort3Ways(T arr[], int L, int R) {

//跳出条件
if(R - L <= 15) {
insertionSort(arr, L, R);
return;
}

//partition
//随机取标准
swap(arr[L], arr[rand()%(R-L+1) + L]);
T v = arr[L];

int lt = L; //arr[L+1...lt] < v
int gt = R + 1; //arr[gt...R] > v
int i = L + 1; // arr[lt...i) == v

while(i < gt) {
if(arr[i] < v) {
swap(arr[i], arr[lt+1]);
lt++;
i++;
}
else if(arr[i] > v) {
swap(arr[i], arr[gt-1]);
gt--;
}
else{
//arr[i] == v
i++;
}
}

swap(arr[L], arr[lt]);

__quickSort3Ways(arr, L, lt-1);
__quickSort3Ways(arr, gt, R);
}

template <typename T>
void quickSort3Ways(T arr[], int n) {

srand(time(NULL));
__quickSort3Ways(arr, 0, n-1);
}


当然优化是无止境,这里就先介绍到这啦!!欢迎交流。

图片引用百度图片

代码实现参照liuyubobobo慕课网教程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  快速排序