前边我们介绍了快速排序的两种方法:Hoare法和挖坑法,今天我们介绍第三种办法:前后指针法。
快排时间复杂度:O(NlogN) ,快速排序的空间复杂度为O (log n),其中n为待排序数组的长度。 这是因为快速排序是一种原地排序算法,它通过在原始数组上进行交换和划分操作来实现排序,而不需要额外的空间来存储临时数据。 在每一次递归调用中,快速排序只需要使用O (log n)的额外空间来保存递归调用的栈空间。
前后指针法:引入prev和cur指针,用来表示下标。prev指针初始为begin位置,cur指针初始为prev+1位置。然后全程由cur往后找小,当cur找到小时,prev就朝后挪动一位,然后交换cur位置和prev位置的值。交换后,cur继续往后找小,直到cur越界时结束。最后交换prev位置的值和基准值。
前后指针法的妙处:cur往后找小,找到小后交换arr[cur]和arr[++prev],如图可以看出,交换二者之后,cur和prev指针之间间隔的数一定是比基准值大的数(因为cur是在找小!)。当最后cur越界之后,prev指针往后的数全都是比基准值大的数,最后交换prev和基准值,那么此时基准值就处于中间位置。
那么接下来我要上代码了:
同样,引出“三数取中”法来进行优化:
//定义一个三数取中函数,取首尾中三个元素的中间值
//这样可以有效的提高效率
//避免每次单趟排序时的begin元素都是最大或最小
int GetMidi(int* arr, int begin, int end)
{
int midi = (begin + end) / 2;
if (arr[begin] < arr[end])
{
if (arr[midi] >= arr[begin] && arr[midi] <= arr[end])
return midi;
if (arr[midi] >= arr[end])
return end;
if (arr[midi] <= arr[begin])
return begin;
}
else
{
if (arr[midi] >= arr[end] && arr[midi] <= arr[begin])
return midi;
if (arr[midi] >= arr[begin])
return begin;
if (arr[midi] <= arr[end])
return end;
}
}
然后我们进行单趟排序:
int SingleSort_Pointer(int* arr, int begin, int end)
{
//prev指针定义在begin位置,cur定义在prev后面一个位置
int prev = begin;
int cur = prev + 1;
//取定基准值
int key = arr[begin];
//当cur越界时结束循环
while (cur <= end)
{
//cur找小,当cur找到小后,就和prev下一个位置互换
//一定是和prev下一个位置换
//当然这里可能出现cur和prev相同的情况,自身互换也没啥问题,可优化可不优化
if (arr[cur] < key)
{
prev++;
Swap(&arr[prev], &arr[cur]);
}
//cur继续往下找小
cur++;
}
//最后交换prev的值和基准值,再返回此时基准值的下标
Swap(&arr[prev], &arr[begin]);
return prev;
}
最后再进行左右区间的递归:
void QuickSort_Pointer_incline(int* arr, int begin, int end)
{
if (begin >= end)
return;
int midi = GetMidi(arr, begin, end);
Swap(&arr[begin], &arr[midi]);
int keyi = SingleSort_Pointer(arr, begin, end);
QuickSort_Pointer_incline(arr, begin, keyi - 1);
QuickSort_Pointer_incline(arr, keyi + 1, end);
}
总结:从前面三种快排方法来看,快排其实很类似于二叉树结构:每次单趟排序后,又递归左区间和右区间,(类似左子树和右子树)。其核心思想在于递归!当然我们写了这么多发现,递归的快排是不是好像也就那样,但是难的是非递归快排!既然如此,那等后面,我在来展示一波非递归快排,先提示一波:借用栈来实现类似递归!