前言
上一章我们介绍了快排的第一版--hoare版,
今天我们来看一下进阶版:
挖坑法和前后指针法
回顾
基本思想:
-
从待排序的数组中选取一个基准值.
(我们把基准值记为key) -
再将数组分为两部分:
1. 左子数组所有元素小于基准值
2. 右子数组所有元素大于基准值 -
左右子数组再选基准值重复这个过程
基准值的选择:
三数取中法,详情见上一节快速排序
挖坑法
基本思路:
- 找到基准值,并记录下来
将基准值的位置挖个坑位
- 右边r先走,找比基准值小的
找到后将这个值扔在坑中
它本身变成新坑.以此类推
- 当左边和右边相遇时
将记录下来的key放在坑位
图示理解:
定义一个无序数组,并将最左边元素定义为基准值
走完单趟排序后key
左子数都小于key
右子数都大于key
说明key已经到达他的最终位置
接下来我们只需要不断递归
基准值的左右子序就可以使
整个数组有序
挖坑法代码实现
GetMidIndex三数取中参考上一节
int PartSort2(vector<int>& f, int left, int right)
{
int midi = GetMidIndex(f, left, right);
swap(f[left], f[midi]);
int key = f[left];
int hole = left;
while (left < right)
{
//右找小
while (left < right && f[right] >= key)
right--;
f[hole] = f[right];
hole = right;
//左找大
while (left < right && f[left] <= key)
left++;
f[hole] = f[left];
hole = left;
}
f[hole] = key;
return hole;
}
递归调用函数:
void QuickSort(vector<int>& f, int begin, int end)
{
if (begin >= end)
return;
int key = PartSort2(f, begin, end);
QuickSort(f, begin, key - 1);
QuickSort(f, key + 1, end);
}
前后指针法
基本思路:
- 定义两个指针cur和prev
- cur指向第二个元素
- prev指向cur前面的元素
c找比基准值小的值
找到后停下,p向后走一格
再交换c和p指向的值
- 最后c走完数组后:
交换key和prev的值
图示理解:
祖传数组
与挖坑法一样:
走完单趟排序后key
左子数都小于key
右子数都大于key
说明key已经到达他的最终位置
接下来我们只需要不断递归
基准值的左右子序就可以使
整个数组有序
并且前后指针法和挖坑法
得出的左右子数组顺序不一样
说明它们的底层思想是不同的
前后指针法代码实现
GetMidIndex三数取中参考上一节
int PartSort3(vector<int>& f, int left, int right)
{
int midi = GetMidIndex(f, left, right);
swap(f[left], f[midi]);
int cur = left+1;
int prev = left;
int keyi = left;
while(cur<=right)
{
if (f[cur] < f[keyi] && ++prev != cur)
swap(f[cur], f[prev]);
cur++;
}
swap(f[keyi], f[prev]);
keyi = prev;
return keyi;
}
递归调用函数:
void QuickSort(vector<int>& f, int begin, int end)
{
if (begin >= end)
return;
int key = PartSort3(f, begin, end);
QuickSort(f, begin, key - 1);
QuickSort(f, key + 1, end);
}
总结
不管是哪一个版本的快排
算法效率也就是时间复杂度都是一样的
下一期讲解最牛逼的非递归版快排!!!