快速排序和归并排序是互补的,归并排序将子数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序。而快速排序则是当两个子数组有序时整个数组也就自然有序了。
归并排序:递归调用发生在整个数组处理之前,一个数组被等分为两半。
快速排序:递归调用发生在处理整个数组之后,切分的位置取决于数组的内容。
初步实现如下:
#include <iostream>
#include <algorithm>
void sort(int A[], int lo, int hi);
int partition(int A[], int lo, int hi);
void QuickSort(int A[], int size)
{
std::random_shuffle(A, A + size - 1); //打乱数组
sort(A, 0, size - 1);
}
void sort(int A[],int lo,int hi)
{
if (lo >= hi)
return;
int j=partition(A, lo, hi);
sort(A, lo, j - 1);
sort(A, j + 1, hi);
}
int partition(int A[],int lo,int hi)
{
//将数组切分为A[lo...i-1],A[i],A[i+1...hi]
int i = lo, j = hi + 1; //扫描指针
int a = A[lo]; //切分元素
while(true)
{
while (A[++i] < a); //左侧元素小于切分元素则继续比较下一个
while (A[--j] > a); //右侧元素大于切分元素则比较下一个
if (i > j) //当两个指针相遇
break;
std::swap(A[i], A[j]);
}
std::swap(A[lo], A[j]); //扫描指针相遇后将切分元素放入正确位置
return j;
}
int main()
{
int A[] = { 1,4,6,4,7,4,10,50,88,2,33,56,3,345,66,67,4 };
QuickSort(A, sizeof(A) / 4);
for (auto a : A)
{
std::cout << a << " ";
}
}
random_shuffle()打乱数组的目的:快速排序在切分不平衡时程序可能会极为低效,比如第一次从第一小的元素开始切分,第二次从第二小的元素开始切分。。在快速排序前将数组打乱就是为了避免这种情况。
算法改进:
(1)对于小数组,快速排序比插入排序慢,因此排序小数组时切换到插入排序
将sort中的语句if(hi<=lo) return;
改为
if(hi<=lo+M)
{
//插入排序
return;
}
(2)三取样切分
实际应用中常会出现大量重复元素的数组,一个简单的方法是将数组切分为三部分,分别小于,等于和大于切分元素。
思路:从左到右遍历数组一次,维护一个指针lt使得A[lo...lt-1]中的元素都小于切分元素。一个指针gt,使得A[gt+1...hi]中的元素都大于切分元素。一个指针i使得A[lt...i-1]中的元素都等于切分元素,A[i...gt]中的元素还不确定。
比较时:
A[i]小于切分元素,将A[lt]和A[i]交换,lt和i加一
A[i]大于切分元素,将A[gt]和A[i]交换,gt减一
A[i]等于切分元素,将i加一.。
三取样切分的快速排序如下:
#include <iostream>
#include <algorithm>
void sort(int A[], int lo, int hi);
void QuickSort(int A[], int size)
{
std::random_shuffle(A, A + size - 1); //打乱数组
sort(A, 0, size - 1);
}
void sort(int A[], int lo, int hi)
{
if (lo >= hi) //如果越界
return;
int lt = lo, i = lo + 1, gt = hi;
int v = A[lo]; //切分元素
while (i <= gt)
{
if (A[i] < v)
std::swap(A[i++], A[lt++]);
else if (A[i] == v)
i++;
else {
std::swap(A[i], A[gt--]);
}
}
//
sort(A, lo, lt - 1);
sort(A, gt + 1, hi);
}
int main()
{
int A[] = { 1,4,6,4,7,4,10,50,88,2,33,56,3,345,66,67,4 };
QuickSort(A, sizeof(A) / 4);
for (auto a : A)
{
std::cout << a << " ";
}
}