algorithm快速排序
快速排序是排序算法中平均时间复杂度为o(nlog n)的一种算法。主要思想如下:
- 找一个基准数x,这个数可以是数组中存在的任何一个数,一般取q[l],q[r],q[l+r>>1],这三种情况。
- 分集合,左边集合的数都小于等于基准数x,右边集合的数都大于等于x。
不是把小于等于x的数放在x的左半边,大于等于x的数放在x的右半边。这两种说法是有区别的,不要混淆,例如一个数组的所有数都是1这种情况,应该能明白把。
- 分别对左边的集合和右边的集合进行上述步骤,结束条件:左边界等于右边界,即集合中只有一个数了。
看代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100000;
int q[N];
void quick_sort(int q[], int l, int r)
{
if(l>=r) return;
int i=l-1,j=r+1,x=q[l+r>>1];
while(i<j)
{
do i++;while(q[i]<x);//小于x直接跳过 直到找到一个大于等于x的数
do j--;while(q[j]>x);//大于x直接跳过 直到找到一个小于等于x的数
if(i<j) swap(q[i],q[j]); //交换 上面找到的数
}
quick_sort(q,l,j),quick_sort(q,j+1,r);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)scanf("%d",&q[i]);
int l=0,r=n-1;
quick_sort(q,l,r);
for(int i=0;i<n;i++)printf("%d ",q[i]);
return 0;
}
看下实际模拟的情况:
10个数
49 59 88 37 98 97 68 54 31 3
可以看到基准数q[4]=98,模拟一下第一趟排序,左边集合的个数都小于等于98,右边都大于等于98.
初始时 i和 j 都往两边扩展了一位,不管3*7=21,代码中用的do while循环,开始时i++,j–。
这样左边集合就是 49 59 88 37 3 97 68 54 31
右边集合是 98
j的位置最终指向了左边集合的最后一个位置。但是i的最终位置是不确定的,有可能是右边集合的第一个位置,也有可能是左边集合的最后一个位置。 记住j的位置,这是分界点的位置。
关于代码
上面的代码肯定会有很多人不理解这样的写法,如果有同学觉得有问题,可以私信我,后面我会专门写一篇文章,写一下上面代码的出错的各种临界情况。
给定一个长度为n的整数数列,以及一个整数k,请用求出数列从小到大排序后的第k个数。 输入格式 第一行包含两个整数 n 和 k。
第二行包含 n 个整数(所有整数均在1~109范围内),表示整数数列。
输出格式 输出一个整数,表示数列的第k小数。
数据范围 1≤n≤100000, 1≤k≤n
输入样例:
5 3
2 4 1 5 3
输出:
3
快速排序的思想是对集合划分成两个子集合,左边集合都小于等于x,右边集合都大于等于x,如果左边集合的个数恰好为k,那么左边集合的最后一个数就是第k小的数。
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int q[N];
int quick_sort(int l,int r,int k)
{
if(l==r) return q[l];
int i=l-1,j=r+1,x=q[l+r>>1];
while(i<j)
{
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j) swap(q[i],q[j]);
}
int s=j-l+1;
if(k<=s) quick_sort(l,j,k);
else quick_sort(j+1,r,k-s);
}
int main()
{
int n,k;
cin>>n>>k;
for(int i=0;i<n;i++) cin>>q[i];
cout<<quick_sort(0,n-1,k);
}
再看一个题,是上道题的应用。
给定一个由整数组成的集合,集合中的整数各不相同,现在要将他分成两个集合,使得这两个集合交际为空集,并集为原集合,同时在两个子集合的元素个数n1与n2之差的绝对值|n1-n2|尽可能小的情况下,要求他们各自的元素之和s1与s2之差的绝对值|s1-s2|尽可能大。并输出这个差
一组样例
13
1 6 33 18 4 0 10 5 12 7 2 9 3
输出 80
其实思路很简单,根据上一题的做法,我们很简单的可以得到一个集合中第k小的数,这个题目要求集合元素数目差尽可能小,和的差尽可能大。常规情况下,我们自然可以想到将这组数据排序后,取前一半元素和后一半元素构成两个集合,这样数目差最大为1(奇数个数据相差为1,偶数个数相差为0),和的差必然是最大的。
我们学会了上一题的做法后,就不用进行排序了,因为我们可以对集合进行划分,找到第n/2小的数,左边为一个集合,右边为一个集合,时间复杂度就可以降低,期望复杂度为o(n),而排序复杂度最好也是o(nlogn)。
#include<bits/stdc++.h>
using namespace std;
const int N=100000;
int q[N];
void quick_sort(int l, int r,int k)
{
if(l>=r) return;
int i=l-1,j=r+1,x=q[l+r>>1];
while(i<j)
{
while(q[++i]<x) ;
while(q[--j]>x) ;
if(i<j) swap(q[i],q[j]);
}
int s=j-l+1;
if(k<=s) quick_sort(l,j,k);
else quick_sort(j+1,r,k-s);
}
int main()
{
int n;
scanf("%d",&n);
int sum=0,sum1=0;
for(int i=0;i<n;i++)
{
scanf("%d",&q[i]);sum+=q[i];
}
int l=0,r=n-1;
quick_sort(l,r,n/2);
for(int i=0;i<n/2;i++)
sum1+=q[i];
cout<<sum-sum1-sum1;
return 0;
}
//13
//1 6 33 18 4 0 10 5 12 7 2 9 3