什么是快速排序
快速排序是一种排序算法 它的思想是:
要对一组无序的的数值序列排序,首先找一个值作为KEY 值(中间值)。
将其余数值小于(大于)KEY值的值放在KEY值位置的左边 大于(小于)KEY值的值放在KEY值的右边。 然后把KEY值左右两边的无序序列按上述逻辑作为递归子问题处理 。 递归出口是递归子序列只有一个值或没有值的时候递归返回到上一层。
上述思想的具体实现方法有三种
一、挖坑法
先把作为KEY值 的最后一个元素保存起来。一个句柄先从前往后寻找大于(小于) 中间值KEY的元素 找到了句柄就停止移动 将这个大于(小于)KEY值的元素值赋到后面句柄的位置上(通常覆盖了作为KEY值的最后一个元素) 另一个句柄从后往前移动寻找大于(小于) 中间值KEY的元素 找到了句柄就停止移动 将这个大于(小于)KEY值的元素值赋到前面句柄的位置上。 如果这时前后句柄没有相遇 则继续上述逻辑 直到前后两个句柄相遇。
两个句柄相遇的位置就是中间值KEY的位置 这个位置之前的元素值比KEY小(大)之后的元素值比KEY大(小)。 这时把之前保存好的KEY值填进去。
代码:
template<typename T>
size_t SortPart2(T* arr, size_t begin, size_t end)
{
assert(arr);
T key = arr[end];
while (begin != end){
while (arr[begin] <= key && begin < end)
++begin;
arr[end] = arr[begin];
while (arr[end] >= key && begin < end)
--end;
arr[begin] = arr[end];
}
if (begin == end)
arr[begin] = key;
return begin;
}
template<typename T>
void _FastSort2(T* arr, size_t begin, size_t end)
{
assert(arr);
if (begin >= end)
return;
T div = SortPart2(arr,begin,end);
_FastSort2(arr, begin, div - 1);
_FastSort2(arr, div + 1, end);
}
图例:
执行一次SortPart2()函数的结果:
二、左右交换法
将待排序列的最后一个元素作为KEY值 一个句柄从前往后寻找大于(小于)KEY值的元素,找到后句柄停止移动 另一个句柄从后往前寻找小于(大于)KEY值的元素,找到后句柄停止移动。然后交换两个元素的值。
如果前后两个句柄没有相遇 则循环执行上述逻辑。
图解:SortPart1()函数执行一次的结果
template<class T>
size_t SortPart1(T* arr, size_t begin, size_t end)
{
assert(arr);
int left = begin;
int right = end;
int key = arr[end];
while (left < right)
{
while (arr[left] <= key && left < right){
++left;
}
while (arr[right] >= key && left < right){
--right;
}
if (left != right)
swap(arr[left], arr[right]);
}
swap(arr[left], arr[end]);
return left;
}
template<typename T>
void _FastSort1(T* arr, size_t begin, size_t end)
{
assert(arr);
if (begin >= end)
return;
T div = SortPart1(arr,begin,end);
_FastSort1(arr, begin, div - 1);
_FastSort1(arr, div + 1, end);
}
三、前后交换法
第三种前后交换法 比较难以理解 但是却可以解决单向链表的快速排序问题。思路是:定义两个句柄 一个句柄cur位置在待排序列头一个元素,另一个句柄prev在待排序列的前一个位置。在前面的句柄cur只要没有走到最后元素的位置就一直向后移动。当前面的句柄prev遇到大于(小于)KEY值的元素时后面的句柄也向前移动一个元素的位置这时前后两个句柄如果没有相遇(cur != prev)则替换前后两个句柄指向的元素。当cur走到最后元素位置时交换++prev位置元素和最后元素的位置。
这种排序的特点是cur任何情况都在向后移动 如果要升序排序的话, 当cur遇到小于KEY值的元素时 prev才会向后移动跟着cur 如果cur遇到的是大于KEY值的元素 则prev不动(此时++prev位置的值大于KEY) 这时当cur再次遇到小于KEY值的元素时交换cur位置和++后的prev位置的元素。
代码:
template<class T>
size_t SortPart3(T* arr, size_t begin, size_t end)
{
assert(arr);
size_t cur = begin;
size_t prev = cur - 1;
int key = arr[end];
while (cur < end){
if (arr[cur] < arr[end] && ++prev != cur)
swap(arr[prev], arr[cur]);
++cur;
}
swap(arr[++prev], arr[end]);
return prev;
}
template<class T>
void _FastSort3(T* arr, size_t begin, size_t end)
{
assert(arr);
if (begin >= end)
return;
T div = SortPart3(arr, begin, end);
_FastSort3(arr, begin, div - 1);
_FastSort3(a``
r, div + 1, end);
}
图示SortPart3()函数调用一次的待排序列变化过程
快速排序的时间复杂度:
快速排序是一种基于“分而治之”思想的算法。他是已知的最快的排序算法。平均时间复杂度为O(N*logN),他的快速主要取决于对待排数据的移动和拷贝相对最少。但是他也是一种不稳定的排序,当基准数选择的不合理的时候他的效率又会变成O(N*N)。
快速排序的最好情况:
快速排序的最好情况是每次都划分后左右子序列的大小都相等,其运行的时间就为O(N*1ogN)。
快速排序的最坏情况:
快速排序的最坏的情况就是当分组重复生成一个空序列的时候,这时候其运行时间就变为O(N*N)
快速排序的平均情况:
平均情况O( N *logN)。
平均时间复杂度(N*logN)的证明。
以这个图为例:每个红色方框为在待排序列种作为KEY值的位置在最后一个的元素 图中位置为执行了一次SortPort()函数后的位置 蓝色长方形代表待排序列
一共15个元素 从第二层开始左边的序列里每一个元素大于右边的序列里每一个元素 递归4层 N个元素也就是logN层 横向一层一层看 每一层n个元素SortPart()函数工作了n次。
所以时间复杂度就是O(N*logN)。
可是如果这种极端情况下:
如图所示蓝色长方形代表待排序列 每个红色方框为在待排序列种作为KEY值的位置在最后一个的元素 图中位置为执行了一次SortPort()函数后的位置 (实际上这个序列已经有序 这里用来模仿接近有序的序列)这种情况下就有N层 每层N次时间复杂度O(N)的平方 。
所以当每次作为KEY值的元素的值在待排序列种相对太小或太大 时间复杂度就会降低成接近于O(N)的平方。
快速排序的优化。
一、加三数取中法。
为了避免每次以待排序列最后一个元素作为KEY值时,万一该值总是比较极端而影响效率。所以 采用三数取中法。 每次从待排序列的首尾和中间三个元素中找大小适中的元素作为KEY值 并把它和最后一个元素位置交换。
二、由于我们采用的是递归方式解决的 每次递归时用开辟新的函数栈帧、参数压栈,递归退出时还要退回詹帧。用于是二差递归 所以递归层数深了以后这种开销会比较大。所以面对递归深层的每一个小的接近有序的待排序列我们可以采用直接插入排序来解决,经过大量实践经验积累,我门把元素个数小于13的定义为用直接插入排序处理的子序列。
优化代码实现:
emplate<class T>
size_t GetMidValue(T *arr, size_t begin, size_t end)
{
size_t mid = (end - begin)/2;
if (arr[begin] < arr[mid]){
if (arr[begin] > arr[end]){
return begin;
}
else if (arr[mid] > arr[end]){
return end;
}
else{
return mid;
}
}
else{
if (arr[begin] > arr[end]){
return begin;
}
if (arr[mid] > arr[end]){
return mid;
}
else{
return end;
}
}
}
template<class T>
size_t SortPart3(T* arr, size_t begin, size_t end)
{
assert(arr);
size_t cur = begin;
size_t prev = cur - 1;
size_t keyindex = GetMidValue(arr,begin,end);
swap(arr[keyindex], arr[end]);
T key = arr[end];
while (cur < end){
if (arr[cur] < key && ++prev != cur)
swap(arr[prev], arr[cur]);
++cur;
}
swap(arr[++prev], arr[end]);
return prev;
}
template<class T>
void _FastSort3(T* arr, size_t begin, size_t end)
{
assert(arr);
if (begin >= end)
return;
if (end - begin + 1 > 13){
T div = SortPart3(arr, begin, end);
_FastSort3(arr, begin, div - 1);
_FastSort3(arr, div + 1, end);
}
else{
InsertSort(arr + begin, end - begin + 1);
}
}
快速排序非递归实现
递归时间的快速排序可以用栈这种数据结构来进行非递归的模拟实现。
template<class T>
size_t GetMidValue(T *arr, size_t begin, size_t end)
{
size_t mid = (end - begin)/2;
if (arr[begin] < arr[mid]){
if (arr[begin] > arr[end]){
return begin;
}
else if (arr[mid] > arr[end]){
return end;
}
else{
return mid;
}
}
else{
if (arr[begin] > arr[end]){
return begin;
}
if (arr[mid] > arr[end]){
return mid;
}
else{
return end;
}
}
}
template<class T>
size_t SortPart3(T* arr, size_t begin, size_t end)
{
assert(arr);
size_t cur = begin;
size_t prev = cur - 1;
size_t keyindex = GetMidValue(arr,begin,end);
swap(arr[keyindex], arr[end]);
T key = arr[end];
while (cur < end){
if (arr[cur] < key && ++prev != cur)
swap(arr[prev], arr[cur]);
++cur;
}
swap(arr[++prev], arr[end]);
return prev;
}
template<class T>
void FastSort3(T* arr, size_t n)
{
assert(arr);
size_t begin = 0;
size_t end = n - 1;
stack<size_t> s;
s.push(end);
s.push(begin);
while (!s.empty()){
size_t left = s.top();
s.pop();
size_t right = s.top();
s.pop();
size_t mid = SortPart3(arr, left, right);
if (mid - 1 > left){
s.push(mid - 1);
s.push(left);
}
if (mid + 1 < right){
s.push(right);
s.push(mid + 1);
}
}
}
//template<class T>
//void FastSort3(T* arr, size_t n)
//{
// assert(arr);
// size_t begin = 0;
// size_t end = n - 1;
// stack<size_t> s;
// s.push(end);
// s.push(begin);
// while (!s.empty()){
// size_t left = s.top();
// s.pop();
// size_t right = s.top();
// s.pop();
// size_t mid = SortPart3(arr, left, right);
// if (right - left + 1 > 13){
// if (mid - 1 > left){
// s.push(mid - 1);
// s.push(left);
// }
// if (mid + 1 < right){
// s.push(right);
// s.push(mid + 1);
// }
// }
// else{
// InsertSort(arr + left, right - left + 1);
// }
// }
//}