快速排序及其优化和快速选择算法
本文主要内容是快速排序的代码编写及其优化,还有快速选择算法(在无序的数中寻找第k大或小的元素)
上代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;
#define MAX_N 10000
// arr :用来存储待排序的元素
// n :代表元素数量
// output :输出过程中的调试信息
// DEBUG = 1 开启调试信息
// DEBUG = 0 关闭调试信息
int arr[MAX_N + 5];
int n;
#define DEBUG 1
void output(int,int,int);
// 快速排序: 对arr中 l 到 r 位进行排序
// arr: 待排序数组
// l : 待排序区间起始坐标
// r : 待排序区间结束坐标
void quick_sort(int *arr,int l,int r){
//递归结束条件
if(l >= r) return;
int x = l, y = r, pivot = arr[l];
while(x < y){
//从后向前扫描,找到比基准值小的值,移动到前面
while(x < y && arr[y] >= pivot) y--;
if(x < y) arr[x++] = arr[y];
//从前向后扫描,找到比基准值大的值,移动到后面
while(x < y && arr[x] <= pivot) x++;
if(x < y) arr[y--] = arr[x];
}
//将基准值pivot放到数组x位
arr[x] = pivot;
output(l,x,r);
quick_sort(arr,l,x-1);
quick_sort(arr,x+1,r);
return;
}
void output(int l,int x,int r){
if(!DEBUG) return;
printf("\n待排序区间范围 [%d, %d]\n",l,r);
printf("基准值: %d\n", arr[x]);
char str[30];
int cnt = 1;
for(int i = 1; i < x; i++){
cnt += sprintf(str,"%d ",arr[i]);
}
for(int i = 1; i < l; i++) printf("%d ",arr[i]);
printf("[");
for(int i = l; i <= r; i++){
printf("%d ",arr[i]);
}
printf("]");
for(int i = r + 1; i <= n; i++) printf("%d ",arr[i]);
printf("\n");
for(int i = 0; i < cnt; i++) printf(" ");
printf("^\n");
for(int i = 0; i < cnt; i++) printf(" ");
printf("|\n");
printf("\n");
return;
}
void read_data(){
printf("请输入元素数量: ");
scanf("%d",&n);
printf("请输入%d个整数: \n",n);
for(int i = 1; i <= n; i++){
scanf("%d",arr + i);
}
while(getchar() != '\n');
return;
}
int main(){
read_data();
quick_sort(arr,1,n);
for(int i = 1; i <= n; i++){
printf("%d ",arr[i]);
}
printf("\n");
return 0;
}
下面再来看下对快速排序的优化。
主要分三点优化:
1)单边递归优化--本层函数调用中完成本层的partition后,依旧在本层对左半边进行partition,对右半边进行递归调用,这样一来,函数调用的次数会减半,大大降低了算法的时间复杂度.
2)基准值选择优化--三点取中,对排序区间最左边的值、中间值、最右边的值进行取中值作为基准值,这样会使得算法的时间复杂度稳定在O(nlogn)
3)对partition操作进行优化,利用两个指针分别从前向后、从后向前,交换比基准值小和比基准值大的元素,直到两指针相遇.
看代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
#include <ctime>
using namespace std;
#define TIME(func) ({ \
int b = clock(); \
func; \
int e = clock(); \
(e - b) * 1000.0 / CLOCKS_PER_SEC; \
})
void quick_sort_old(int *arr, int l, int r) {
if (l >= r) return ;
int x = l, y = r, z = arr[l];
while (x < y) {
while (x < y && arr[y] >= z) --y;
if (x < y) arr[x++] = arr[y];
while (x < y && arr[x] <= z) ++x;
if (x < y) arr[y--] = arr[x];
}
arr[x] = z;
quick_sort_old(arr, l, x - 1);
quick_sort_old(arr, x + 1, r);
return ;
}
int select_value(int a, int b, int c){
if(a > b) swap(a,b);
if(a > c) swap(a,c);
if(b > c) swap(b,c);
return b;
}
void quick_sort_new(int *arr, int l, int r){
while(l < r){
//三点取中
int x = l, y = r, z = select_value(arr[l],arr[r],arr[(l + r) >> 1]);
//对partition的优化
do{
while(arr[x] < z) x++;
while(arr[y] > z) y--;
if(x <= y){
swap(arr[x],arr[y]);
x++, y--;
}
}while(x <= y);
quick_sort_new(arr,x,r);
//单边递归优化
r = y;
}
return;
}
#define MAX_N 1000000
int a1[MAX_N + 5], a2[MAX_N + 5];
void test_one(int t){
for(int i = 0; i < MAX_N; i++){
a1[i] = rand();
a2[i] = a1[i];
}
int t1 = TIME(quick_sort_old(a1,0,MAX_N - 1));
int t2 = TIME(quick_sort_new(a2,0,MAX_N - 1));
printf("第%d轮测试,quick_sort_old(%dms), quick_sort_new(%dms)\n", t, t1, t2);
return;
}
void test_random(int n){
printf("测试次数: %d\n每轮测试数据量:%d\n",n,MAX_N);
for(int i = 1; i <= n; i++){
test_one(i);
}
return;
}
int main(){
srand(time(0));
test_random(10);
return 0;
}
看完上述代码,相信你对快速排序算法有了一定的认知,如果我问你,如何在无序的数中选择第k大的元素呢,我们的第一反应可能是对所有元素排序,然后取第k大的元素,仔细想想,有必要吗?
其实,在我们完成一次partition操作之后,我们就可以知道基准值的排名, 假设为ind,我们可以比较ind和k的大小,如果ind和k相等,那么基准值就是第k大的值,如果ind比k大,那么我们只需在左半边递归查找第k大的值即可,如果ind比k小,那么我们只需在右半边递归查找第k-ind大的值即可,因为已经可以排除左半边的元素,而左半边的元素有ind个。
这样说你理解了吗?接下来看代码如何实现.
//arr :待查找数组
//l--r:待查找区间
//k :待查找元素的排名
//在arr数组的l到r区间内,查找排名为k的元素
int quick_select(){
int x = l, y = r, z = arr[l];
while(x < y){
while(x < y && arr[y] >= z) --y;
if(x < y) arr[x++] = arr[y];
while(x < y && arr[x] <= z) ++x;
if(x < y) arr[y--] = arr[x];
}
arr[x] = z;
//ind为当前基准值的排名
int ind = x - l + 1;
if(ind == k) return arr[x];
if(ind > k) return quick_select(arr,l,x-1,k);
return quick_select(arr,x+1,r,k-ind);
}
好了,今天的文章就介绍到这里了。有不对的地方欢迎指正。