快速排序
总结一下如何写一个快速排序的代码实现。
实现一个快速排序要首先准备好以下几个模块的内容。
- 交换函数
- 随机选择函数
- 分割数组函数
- 快速排序函数
其中第三第四部分数快排的核心代码。
交换函数很简单,实现的是在数组内交换两个位置上的value。注意这里要传入vector的引用,才可以原地修改数组的值。
void swap(vector<int> &data, int p1, int p2)
{
int tmp = data[p1];
data[p1] = data[p2];
data[p2] = tmp;
}
随机选择函数,就是要在数组的一段中随机选择一个数作为排序的基准点,这样做使得快排成为一个随机算法,平均复杂度有理论保证。
顺带总结一下怎么产生随机数。
要取得[a,b)的随机整数,使用(rand() % (b-a))+ a;
要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a;
要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1;
通用公式:a + rand() % n;其中的a是起始值,n是整数的范围。
要取得a到b之间的随机整数,另一种表示:a + (int)b * rand() / (RAND_MAX + 1)。
要取得0~1之间的浮点数,可以使用rand() / double(RAND_MAX)。
int randomInRange(int low, int high)
{
return (rand() % (high-low+1)) + low;
}
分割函数,核心之一,我们首先要随意选择一个基准点,然后把小于这个基准点的都放到左半部,大于这个基准点的都放到右半部。为了保证这个过程可以扫一遍完成,我们需要先把这个基准点放到数组尾部(头部亦可),等到分割完成后再换回来。
// 分割数组
int partition(vector<int> &data, int length, int start, int end)
{
if (length <= 0 || start < 0 || end >= length)
return -1;
int index = randomInRange(start, end);
swap(data, index, end);
int small = start-1;
for(int i = start; i < end; i++)
{
if (data[i] < data[end])
{
small++;
if (small != i)
swap(data, small, i);
}
}
small++; // 此时small代表了中间的索引数
swap(data, small, end);
return small;
}
这里使用了双指针的办法,small这个值很有讲究,他其实是每次遍历时一直指向的是小于基准点的最右边一个数的下标,每次交换的时候,small先自增,再交换就可以保证small这个性质。循环结束后,small再次自增(注意,这里不自增,下面换基准点的时候,小于基准点的数就会被换到最后,这明显错误了),就变成了比基准点大的最左边的那个数,将它和基准点交换,基准点就可以把数组分成小于它本身的左半部,和大于它本身的右半部了。
快排调用,递归调用即可。递归出口就是start与end相等,如果不等,说明待排序的子数组一定有两个以上的数,那么就有必要找基准点分割数组,返回的基准点如果比start大,那么就说明左半部还有没排好的数,同理,如果比end小,这说明右半部还有没排好的数,递归调用。
// 快速排序
void randomQuickSort(vector<int> &data, int length, int start, int end)
{
if (start == end)
return;
int index = partition(data, length, start, end);
if (index > start)
randomQuickSort(data, length, start, index-1); // 排序左半部
if (index < end)
randomQuickSort(data, length, index+1, end); // 排序右半部
}