介绍
快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
三数取中
我们在 进行快速排序时需要把数据划分为两个部分,需要一个key值。为了避免极端情况(key值是最大或最小值–>划分的两个部分就变成了,一部分数最大最小值,一部分是剩下的元素),我们可以利用三数取中的办法,挑选一个中间值。
int GetMidIndex(int* arr, int left, int right)//三数取中法
{
assert(arr);
assert(left < right);
int mid = left + (right - left) / 2;
if (arr[left] < arr[mid])
{
if (arr[mid] < arr[right])
return mid;
else if (arr[left] > arr[right])
return left;
else
return right;;
}
else //arr[left] > arr[mid]
{
if (arr[mid] > arr[right])
return mid;
else if (arr[right] > arr[left])
return left;
else
return right;
}
}
我们可以用三种方法实现快速排序。
左右指针法
有一个数组a[size],左指针指向下标0的位置,右指针right指向下标size-1的位置。同时选择一个key,key=a[right];left找a[left] > key的下标,right找a[right] < key的下标。left先开始走,当遇到比key大的数时,停下来。right开始往前走,找到比key小的数时,停下来。将left和right分别指向的元素交换。
但是,此时应保证left < right,如果left >= right就说明一次快排结束了、再将left指向的值与key值交换即可。此时的数组呈现出左边的值都比key值小,右边的值都比key值大。一次快排将数组分为两个区间,我们再对每个区间进行上述的排序方式。直到每个小区间已不能再划分。
原理图:
代码实现
//左右指针法
int PartSort1(int* arr, int left, int right)
{
assert(arr);
assert(right > left);
int mid = GetMidIndex(arr, left, right);//三数取中
swap(arr[mid], arr[right]);
int key = arr[right];
int begin = left;
int end = right;
while (begin < end)
{
while (begin < end && arr[begin] <= key)//找大
++begin;
while (begin < end && arr[end] >= key)//找小
--end;
if (begin < end)
swap(arr[begin], arr[end]);
}
swap(arr[begin], arr[right]);
return begin;
}
挖坑法
有一个数组a[size],左指针left指向下标为0的位置,右指针right指向下标为size-1的位置,同时标志key=a[right];首先将key的位置设为一个坑,从left开始,找比key值大的,找到后将a[left]放到坑里,即a[right]=a[left],同时将left的位置再设为坑;再从right开始找比key小的,找到后将a[right]放到上次设的坑里,即a[left]= a[right];直到left>=right,将key值放到现在的坑中即可。此时的数组呈现出左边的值都比key值小,右边的值都比key值大。一次快排将数组分为两个区间,我们再对每个区间进行上述的排序方式。直到每个小区间已不能再划分。
原理图
代码实现
//挖坑法
int PartSort2(int *arr, int left, int right)
{
assert(arr);
assert(left < right);
int key = arr[right];
while (left < right)
{
while (left < right && arr[left] <= key)
++left;
arr[right] = arr[left];
while (left < right && arr[right] >= key)
--right;
arr[left] = arr[right];
}
arr[left] = key;
return left;
}
前后指针法
定义两个指针,prev和cur,在定义key的值,cur从left开始往后走,遇到直到比key小的值,prev++。判断prev和cur是否相等。如果不相等,就讲两个数进行交换。然后继续进行上述步骤。最后一趟排序下来,比key小的,都留在了key的左边,比key大的都在key的右边。
原理图
代码实现
//前后指针法
int PartSort3(int* arr, int left, int right)
{
assert(arr);
assert(left < right);
int mid = GetMidIndex(arr, left, right);
swap(arr[mid], arr[right]);
int cur = left;
int prev = left - 1;
int key = arr[right];
while (cur < right)
{
if (arr[cur] < key && ++prev != cur)
swap(arr[cur], arr[prev]);
++cur;
}
swap(arr[++prev], arr[right]);
return prev;
}
非递归实现快速排序
//非递归实现快速排序
void QuickSortNoR(int* a, int left, int right)
{
stack <int> s;
s.push(right);
s.push(left);
while (!s.empty())
{
int begin = s.top();
s.pop();
int end = s.top();
s.pop();
int div = PartSort3(a,begin,end);
if (div - 1 > begin)
{
s.push(div - 1);
s.push(begin);
}
if (div + 1 < end)
{
s.push(end);
s.push(div + 1);
}
}
}