快速排序
快排基本思想
通过一趟排序将待排序的数据分为两个部分,一部分的数据小于另一部分,再利用递归对两部分的数据进行快速排序,最终使整个序列变为有序。
具体步骤:
- 首先,挑选数据中某个数据作为基准数。
- 将数据分区,比基准数大的数据放在右边,小于基准书的数据放在左边。
- 再对左右分区进行快速排序,直到各区只有一个值。
三种不同的快排思路
a.固定基准数法
取首个元素或最后一个元素作为基准数。
此时取33作为基准数,left指向数据头,right指向数据尾。
right找小于基准数的值,left找大于基准数的值,找到后交换两值。需要注意的是,当选择数据头做基准数时,要先移动right指针扫描,而选择数据尾值做基准数时,先移动left指针扫描。
重复上述步骤,
当左右指针同时指向某元素时,交换基准数与当前指向位置的值,单趟排序结束。再将基准数左边和右边的数据分别快排递归即可。
b.挖坑法
挖坑法是将数据第一个值看做基准数,将此元素拿出数组,此时他所在的位置形成一个坑位,从后向扫描寻找小于基准数的值,直接放入坑位,形成新的坑,再从前向后扫描寻找大于基准数的值,放入当前坑位,形成新坑,如此往复,在最后的坑位中填入基准数。
c.指针法
最右侧数据为key。
定义cur和prev指针指向最左边,cur向后寻找小于key的值。
找到后和prev交换,prev向后移动一位。
最后将key所在的位置和prev最后停下的位置交换。
由于找不到比3小的值,prev(33)和key(3)交换。
再对{22,54,7,79,17,25,33}进行递归快排。
再重复对左右取件递归,完成排序。
快排的效率
在最传统的快排基准数方法中,情况最佳下复杂度为O(nlogn),但当数组有序时,此种方法下,快排效率将退化到冒泡排序即O(N^2)。
快排的几种优化
1.当数据长度满足某情况时,使用插入排序(小区间优化)。
当快速排序的递归深度较深时候,划分区间较小,此时使用快速排序的话,效率较差,所以在较深程度下,使用插入排序更优。
void QuickSort(int* a, int left,int right)
{
if (left >= right)
{
return;
}
// 小区间优化
if (right - left + 1 < 10)
{
InsertSort(a+left, right - left + 1);
}
else
{
int keyi = Partition(a, left, right);
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
}
2.三数取中
当数据为逆序时,基准数选择为最大或最小值,快速排序效率较差,此时可以将基准数换为数组中间位置的值。
int GetMidIndex(int* a, int left, int right)
{
int mid = (left + right) / 2;
// int mid = left + (left + right) / 2;
if (a[left] < a[mid])
{
if (a[mid] < a[right])
{
return mid;
}
else if (a[left] > a[right])
{
return left;
}
else
{
return right;
}
}
else
{
if (a[mid] > a[right])
{
return mid;
}
else if (a[left] < a[right])
{
return left;
}
else
{
return right;
}
}
}
代码实现
固定基准数法:
int Partition(int* a, int left, int right)
{
assert(a);
int mini = GetMidIndex(a, left, right);
swap(a[mini], a[left]);
// 三数取中,防止递归栈溢出
int keyi = mini;
while (left < right)
{
// 右边先走,找小
while (a[right] >= a[keyi] && left < right)
{
--right;
}
// 左边再走,找大
while (a[left] <= a[keyi] && left < right)
{
++left;
}
swap(&a[left], &a[right]);
}
swap(&a[left], a[keyi]);
return left;
}
挖坑法:
int Partion2(int* a, int left, int right)
{
int key = a[left];
int begin = left, end = right;
while (begin < end)
{
while (a[end] >= key && begin < end)
{
end--;
}
a[begin] = a[end];
while (a[begin] <= key && begin < end)
{
begin++;
}
a[end] = a[begin];
}
a[begin] = key;
return begin;
}
指针法
int Partion3(int* a, int left, int right)
{
int keyi = right;
int prev = left;
int cur = left;
while (cur <= right)
{
if (a[cur] <= a[keyi] && prev != cur) // 防止自身交换
{
prev++;
swap(&a[cur], &a[prev]);
}
// 无论cur是否找到了比key小的数据,cur都要继续向下寻找
cur++;
}
swap(&a[keyi], &a[prev]);
return prev;
}