1 快速排序法
通常使用第一个元素作为分界点;
1)i处的元素大于v,直接添加在大于v的后面;
2)i处的元素小于v,则将其和j后面一个位置的元素进行交换;
3)最后,将v和j后面一个位置的元素进行交换。
排序过程:
当i处的元素<e时,交换j+1和i处的元素:
最后,将元素v和j+1处元素交换:
2 随机化快速排序
如上图,这是快速排序的思路。与归并排序相比,归并排序则是直接从中间切割,一分为二;但是快速排序的分割点是我们选定的标定点v,前面我们选择第一个元素作为标定点v。
这样做存在一个问题?
如果我们面对的是一个高度有序的序列,那么就会出现下面这种情况:极端情况就是这个序列已经有序了,我们每次选取的标定点都是第一个,就会退化成下图这样。
面对上面这种情况,我们可以考虑标定点v的选取,我们并不是每次都选择第一个元素作为标定点。而是从序列中随机选择一个点作为标定点,
请注意,这里我们只需要将随机选择的这个点和第一个元素进行交换,就达到了随机化标定点的意图。
partition操作:
int __partition(T arr[], int l, int r) {
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap(arr[l],arr[rand()%(r-l+1)+l]);
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){
swap(arr[j+1],arr[i]);
j++;
}
}
swap(arr[l],arr[j]);
return j;
}
随机化快速排序法:
// 对arr[l...r]部分进行快速排序
template <typename T>
void __quickSort(T arr[],int l,int r){
// if(l>=r)
// return;
if(r-l<=15){ //优化1,小部分数据直接使用插入排序
insertionSort(arr,l,r);
return ;
}
int p=__partition(arr,l,r);
__quickSort(arr,l,p-1);
__quickSort(arr,p+1,r);
}
template <typename T>
void quickSort(T arr[],int n){
srand(time(NULL));
__quickSort(arr,0,n-1);
}
3 双路快速排序
存在大量重复的元素,也会导致 O ( n 2 ) O(n^2) O(n2)。
情况如下:
参照前面的快速排序算法,我们可以知道,不管我们将等于v 的情况放在哪一侧,只要序列中存在大量重复元素,就会出现极度不平衡的现象。出现上图的情况。
新的思路:
使用双路快速排序,如下图,当存在等于v的情况,不需要进行位置的交换,只需要移动指针。
我们注意上图中发生交换时包含了e=v的情况,i、j都包含了等于的情况,这样就会将e=v的数据分到两侧,而不是都归于一侧。如此,可以在一定程度上避免极度不平衡的情况发生。
// 双路快速排序的partition
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
template <typename T>
int _partition2(T arr[], int l, int r){
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
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( true ){
// 注意这里的边界, arr[i] < v, 不能是arr[i] <= v
// 思考一下为什么?
while( i <= r && arr[i] < v )
i ++;
// 注意这里的边界, arr[j] > v, 不能是arr[j] >= v
// 思考一下为什么?
while( j >= l+1 && arr[j] > v )
j --;
if( i > j )
break;
swap( arr[i] , arr[j] );
i ++;
j --;
}
swap( arr[l] , arr[j]);
return j;
}
4 三路快速排序
存在大量等于v的情况,这种方式就比较快。
// 递归的三路快速排序算法
template <typename T>
void __quickSort3Ways(T arr[], int l, int r){
// 对于小规模数组, 使用插入排序进行优化
if( r - l <= 15 ){
insertionSort(arr,l,r);
return;
}
//partition
// 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot
swap( arr[l], arr[rand()%(r-l+1)+l ] );
T v = arr[l];
int lt = l; // arr[l+1...lt] < v
int gt = r + 1; // arr[gt...r] > v
int i = l+1; // arr[lt+1...i) == v
while( i < gt ){//终止条件
if( arr[i] < v ){
swap( arr[i], arr[lt+1]);
i ++;
lt ++;
}
else if( arr[i] > v ){
swap( arr[i], arr[gt-1]);
gt --;
}
else{ // arr[i] == v
i ++;
}
}
swap( arr[l] , arr[lt] );
__quickSort3Ways(arr, l, lt-1);
__quickSort3Ways(arr, gt, r);
}
5 总结
归并排序和快速排序体现了分治算法
的思想:将原问题分割成同等结构的子问题,之后将子问题逐一解决后,原问题也就得到了解决。
归并排序:体现在治上,对于分割,归并排序是一刀切,一分为二,重点在治理数据上,使用了归并操作;
快速排序:体现在分上,对于分割,需要设置一个标定点,对于标定点的选择要仔细考虑,而在治理数据上,快速排序则比较简单,主要就是分情况讨论一下就可以。
求解的典型问题: