快速排序:就是在一个数组中先选取一个参考值V(基准),并将数组分为小于基准和不小于基准的两部分(快速排序的基本版),并递归将左右部分两部分继续分别快速排序,用到了分治的算法思想,将一个大问题分解成一系列有相同特点或性质的子问题,当子问题仍无法解决时继续递归划分,直到子问题可以被解决为止,这样只要将子问题一一解决,总的问题也就被解决了。
// 对arr[l...r]部分进行partition操作
// 返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template
int __partition(T arr[], int l, int r){
T v = arr[l];
int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
for( int i = l + 1 ; i <= r ; i ++ )
if( arr[i] < v ){
j ++; //这里的j变量代表的是每次交换的大于V的元素的下标,而且j的大小也代表着在当前情况下已经出现了多少的比V小的元素
swap( arr[j] , arr[i] );
}
swap( arr[l] , arr[j]);
return j;
}
// 对arr[l...r]部分进行快速排序
template
void __quickSort(T arr[], int l, int r){
if( l >= r )
return;
int p = __partition(arr, l, r); //分成左右两区间,左边的都小于基准,右边的都不小于基准,函数返回记住的索引值
__quickSort(arr, l, p-1 ); //递归对左右两部分进行快排
__quickSort(arr, p+1, r);
}
template
void quickSort(T arr[], int n){
__quickSort(arr, 0, n-1);
}
基本的快速排序存在的问题是:当数组中有大量重复元素的时候,快排的效率较低,因为这个时候左右部分很不平衡。
template
int _partition2(T arr[], int l, int r){
swap( arr[l] , arr[rand()%(r-l+1)+l] );
T v = arr[l];
// arr[l+1...i) <= v; arr(j...r] >= v
int i = l+1, j = r;
while( i<=j ){ //二路快速排序较一路的代码编写特点有一个是:二路快排用了两个循环整个数组的变量,即这里的i和j
while( i <= r && arr[i] < v ) //退出这个循环的时候i停在不小于基准值V的位置上
i ++;
while( j >= l+1 && arr[j] > v ) 退出这个循环的时候i停在不大于基准值V的位置上
j --;
swap( arr[i] , arr[j] );
i ++;
j --;
}
swap( arr[l] , arr[j]); //交换arr[l]和arr[j]使arr[l]左边部分元素都小于基准值。
return j; //i停在第一个不小于基准值V的元素的位置上,而j停在最后一个小于基准值V的元素的位置上。
}
template
void _quickSort(T arr[], int l, int r){
if( r - l <= 15 ){ //当元素数量只有15个的时候,这时候用插入排序的效率更高一些
insertionSort(arr,l,r);
return;
}
int p = _partition2(arr, l, r);
_quickSort(arr, l, p-1 );
_quickSort(arr, p+1, r);
}
template
void quickSort(T arr[], int n){
srand(time(NULL));
_quickSort(arr, 0, n-1);
}
二路快速排序将相同值的元素尽量平分给了左右部分,使得对有大量重复元素的数组的排序效率有了较大的提高。
基准V | 小于V | 等于V | e | 未遍历到 | 大于V |
void __quickSort3Ways(T arr[], int l, int r){
if( r - l <= 15 ){
insertionSort(arr,l,r);
return;
}
swap( arr[l], arr[rand()%(r-l+1)+l ] ); //选取基准的时候随机选取。
T v = arr[l];
int lt = l; // arr[l+1...lt] < v ,lt的大小为最后一个小于基准值的元素下标值
int gt = r + 1; // arr[gt...r] > v ,gt的大小为第一个大于基准值的元素的下标值
int i = l+1; // arr[lt+1...i) == v ,i的大小始终为当前待判断的元素的下标值
//在设定lt,gt,i的初始值的时候要先保证这时候v三个区间内不存在元素,因为这时候还没开始分出来,下面的while()循环才是选出元素的。
while( i < gt ){
if( arr[i] < v ){
swap( arr[i], arr[lt+1]);
i ++;
lt ++; //别忘了lt++
}
else if( arr[i] > v ){
swap( arr[i], arr[gt-1]);
gt --; //这时候i不需要++,因为这时候交换过来的元素仍是未判断的元素
}
else{ // arr[i] == v
i ++;
}
}
swap( arr[l] , arr[lt] );
__quickSort3Ways(arr, l, lt-1);
__quickSort3Ways(arr, gt, r);
}
template
void quickSort3Ways(T arr[], int n){
srand(time(NULL));
__quickSort3Ways( arr, 0, n-1);
}