一、快速排序的过程
快速排序:
快速排序是一种划分交换的方法,它采用分治法进行排序。其基本思想是取待排序元素序列中的某个元素作为基准值,按照这个元素的大小,将整个元素序列划分成两个左右两个子序列,使得左子序列的值都比这个元素小,右子序列中的值都比这个元素值要大于或等于,基准元素值则排在这两个序列中间,然后再对这两个子序列进行同样的方法,直到所有元素都排在相应的位置(即序列中只有一个元素或者没有元素的时候)。
例如:使用快速排序算法对{2,5,4,9,3,6,8,7,1,0}进行排序。
上图是对这个数组进行的第一次的划分,将这个数组分成了左子序列和右子序列(紫色的那部分),我们再对这两部分进行同样的操作,直到划分的序列中只有一个元素或者没有元素的时候停止。所以这种算法用递归来解是非常简单的。
代码实现:
方式1:
我们使用一个PartSort函数,其作用是对一个数组进行划分,并返回最后基准位置的下标。
int PartSort(int *a,int begin,int end)
{
int left = begin;
int right = end;
while (left < right)
{
while (left < right&&a[left]<=a[end]) //找到一个大于基准值的值
{
left++;
}
while (left < right&&a[right] >=a[end]) //找到一个小于基准值的值
{
right--;
}
if (left<right)
swap(a[left],a[right]);
}
swap(a[left], a[end]);
return left;
}
再使用一个QuickSort函数,对划分后的得到的子序列进行递归处理。
//[begin,end]
void QuickSort(int *a,int begin,int end)
{
assert(a);
int div=PartSort(a,begin,end);
if (div-1>begin) //这个区间的元素个数大于一个的时候就要在进行划分排序
QuickSort(a,begin,div-1);
if (div+1<end)
QuickSort(a,div+1,end);
}
以上面的例子为例,选择第一个元素作为基准值,写的时候应该要注意到:
1、从left开始找大于基准值的值的时候的判断条件必须是小于等于,因为有可能会出现重复的值。
2、left必须从基准值的下标开始。否则的话:如果第一个元素就是最小的值,则后面的值都比基准值大,则在left和right相遇处的值与基准值再交换的话就会产生错误。
方法2:挖洞法。
再对子序列重复上述操作,直到划分的子序列中元素的个数只有一个或者没有的时候才停止,其代码实现用递归来实现是非常容易的。
int PartSort(int *a,int begin,int end)
{
int left = begin;
int right = end;
int key = a[end];
int blank = end;
while (left<right)
{
if (blank == right) //从左边开始找一个比基准数大的值
{
while (left<right&&a[left]<=key)
{
left++;
}
a[blank] = a[left];
blank = left;
}
else if (blank == left) //从右边开始找一个比基准值小的值
{
while (left<right&&a[right]>=key)
{
right--;
}
a[blank] = a[right];
blank = right;
}
}
a[blank] = key;
return blank;
}
void QuickSort(int *a,int begin,int end)
{
assert(a);
if (begin < end)
{
int div=PartSort(a,begin,end);
QuickSort(a,begin,div-1);
QuickSort(a,div+1,end);
}
}
方法3:前后指针法
再对子序列重复上述操作,直到划分的子序列中元素的个数只有一个或者没有的时候才停止,其代码实现用递归来实现是非常容易的。
int PartSort(int* a,int begin,int end)
{
int prev = begin - 1;
int cur = begin;
while (cur<end)
{
if (a[cur] < a[end])
{
prev++;
if (prev != cur)
swap(a[prev], a[cur]);
}
cur++;
}
prev++;
swap(a[prev], a[end]);
return prev;
}
//利用前后指针来快速的实现划分左右子区间
void QuickSort(int *a,int begin,int end)
{
assert(a);
if (end>begin)
{
int div = PartSort(a, begin, end);
QuickSort(a, begin, div - 1);
QuickSort(a, div + 1, end);
}
}
二、快速排序的性能分析
快速排序是一种快速的分而治之的算法,它是已知的最快的排序算法,其平均运行时间为O(N*1ogN) 。它的速度主要归功于一个非长紧凑的并且高度优化的内部循环。但是他也是一种不稳定的排序,当基准数选择的不合理的时候他的效率又会编程O(N*N)。
快速排序的最好情况:
快速排序的最好情况是每次都划分后左右子序列的大小都相等,其运行的时间就为O(N*1ogN)。
快速排序的最坏情况:
快速排序的最坏的情况就是当分组重复生成一个空序列的时候,这时候其运行时间就变为O(N*N)
快速排序的平均情况:
平均情况下是O(N*logN),证明省略。
三、快速排序的改进
改进1:
因为虽然快速排序整体的效率可观,但是当最坏情况发生时它的效率就会降低,为了降低最坏情况发生的概率,我们可以做如下改进。
当我们每次划分的时候选择的基准数接近于整组数据的最大值或者最小值时,快速排序就会发生最坏的情况,但是每次选择的基准数都接近于最大数或者最小数的概率随着排序元素的增多就会越来越小,我们完全可以忽略这种情况。但是在数组有序的情况下,它也会发生最坏的情况,为了避免这种情况,我们在选择基准数的时候可以采用三数取中法来选择基准数。
三数取中法:
选择这组数据的第一个元素、中间的元素、最后一个元素,这三个元素里面值居中的元素作为基准数。
改进2:
当划分的子序列很小的时候(一般认为小于13个元素左右时),我们再使用快速排序对这些小序列排序反而不如直接插入排序高效。因为快速排序对数组进行划分最后就像一颗二叉树一样,当序列小于13个元素时我们再使用快排的话就相当于增加了二叉树的最后几层的结点数目,增加了递归的次数。所以我们在当子序列小于13个元素的时候就改用直接插入排序来对这些子序列进行排序。
int GetMid(int *a, int left, int right)
{
int mid = ((left^right) >> 1) + (left&right); //求平均值
if (a[left] < a[mid])
{
if (a[mid] < a[right])
return mid;
else if (a[left] < a[right])
return right;
else
return left;
}
else
{
if (a[left] < a[right])
return left;
else if (a[mid]>a[right])
return mid;
else
return right;
}
}
int PartSort(int* a,int begin,int end)
{
int key = GetMid(a,begin,end);
swap(a[key],a[end]);
int prev = begin - 1;
int cur = begin;
while (cur<end)
{
if (a[cur] < a[end])
{
prev++;
if (prev != cur)
swap(a[prev], a[cur]);
}
cur++;
}
prev++;
swap(a[prev], a[end]);
return prev;
}
//利用前后指针来快速的实现划分左右子区间
void QuickSort(int *a,int begin,int end)
{
assert(a);
if (begin < end)
{
if (end - begin>13)
{
int div = PartSort(a,begin,end);
QuickSort(a,begin,div-1);
QuickSort(a,div+1,end);
}
else
{
InsertSort(a+begin,end-begin+1);
}
}
}
四、快速排序的非递归实现
利用栈来模拟快速排序递归算法的过程。
int GetMid(int *a, int left, int right)
{
int mid = ((left^right) >> 1) + (left&right); //求平均值
if (a[left] < a[mid])
{
if (a[mid] < a[right])
return mid;
else if (a[left] < a[right])
return right;
else
return left;
}
else
{
if (a[left] < a[right])
return left;
else if (a[mid]>a[right])
return mid;
else
return right;
}
}
int PartSort(int *a,int begin,int end)
{
int key = GetMid(a,begin,end);
swap(a[key],a[end]);
int prev = begin - 1;
int cur = begin;
while (cur < end)
{
if (a[cur] < a[end]) //如果a[cur]小于基准值,则prev++
{
prev++;
if (prev != cur) //如果prev不等于cur,则说明a[prev]大于基准值
swap(a[prev],a[cur]);
}
cur++;
}
prev++;
swap(a[prev],a[end]);
return prev;
}
//
利用栈实现非递归快速排序
void QuickSort(int *a,int begin,int end)
{
assert(a);
stack<int> s;
s.push(end);
s.push(begin);
while (!s.empty())
{
int left = s.top(); //先取左边界
s.pop();
int right =s.top(); //再取右边界
s.pop();
int div = PartSort(a,left,right);
if (div-1>left) //只有子区间的值大于1个的时候才继续划分
{
s.push(div - 1);
s.push(left);
}
if (right>div + 1) //只有子区间的值大于1个的时候才继续划分
{
s.push(right);
s.push(div + 1);
}
}
}