<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">1.快速排序水中直接在原来的数组上面进行排序的算法,不用另外开辟新的空间。任意选择一个元素,当作主元,把剩下的所有元素,比它大的放在右边,比它小的放在左边。</span>
2.最差时间复杂度是 O(n^2),造成最差时间复杂度的原因是:每一次的partition(划分子数组)都会造成不平衡的划分,都会划分成为n-1 个
元素的子数组和0个元素的子数组,则每一次的运行时间加起来就是n^2。
3.平均时间复杂度是O(n * logn),除开上面的那种不平衡的划分,只要划分的子数组中的元素比例为常数,无论这个比例是多少,
算法的运行时间总是O(n * logn);
下面我们可以得到代码:
quicksort.h
#ifndef QUICKSORT_H
#define QUICKSORT_H
#include <iostream>
#include <vector>
int getPartition(std::vector<int>&, int start, int end);
void quickSort(std::vector<int>&, int start, int end);
void swap(int& first,int& second);
#endif
其中:
1.getPartition()函数接受三个参数,第一个是待排序的数组,第二个是数组的起始下标,第三个是数组的长度(并不是结束下标,按照编程的区间要求),
函数返回一个int值,或者vector<T>::size_type 值,即作为主元的元素(不采用随机化快排的话一般都是数组的第一个或者最后一个元素)经过一个
getPartition()之后,所固定的下标(对于这个元素来说,位置已经不变了)。
2.quickSort()函数也接受三个三个参数,意义和上面一样,这个是一个递归函数。
3.swap() 交换两个元素的位置。
quicksort.cpp
#include "quickSort.h"
void swap(int& first,int& second)
{
int current = first;
first = second;
second = current;
}
int getPartition(std::vector<int>& target, int start, int end)
{
int count = start; //这个整数是用来记录要发生交换的元素的下标,考虑子数组,所以起始值要为数组的第一个元素的下标
if (start == end) //如果数组的长度为1,那么就直接返回起始元素的下标值。结束函数
{
return start;
}
<span style="white-space:pre"> </span>/*这个是关键的部分,在下面解释*/
for (int i = start + 1; i < end; ++i)
{
if (target[start] >= target[i])
{
count += 1;
swap(target[count],target[i]);
}
}
swap(target[start],target[count]);
return count;
}
/*这个就是起始函数,<span style="font-family: Arial, Helvetica, sans-serif;">得到中间下标后,</span><span style="font-family: Arial, Helvetica, sans-serif;">会递归调用,并且要进行数组的长度判断:如果数组的起始大于或者等于数组的结束,那么就直接终止这个函数,不进行递归*/</span>
void quickSort(std::vector<int>& target, int start, int end)
{
if(start < end)
{
int middle = getPartition(target, start, end);
quickSort(target, start, middle);
quickSort(target, middle + 1, end);
}
}
其中,getPartition()这个函数是关键函数,想法是:在对于数组的一遍扫描中:以第一个元素为主元,count表示后面元素小于主元的个数,循环里面的 i
表示现在与主元比较的元素的下标,如果主元大于等于当前元素,那么count值加1,并且交换下标为count 和 i 的两个元素(会出现自己交换自己的情况)
最后再把count下标的元素和 主元交换,然后返回主元所在的下标。
count其实可以从0开始,因为偷懒,我就把它写成了 count = start;正确的写法应该是:
int count = 0;
/*在交换的时候*/
swap(target[count + start],target[i]);
在c++中,我很少用到<=,>=,<,>这些符号,因为用多了迭代器,所以习惯于用 != 。但是处理这样的下标值,还是用前面4个比较方便,
因为考虑到区间问题:半开半闭区间的编程:[start,end),这个性质很好地处理了我们的习惯(从1 开始)和在机器中(从0开始)的现实的矛盾。
上面的循环和递归调用的函数所传入的值,都是遵循着这个原则,
最后是主函数:
#include "quickSort.h"
int main()
{
int a[15] = {13,19,9,5,12,8,7,4,4,21,12,2,6,11,19};
std::vector<int> test(a,a+15);
quickSort(test,0,test.size());
std::vector<int>::size_type size = test.size();
for(std::vector<int>::size_type i = 0; i != size; ++i)
{
std::cout << test[i] << " ";
}
std::cout << std::endl;
return 0;
}
简单调用quickSort这个函数即可得到排序的结果。
最后:关于快排的随机化,改进随机化:(假设元素是互异的)
1.随机化:我们不再以数组的第一个元素作为主元了,而是在剩下的元素里面随机地挑一个元素作为主元,并在开始排序之前把选中的主元
放到数组的第一个元素的位置上,通过这个随机,我们可以使得算法对于所有的输入都能得到较好的期望性能。假如要加代码也只是两行:
一行调用rand函数,另一行交换位置,其他一样。
2.改进随机化:三数取中划分:我们还是在剩下的元素中随机找数字,但是这次找三个数字,并且取他们之中的中位数来作为主元,这个方法只会改变
O(n * logn)的常数项因子。