1、算法概述
分而治之
选取一个主元,将元素分成小于主元的子集和大于主元的子集,在子集中继续选主元,划分
什么是快速排序算法的最好情况?
每次正好中分:T(N) = O(N logN)
void Quicksort(ElementType A[], int N)
{
if (N < 2) return;
pivot = 从A[]中选一个主元;
将S = { A[] \ pivot } 分成2个独立子集:
A1 = { a∈S | a<=pivot } 和
A2 = { a∈S | a>=pivot };
A[] = Quicksort(A1, N1)∪{pivot}∪Quicksort(A2, N2);
}
2、选主元
2.1、选取第一个元素作为主元
T (N) = O(N) + T(N–1)
= O(N) + O(N–1) + T(N–2)
= O(N) + O(N–1) + …+ O(1)
= O(N2)
这种策略对于基本有序的数列时非常糟糕的。
2.2、随机取pivot
rand()函数不便宜啊!,时间复杂度同样大
2.3、取头、中、尾的中位数
ElementType Median3(ElementType A[], int Left, int Right)
{
int Center = (Left + Right) / 2;
if (A[Left] > A[Center])
swap(A[Left], A[Center]);
if (A[Left] > A[Right])
swap(A[Left], A[Right]);
if (A[Center] > A[Right])
swap(A[Center], A[Right]);
//此时A[Left] <= A[Center] <= A[Right]
swap(A[Center], A[Right]); //将基准Pivot藏到右边
return A[Right]; //返回基准Pivot
}
3、子集划分
如果有元素正好等于pivot怎么办?
- 停下来交换
- 不理它,继续移动指针
两种方法各有好坏,举一个极端的例子,假设所有的元素都是相同的。
采用第一种方法,则左边指针移动一步,右边指针移动一步,二者交换,这样下来,每个元素都要交换一遍,有点是每次将数组等分成两份。
采用第二种方法,左边指针一直移动,右边不动,缺点,每次将数组分出一个元素。
二者对比之下,我们采用第一种方法,元素等于pivot,停下来交换。
4、小规模数据的处理
- 快速排序的问题
- 用递归……
- 对小规模的数据(例如N不到100)可能还不如插入排序快
- 解决方案
- 当递归的数据规模充分小,则停止递归,直接调用简单排序(例如插入排序)
- 在程序中定义一个Cutoff的阈值,大于阈值,用快排,小于阈值,用简单排序。
5、算法实现
//核心递归函数
void Qsort(ElementType A[], int Left, int Right)
{
int Pivot, Low, High;
if (Cutoff <= Right - Left+1) //如果序列元素充分多,进入快排
{
Pivot = Median3(A, Left, Right);//选基准
Low = Left; High = Right - 1;
while (1) //将序列中比基准小的移到基准左边,大的移到右边
{
while (A[Low++] < Pivot);
while (A[High--] > Pivot);
Low--;
High++;
if (Low < High)
swap(A[Low], A[High]);
else break;
}
swap(A[Low], A[Right]); //将基准换到正确的位置
Qsort(A, Left, Low - 1); //递归解决左边
Qsort(A, Low + 1, Right); //递归解决右边
}
else Insertion_Sort(A + Left, Right - Left + 1); //元素太少,用插入排序
}
//统一接口
void Quick_Sort(ElementType A[], int N)
{
Qsort(A, 0, N - 1);
}
完整c++代码
#include<iostream>
using namespace std;
typedef int ElementType;
#define Cutoff 2
void Insertion_Sort(ElementType A[], int n)
{
for (int i = 1; i < n; i++)
{
int tmp = A[i];
int j;
for (j = i; j >0 && A[j - 1]>tmp; j--)
A[j] = A[j - 1];
A[j] = tmp;
}
}
//快速排序
ElementType Median3(ElementType A[], int Left, int Right)
{
int Center = (Left + Right) / 2;
if (A[Left] > A[Center])
swap(A[Left], A[Center]);
if (A[Left] > A[Right])
swap(A[Left], A[Right]);
if (A[Center] > A[Right])
swap(A[Center], A[Right]);
//此时A[Left] <= A[Center] <= A[Right]
swap(A[Center], A[Right]); //将基准Pivot藏到右边
return A[Right]; //返回基准Pivot
}
//核心递归函数
void Qsort(ElementType A[], int Left, int Right)
{
int Pivot, Low, High;
if (Cutoff <= Right - Left+1) //如果序列元素充分多,进入快排
{
Pivot = Median3(A, Left, Right);//选基准
Low = Left; High = Right - 1;
while (1) //将序列中比基准小的移到基准左边,大的移到右边
{
while (A[Low++] < Pivot);
while (A[High--] > Pivot);
Low--;
High++;
if (Low < High)
swap(A[Low], A[High]);
else break;
}
swap(A[Low], A[Right]); //将基准换到正确的位置
Qsort(A, Left, Low - 1); //递归解决左边
Qsort(A, Low + 1, Right); //递归解决右边
}
else Insertion_Sort(A + Left, Right - Left + 1); //元素太少,用插入排序
}
//统一接口
void Quick_Sort(ElementType A[], int N)
{
Qsort(A, 0, N - 1);
}
int main()
{
int a[] = {11, 4, 14,6, 1,12, 8,9, 3,13, 7, 10,0 ,5,2,15};
int len = sizeof(a) / sizeof(a[0]);
Quick_Sort(a, len);
for (int i = 0; i < len; i++)
cout << a[i] << " ";
return 0;
}