左右指针法(hoare版本)
这个方法就是在数组的首与尾设置左右指针,并在首处设置keyi
接下来移动右指针,如果当前指针指向的数值小于keyi的数值,则--right,左指针同理,如果left的值大于keyi,++left。如果达成条件,则交换left跟right的值,直到left跟right相遇。
当单趟结束时,keyi的值就会到达它最终的位置
此时数组已经是相对有序的状态,我们接下来递归这个函数,直到区间不存在或者只剩下一个值,此时数组将是有序状态。
接下来是具体的代码实现
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void QuickSort(int* a, int left, int right)
{
//如果区间不存在或只剩一个值,则返回上一层
if (left >= right)
return;
int keyi = left;
int begin = left, end = right;
while (left < right)
{
while (left < right && a[right] >= a[keyi])
right--;
while (left < right && a[keyi] >= a[left])
left++;
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
//[begin,keyi - 1] keyi [keyi + 1, end]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
这是最淳朴的快排代码,时间复杂度是O(N*logN)
我们可以设想一下,在一个巨大的数组面前,如果开始时数组相对有序,则很有可能是最坏情况 ,可以理解为单趟下keyi只移动了很小一段距离,如此下来,函数将递归非常深的层数,此时代码很有可能崩溃,为了解决这种情况,又分出了两种方法,随机数法以及三数选中。
随机数法相对简单,我直接展示代码
void QuickSortRand(int* a, int left, int right)
{
if (left >= right)
return;
int begin = left, end = right;
//把randi的距离限制在区间内
int randi = rand() % (right - left);
randi += left;
Swap(&a[randi], &a[left]);
int keyi = left;
while (left < right)
{
//right找小
while (left < right && a[keyi] <= a[right])
--right;
//left找大
while (left < right && a[keyi] >= a[left])
++left;
Swap(&a[left], &a[right]);
}
Swap(&a[left], &a[keyi]);
keyi = left;
QuickSortRand(a, begin, keyi - 1);
QuickSortRand(a, keyi + 1, end);
}
这样的话keyi在数组相对靠中的概率将大大增大,快排效率也会有些许提高
三数靠中法相对麻烦,我们需要编写一个GetMid函数,来取得left,right,mid三数中在数组中代表中间的值,以下是代码实现
int GetMid(int* a, int left, int right)//三数取中
{
int mid = (left + right) / 2;
if (a[mid] < a[left])//mid至少第二小
{
if (a[mid] > a[right])//a[right]最小
{
return mid;
}
else//a[mid]最小
{
if (a[right] > a[left])
return left;
else
return right;
}
}
else//a[mid]>=a[left]
{
if (a[left] > a[right])
{
return left;
}
else
{
if (a[mid] > a[right])
{
return right;
}
else {
return mid;
}
}
}
}
接下来介绍前后指针法,
前后指针法相对于左右指针法的最大优势是实现相对简单,我将结合代码进行讲解
void QuickSort2(int* a, int left, int right)
{
if (left >= right)
return;
int prev = left, cur = left + 1;
int keyi = left;
while (cur <= end)
{
if (a[cur] < a[keyi] && ++prev != cur)
Swap(&a[prev], &a[cur]);
cur++;
}
Swap(&a[keyi], &a[prev]);
keyi = prev;
QuickSort2(a, left, keyi - 1);
QuickSort2(a, keyi + 1, right);
}
前后指针版最大的优势就是代码简洁了不少,效率方面并无太大差异。