博客内容中的排序以升序排列为例
1. 算法理解
快排的基本思想是:
- 从原始序列Array(集合S)中选取一个元素(称之为枢纽元pivot)
- 将剩余的元素分为两个集合S1和S2,其中S1中元素全都小于或等于pivot,S2中的元素全都大于或等于pivot
- 递归分别对S1和S2执行上述两步
1.1 选取pivot
通常不加考虑地可以直接选取序列的第一个元素作为pivot,但是极端情况下,当原始序列恰好以降序排列时,快排时间复杂度将达到O(N^2)。比较好的方法是“三数中值分割法”,即选取序列的第0个、最后一个和最中间一个这三个元素,按照升序排列(left, center, right),选取中值(center)作为pivot,这样可以一定程序上缓解上述“坏情况”。
1.2 分割策略
快排可以通俗理解为“挖坑”和“填坑”的过程。
- 因为选取了Array[center]作为pivot,所以相当于空出来了一个坑,首先将Array[left]的值填到center位置,则left位置就空出来了(“挖坑”)
- 从最后一个元素开始往前(左)遍历,找到第一个小于或等于pivot的元素位置(标为位置 j),将该位置的元素填到left位置
- 从left开始往后(右)遍历,找到第一个大于或等于pivot元素的位置(标为位置 i),将该位置的元素之值填到位置 j
- 循环执行2、3步,直至i=j,将pivot的值填入位置i中,则pivot左边的元素均小于等于pivot(集合S1),pivot右边的元素均大于等于pivot(集合S2)
- 对集合S1和S2分别递归执行上述四步
2. C++实现
#include <iostream>
using namespace std;
void swap(int *pa, int *pb)
{
int temp = *pa;
*pa = *pb;
*pb = temp;
}
int median3(int a[], int left, int center, int right)
{
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]);
return a[center];
}
void quicksort(int a[], int left, int right)
{
if (left >= right) return;
int center = (left + right) / 2;
int pivot = median3(a, left, center, right);
a[center] = a[left]; //将第0个元素空出来
int i = left;
int j = right;
while (i < j)
{
while (a[j] >= pivot && i < j) j--;
a[i] = a[j];
while (a[i] <= pivot && i < j) i++;
a[j] = a[i];
}
a[i] = pivot;
quicksort(a, left, i-1);
quicksort(a, i+1, right);
}
int main()
{
int n, i;
cout << "请输入序列元素个数" << endl;
cin >> n;
int *a = new int(n);
cout << "请输入序列元素" << endl;
for (i = 0; i < n; i++)
cin >> a[i];
quicksort(a, 0, n - 1);
for (i = 0; i < n; i++)
cout << a[i] << ' ';
cout << endl;
return 0;
}
3. 算法复杂度分析
最优情况下,集合S1和S2的元素数目每次都比较均匀,则时间复杂度为O(NlogN),最坏情况上面已经分析过,平均情况下是O(NlogN),分析过程可参照二叉树。
就空间复杂度来说,主要是递归造成的栈空间的调用,最好情况下递归树的深度为log2n,其空间复杂度也就为O(logn),最坏情况,需要进行n‐1递归调用,其空间复杂度为O(n),平均情况,空间复杂度也为O(logn)。
参考:
1. 《数据结构与算法分析——C语言描述》
2. http://blog.csdn.net/weshjiness/article/details/8660583