快速排序是已知的在实践中最快的排序算法。
它的最坏时间复杂度是O(n^2),平均时间复杂度是O(nlogn)。
快速排序的算法主要的思想在于分部算法(partition),下面的步骤是基于升序排序的假设:
1.选取合适的枢纽。
2.遍历整个数组,将数组中小于或者等于枢纽的元素放在左边,大于或者等于枢纽的元素放在右边。
快排的最坏情况出现在输入的数据是有序的,且是逆序的时候。为了避免最坏情况的发生,枢纽的选择是一个关键。
很明显,枢纽的选择最好就是数组的中值,但是要获得数组中的中值,我们需要扫描一遍数组。为此,我们需要一个近似的做法,使用三数中值来代替枢纽。具体的做法就是选择最左端,最右端和中间的元素,三个元素的中值作为枢纽。
快排的划分算法,每次划分都会使数组越来越接近有序。而
当被排序的是小数组且基本有序的时候,使用插入排序能获得更好的时间复杂度。因为插入排序能避免递归。据统计,当数组大小小于或者等于10的时候使用插入排序来代替快速排序,能提高25%的速度。
有关插入排序,可以看我的这一篇博文:http://blog.csdn.net/sky453589103/article/details/43371913
下面是快速排序的实现 :
#ifndef QSORT_H
#define QSORT_H
#include <vector>
int Partition(std::vector<int> &nums, size_t start, size_t end);
void QSort(std::vector<int> &nums, const size_t &start, const size_t &end) {
if (start >= end || end >= nums.size()) {
return ;
}
int pos = Partition(nums, start, end);
if (pos >= 0) {
QSort(nums, start, pos);
QSort(nums, pos + 1, end);
}
}
void Swap(int &lhs, int &rhs) {
int temp = lhs;
lhs = rhs;
rhs = temp;
}
int Partition(std::vector<int> &nums, size_t start, size_t end) {
if (start > end || end >= nums.size()) {
return -1;
}
int middle = (start + end) >> 1;
if (nums[start] > nums[middle]) {
if (nums[end] < nums[middle]) {
// nums[end] < nums[middle] < nums[start]
Swap(nums[start], nums[middle]);
}
else if (nums[start] > nums[end]) {
// nums[middle] <= nums[end] < nums[start]
Swap(nums[start], nums[end]);
}
}
else if (nums[start] < nums[middle]) {
if (nums[end] > nums[middle]) {
// nums[start] < nums[middle] < nums[end]
Swap(nums[start], nums[middle]);
}
else if (nums[end] > nums[start]) {
// nums[start] < nums[end] <= nums[middle]
Swap(nums[start], nums[middle]);
}
}
int key = nums[start];
while (start < end) {
while (start < end && nums[end] >= key) {
--end;
}
if (start < end) {
nums[start] = nums[end];
}
while (start < end && nums[start] <= key) {
++start;
}
if (start < end) {
nums[end] = nums[start];
}
}
nums[start] = key;
return start;
}
#endif // QSORT_H
需要注意的有两个点:
1.Partition函数中的获得三数中值的方法。在注释中已经解释了做法,这里就不在多说。
2.Partition函数是基于下面的思想:
a)先把第一个元素保存在key中,三数中值的处理保证了第一个元素是三数中值;
b)从后往前找,找到第一个小于key的元素,如果start < end就将end位置的元素放到start的位置
(一开始的保存第一个元素,保证了start位置的元素不会被覆盖),注意,这时候end位置的元素是重复的
c)从前往后找,找到第一个大于key的元素,
如果start < end就将start位置的元素放到end的位置,因为b步骤保证了end位置是重复的,因此end位置的元素不会被覆盖。而这个赋值操作又导致可start位置的元素是重复的。
d)如果start >= end则结束循环,否则继续执行b步骤。
3.在结束循环的时候,start位置的元素左边是小于key,右边是大于key的,而start位置的元素是重复的,因此用key放到start位置,保证数组中元素不变(不是位置不变,内含元素不变)。