【算法笔记3】快速排序
终于讲到快速排序(quick sort)了
假设有下面这样一串数字,你想要将它们从小到大排序:

现在请用上一个笔记刚刚学到的分而治之思想,来解决这个问题。
回想一下,在上一个笔记中,分而治之的关键两点:
(1)找到基线条件。
(2)找到一种方法,它可以不断使用以缩小问题的规模,使其符合基线条件。
那么排序问题最简单的情况是什么呢?那必须是简单到可以一眼看出顺序的数组呀,大概是这样吧:

那么问题来了,怎样才可以把这个排序问题逐渐转化成这个简单的问题呢?我们可以把这个数字串分成两部分,把这两部分分别排序,最后再合成一个数字串,就是排序后的数字串。在对子串排序时,如果这个子串不符合基准条件,就再把它分成两部分,分别排序,这样不断递归,直到遇到基线条件。
算法原理
对于上面的数字串,随机选择一个数字,称其为基准值(base)。对于上面的数字串,我们可以选择第一个数字3,接下来将3与其余的数字比较,比它大的放在其左边,比它小的放在数组右边,经过一轮比较后,就变成了这样:

好了,现在原来的数组被分成了两部分,分别是基准值3左边的部分和3右边的部分。对于这其中一个子串,随机选择一个基准值,再与子串中其它的数字比较,比基准值小的放在左边,比它大的放在右边,对于另一个子串也执行同样的操作,直至遇到基线条件。

至此,通过几次递归,我们得到了一个有序数组。容易理解的是,不管选择哪个数作为基准值,最后都能得到这个有序数组,只不过递归的次数有可能不同。
快速排序的运行时间
想必你已经发现,快速排序的递归次数与选择的基准值有关。在最糟糕的情况下,快速排序的运行时间为O(n2), 与选择排序一样,比如在每次递归中,选择最大值或者最小值为基准值,但是在平均情况下,快速排序可以达到O(nlogn)。还有一种叫做合并排序的排序算法,它的运行时间也是O(nlogn),和平均情况下的快排一样,但是,由于快排的O(nlogn)中的常量比合并排序小,所以在实际情况中,快排基本上是最好的选择。
需要注意的是,只有在两种算法的大O表示法在一个量级时,比较它们其中的常量才有意义。
练习
POJ 2388 3664
// 2388 #include <iostream> using namespace std; void exchange( int milk[], int id1, int id2) { int tmp; tmp = milk[id1]; milk[id1] = milk[id2]; milk[id2] = tmp; } void quickSort( int milk[], int head, int tail) { int base; int left; if( head < tail ) { base = milk[head]; left = head; for( int i = head+1; i <= tail; i++ ) { if( milk[i] < base ) { left++; exchange(milk, i, left); } } exchange(milk, left, head); quickSort( milk, head, left-1 ); quickSort( milk, left+1, tail ); } } int main() { int milk[10000]; int milk_num; while(cin >> milk_num) { for( int a = 0; a < milk_num; a++) { cin >> milk[a]; } quickSort( milk, 0, milk_num-1); int re = milk[(milk_num-1) / 2]; cout << re << endl; } return 0; }
// 3664 #include <iostream> using namespace std; void exchange(int num[], int idx1, int idx2) { int tmp = num[idx1]; num[idx1] = num[idx2]; num[idx2] = tmp; } void quickSort( int num[], int id[], int head, int tail) { int left; int base; if( head >= tail ) { return; } else { base = num[head]; left = head; for( int a = head + 1; a <= tail; a++ ) { if( num[a] < base ) { left++; exchange(num, a, left); exchange(id, a, left); } } exchange( num, head, left ); exchange( id, head, left ); quickSort( num, id, head, left-1 ); quickSort( num, id, left+1, tail ); } } int main() { int n_cow; int k_cow; int a_id[50010]; int b_id[50010]; int a_vote[50010]; int b_vote[50010]; while( cin >> n_cow && cin >> k_cow ) { for( int a = 0; a < n_cow; a++ ) { a_id[a] = a; b_id[a] = a; cin >> a_vote[a]; cin >> b_vote[a]; } quickSort( a_vote, a_id, 0, n_cow-1 ); for( int b = n_cow - k_cow-1; b >= 0; b-- ) { b_vote[ a_id[b] ] = 0; } quickSort( b_vote, b_id, 0, n_cow-1 ); cout << b_id[n_cow-1] + 1 << endl; /* for( int c = 0; c < n_cow; c++ ) { cout << a_id[c] << " " << a_vote[c] << endl; } cout << "&&&&&&&&&&&" << endl; for( int c = 0; c < n_cow; c++ ) { cout << b_id[c] << " " << b_vote[c] << endl; } */ } return 0; }
参考资料:算法图解》[美] Aditya Bhargava 译者:袁国忠
浙公网安备 33010602011771号