快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:首先从待排序序列中选一个关键字作为枢轴,使枢轴左边的所有数据都小于这个枢轴,枢轴右边的数据都大于这个枢轴,再用递归的方法对这两个子序列进行快速排序,最终使得整个序列有序
#include<iostream>
using namespace std;
void Swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
/*****************************************************************************
* 函 数 名 : QuickSort
* 函数功能 : 快速排序
* 输入参数 : int arr[] 待排序数组
int low 数组的左边界值,开始为0
int high 数组的右边界值,开始为length-1
* 输出参数 : 无
* 返 回 值 :
* 调用关系 :
* 记 录
* 1.日 期: 2017年12月03日
* 作 者: zxz
* 修改内容: 新生成函数
*****************************************************************************/
void QuickSort(int arr[], int low, int high){
if(low>=high){ //递归终止条件:只有一个元素时
return;
}
int pivot = arr[low];
int i=low;
for(int j=low+1;j<=high;j++){
//小 小 小 小 小 小 大 大 大 大 大 ? ? ? ? ? ? ? ?
// ^ ^
// i j
//j指示的元素一旦小于等于pivot,i就是自加,指示第一个大于pivot的元素(被交换的元素)
//完成for循环之后,i指示最后一个小于等于pivot的元素
if(arr[j]<=pivot){
i++; //i指示第一个大于pivot的元素
//-->这里确保i最后总是指示最后一个小于等于pivot的元素
if(i!=j)
{
Swap(arr[i],arr[j]);//但只有目标不占坑位时才需要交换
}
}
}
//最后才将pivot放到中间隔离两边的数
//有可能所有的数都比pivot大,这样也不需要交换
Swap(arr[low],arr[i]); //Swap pivot to middle position
//进行分化(partition),递归
QuickSort(arr,low,i-1); //a[i] is the pivot now
QuickSort(arr,i+1,high);
}
int main()
{
int array[]={8 ,1 ,4 ,9 ,6, 3, 5, 2, 7, 0};
for(int i=0;i<sizeof(array)/sizeof(int);i++)
{
cout<<array[i]<<endl;
}
QuickSort(array,0,sizeof(array)/sizeof(int)-1);
cout<<"###############sort done#####################"<<endl;
for(int i=0;i<sizeof(array)/sizeof(int);i++)
{
cout<<array[i]<<endl;
}
return 0;
}
对于分治算法,当每次划分时,算法若都能分成两个等长的子序列时,那么分治算法效率会达到最大。也就是说,基准的选择是很重要的。选择基准的方式决定了两个分割后两个子序列的长度,进而对整个算法的效率产生决定性影响。
最理想的方法是,选择的基准恰好能把待排序序列分成两个等长的子序列。
方法1 固定基准元
如果输入序列是随机的,处理时间是可以接受的。如果数组已经有序时,此时的分割就是一个非常不好的分割。因为每次划分只能使待排序序列减一,此时为最坏情况,快速排序沦为冒泡排序,时间复杂度为Θ(n^2)。而且,输入的数据是有序或部分有序的情况是相当常见的。因此,使用第一个元素作为基准元是非常糟糕的,应该立即放弃这种想法。
方法2 随机基准元
这是一种相对安全的策略。由于基准元的位置是随机的,那么产生的分割也不会总是会出现劣质的分割。在整个数组数字全相等时,仍然是最坏情况,时间复杂度是O(n^2)。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。
方法3 三数取中
引入的原因:虽然随机选取基准时,减少出现不好分割的几率,但是还是最坏情况下还是O(n^2),要缓解这种情况,就引入了三数取中选取基准。
分析:最佳的划分是将待排序的序列分成等长的子序列,最佳的状态我们可以使用序列的中间的值,也就是第N/2个数。可是,这很难算出来,并且会明显减慢快速排序的速度。这样的中值的估计可以通过随机选取三个元素并用它们的中值作为基准元而得到。事实上,随机性并没有多大的帮助,因此一般的做法是使用左端、右端和中心位置上的三个元素的中值作为基准元。显然使用三数中值分割法消除了预排序输入的不好情形,并且减少快排大约5%的比较次数。
举例:待排序序列为:8 1 4 9 6 3 5 2 7 0
左边为:8,右边为0,中间为6
我们这里取三个数排序后,中间那个数作为枢轴,则枢轴为6。
枢轴取中后实现:
#include<iostream>
using namespace std;
void Swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
/*****************************************************************************
* 函 数 名 : QuickSort
* 函数功能 : 快速排序
* 输入参数 : int arr[] 待排序数组
int low 数组的左边界值,开始为0
int high 数组的右边界值,开始为length-1
* 输出参数 : 无
* 返 回 值 :
* 调用关系 :
* 记 录
* 1.日 期: 2017年12月03日
* 作 者: zxz
* 修改内容: 新生成函数
*****************************************************************************/
void QuickSort(int arr[], int low, int high){
if(low>=high){ //递归终止条件:只有一个元素时
return;
}
/*三数中值分割法*/
int mid = (low+high)/2;
//把最大的放到high
if(arr[low]>arr[mid])
{
Swap(arr[low],arr[mid]);
}
if(arr[mid]>arr[high])
{
Swap(arr[mid],arr[high]);
}
//把第二大的放到low
if(arr[low]<arr[mid])
{
Swap(arr[low],arr[mid]);
}
int pivot = arr[low];
int i=low;
for(int j=low+1;j<=high;j++){
//小 小 小 小 小 小 大 大 大 大 大 ? ? ? ? ? ? ? ?
// ^ ^
// i j
//j指示的元素一旦小于等于pivot,i就是自加,指示第一个大于pivot的元素(被交换的元素)
//完成for循环之后,i指示最后一个小于等于pivot的元素
if(arr[j]<=pivot){
i++; //i指示第一个大于pivot的元素
//-->这里确保i最后总是指示最后一个小于等于pivot的元素
if(i!=j)
{
Swap(arr[i],arr[j]);//但只有目标不占坑位时才需要交换
}
}
}
//最后才将pivot放到中间隔离两边的数
//有可能所有的数都比pivot大,这样也不需要交换
Swap(arr[low],arr[i]); //Swap pivot to middle position
//进行分化(partition),递归
QuickSort(arr,low,i-1); //a[i] is the pivot now
QuickSort(arr,i+1,high);
}
int main()
{
int array[]={8, 1, 4, 9, 6, 3, 5, 2, 7, 0};
for(int i=0;i<sizeof(array)/sizeof(int);i++)
{
cout<<array[i]<<endl;
}
QuickSort(array,0,sizeof(array)/sizeof(int)-1);
cout<<"###############sort done#####################"<<endl;
for(int i=0;i<sizeof(array)/sizeof(int);i++)
{
cout<<array[i]<<endl;
}
return 0;
}