您的位置:首页 > 其它

快速排序

2018-03-24 15:47 337 查看

一、快速排序的思想

快速排序是生活中比较常用的一种排序算法,它的特点就像它的名字一样速度快、效率高。
快速排序采用的思想是分治思想,先简单介绍一下分治思想。分治思想的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可以得到原问题的解。      

既然快速排序用到了分治的思想,那么也就是说快速排序也就和分治算法一样为了进行排序需要先对其划分的子区间进行排序。其基本思想就是:在待排序的序列中选取一个值作为一个基准值,按照这个基准值的大小将这个序列划分为两个子序列,基准值会在这两个子序列的中间,一边是比基准值小的,另一边是比基准值大的。 这样快速排序第一次排完,我们选取的这个基准值就会出现在它该出现的位置上。 这就是快速排序的单趟算法,也就是完成了一次快速排序。然后再对这两个子序列按照同样的方法进行排序,直到只剩下一个元素或者没有元素的时候就停止,这时候所有的元素都出现在了该出现的位置上。

二、快速排序的单趟算法   

    关于快速排序的单趟算法,我所熟知的有三种方法:左右指针法、挖坑法、前后指针法。
1、左右指针法:
左右指针法的实现思路:在一段区间内我们有一个值key,从左区间进行遍历,直到找到一个大于key的值就停下来,然后再从右边开始找小于key的值,找到一个也停下来,然后将左右的值进行交换,那样左边那个大于key的值就被换到了右边,而右边那个小于key的值就被换到了左边。当左右两个指针相遇的时候就说明所有的元素都与key做过了比较。然后再将左指针所在的元素赋给key,此时按照上述方法进行递归实现[left,key]和[key+1,right]。//1.左右指针法
int PartSort1(int *a,int left,int right)
{
int& key = a[right]; //此处必须用引用才能达到后面用key作为交换值的效果
int begin = left;
int end = right;
while(begin < end)
{
while(begin < end && a[begin] <= key)//特别需要注意边界条件
++begin;
while(begin < end && a[end] >= key)
--end;
//走到此处,begin所指向的值比key大,end所指向的值比key小
if(begin < end)
swap(a[begin],a[end]);
}
//走到此处,begin和end相遇
swap(a[begin],key);//上面的key需要加上引用
return begin;
}
2.挖坑法:
挖坑法的思想是类似于左右指针法的,思想是将最右边的值保存下来,作为key值。这时候最右边的值被取出去,最右边就相当于有了一个坑,我们从左向右遍历,找到一个比key值大的数把它填到这个坑里,这时候就相当于坑在左边,我们从右向左进行遍历找比key小的数,找到后再次填到当前的坑里,以此类推,大致思想和上面的解法其实是很相似的。//挖坑法
int PartSort2(int *a,int left,int right)
{
int begin = left;
int end = right;
int key = a[end];
int dig = right; //标记当前坑的位置
while(begin < end)
{
while(begin < end && a[begin] <= key)
++begin;
if(begin < end)
{
a[dig] = a[begin];
dig = begin;
}
while(begin < end && a[end] >= key)
--end;
if(begin < end)
{
a[dig] = a[end];
dig = end;
}
}
a[dig] = key;
return dig;
}
3.前后指针法:
前后指针法的思路就是有两个指针,一个为cur,另一个为prev。开始的时候让cur指向left,让prev指向left的前一个位置。让cur向后找比key小的值,找到之后就让prev++,如果此时prev与cur不相等就让prev与cur进行交换。如果找不到比key小的值就一直让cur向后走,直到走到区间的最右边就停止,当cur走到边界的时候就让cur与prev进行交换。不断缩小边界,相同的方法进行遍历子区间。//前后指针法
int PartSort3(int *a,int left,int right)
{
int cur = left;
int prev = left-1;
int& key = a[right];
while(cur < right)
{
//prev == cur时,其实也要交换的,不过此时它俩是指向同一个值
if(a[cur] < key && ++prev != cur)
{
swap(a[prev],a[cur]);
}
++cur;//不管怎样,cur都要往后走
}
//cur走到了key所在的位置了
swap(a[++prev],key);
return prev;
}

三、快速排序的时间复杂度以及优化

通过上面讲解的快速排序的单趟算法我们知道,快速排序试讲一个问题转化为求解小区间来进行解决。如果每次我们选取的那个key值刚好是整个区间序列的中间那个位置,那么它分成的那个子区间就会差别不大,这个时候我们可以把快速排序看成一颗二叉树。如下图所示:



我们可以看到如果选的key值的正确位置刚好在这个序列中间,那么此时可以看成一个二叉树。这个时候快速排序的时间复杂度是O(n*lgn)。但是,如果这个key值的正确位置是在这个区间的最边上,就是说我们选择的key是最大值或者最小值,那么就会产生的一个子区间就是空的,这时候快速排序的时间复杂度就会达到O(n*n)。所以,我们需要的是时间复杂度小的快速排序,为此我们就要让快速排序选择的那个key值接近于这个序列的中间。于是,我们以此思想来进行快速排序的优化。

优化1:三数取中法
三数取中法就是我们取三个数中间的那个数,这样我们就能在给定的一段区间中找到那个每次出现在中间的那个数。 int GetMidIndex(int* a,int left,int right) //三数取中法
{
int mid = left + ((right - left) >> 1);

if (a[left] < a[mid])
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return right;
}
else
{
return left;
}
}
else //a[left]>=a[mid]
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[left] > a[right])
{
return right;
}
else
{
return left;
}
}
}
优化2:小区间优化
当我们划分的子区间很小的时候,我么使用快速排序对于这些小区间进行排序的时候,如果我们还使用快速排序的话就会得不偿失,因为快速排序区间的划分就像二叉树一样,约到下面递归越深,那么还不如我们把这剩下的数取出来用其他的排序,这样的话也就提高了快速排序的效率。 void QuickSort(int *a,int left,int right) //小区间优化
{
assert(a);
if (left < right)
{
if (right - left > 10) //当然这里的小区间是视情况而定的
{
int div = PartSort1(a,left,right);
QuickSort(a,left,div-1);
QuickSort(a,div+1,right);
}
else
{
InsertSort(a+left,right-left+1); //这里的InsertSort用的是直接插入排序
}
}
}
四、关于快速排序的非递归实现
上面讲到快速排序使用递归来实现的,我们知道如果递归特别深的情况下就会不断的去创建函数栈帧,增加了函数调用的开销就会影响函数执行的效率,那么这个时候采取非递归的快速排序是非常有必要的。说到非递归,已经非常简单了,我们可以直接使用前面学过的栈来进行实现。//递归转化为非递归,借用栈先进后出的性质来实现
#include<stack>
void QuickSortNonR(int *a,int left,int right)
{
assert(a);
stack<int> s;
if(left < right)
{
s.push(right);//先进去的后出来
s.push(left);
}
while(!s.empty())
{
int begin = s.top(); //left
s.pop();
int end = s.top(); //right
s.pop();

int div = PartSort1(a,begin,end);
// [begin,div-1] [div+1,end]
if(begin < div-1)
{
s.push(div-1);
s.push(begin);
}
if(div+1 < end)
{
s.push(end);
s.push(div+1);
}
}
}上面就是我关于快速排序的理解,包括快速排序单趟的算法以及它的优化方法。在快速排序中我们知道它的时间复杂度在O(lgn)~O(n*n),在经过我们的优化方法改善之后它的时间复杂度是接近O(lgn)的,相对于其他排序来说快速排序时间复杂度还是比较低的。

若有错误请留言指正呐,我们共同进步!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  快排 算法 优化