分治把一个任务分解成多个规模更小的任务,能显著改善解决问题的效率。归并排序与快速排序是其典型应用,变形问题有排列的逆序数与输出前k大的数。
一、排列的逆序数
1.问题简述
考虑1,2,…,n的排列 i1,i2,…,in,如果其中存在j,k,满足 j < k 且 ij> ik, 那么就称(ij,ik)是这个排列的一个逆序。一个排列含有逆序的个数称为这个排列的逆序数。
现给定1,2,…,n的一个排列,求它的逆序数。
输入
第一行是一个整数n,表示该排列有n个数(n <= 100000)。
第二行是n个不同的正整数,之间以空格隔开,表示该排列。
输出
输出该排列的逆序数。
样例输入
6
2 6 3 4 5 1
样例输出
8
注意结果可能超过int的范围,需要用long long存储。
2.问题分析
把序列分成前后两部分,则
整个序列的逆序数=左序列的逆序数+右序列的逆序数+左右间相互形成的逆序数
与归并排序的思想完全一致,在归并排序的基础上添加计数逆序数的功能即可。
3.源代码
#include
using namespace std;
long long cnt=0; //注意题目提示
void MergeSort(int a[],int s,int e,int temp[]);//归并排序
void Merge(int a[],int s,int m,int e,int tem[]);//合并分别排序后的左右区间
//统计逆序数也在该区间
int main()
{
int n,a[100000],temp[100000];
cin>>n;
for (int i=0;i<n;++i)
cin>>a[i];
MergeSort(a,0,n-1,temp);
cout<<cnt<<endl;
}
void MergeSort(int a[],int s,int e,int temp[])
{
if(s<e)
{
int m=s+(e-s)/2;
MergeSort(a,s,m,temp); //排序左区间
MergeSort(a,m+1,e,temp);//排序右区间
Merge(a,s,m,e,temp); //合并
}
}
void Merge(int a[],int s,int m,int e,int temp[])
{
int p1=s,p2=m+1,pt=0;//左、右、临时序列 的下标
while(p1<=m&&p2<=e)
{
if(a[p1]<a[p2])
temp[pt++]=a[p1++];
else
{
temp[pt++]=a[p2++];
cnt+=m-p1+1;//计数+,左序列p1右侧的数必然为逆序,
//与归并排序唯一的区别
}
}
//处理左右部分的剩余序列
while(p1<=m)
temp[pt++]=a[p1++];
while(p2<=e)
temp[pt++]=a[p2++];
//放回排序后的序列
for(int i=0;i<e-s+1;++i)
{
a[s+i]=temp[i];
}
}
二、输出前k大的数
1.问题简述
给定一个数组,统计前k大的数并且把这k个数从大到小输出。
输入
第一行包含一个整数n,表示数组的大小。n < 100000。
第二行包含n个整数,表示数组的元素,整数之间以一个空格分开。每个整数的绝对值不超过100000000。
第三行包含一个整数k。k < n。
输出
从大到小输出前k大的数,每个数一行。
样例输入
10
4 5 6 9 8 7 1 2 3 0
5
样例输出
9
8
7
6
5
2.问题分析
目标明确,不使用直接排序再输出的方法,采用分治求解。与快速排序思想一致:先选定某个值k1=a[0],进行一次序列调整后,计算出位于k1及之后的数的个数num(不小于k1的数的个数),如果
(1)num==k,即num个数恰好与所需的k个数一样多,结束计算;
(2)num>k,意味着k1右侧的数多于k个了,需要再次再右侧进行序列调整;
(3)num
在快排里,第(2)、(3)步都是需要执行的,而本例中只选择其中之一执行,且快排中是没有(1)中的结束条件的。
3.源代码
#include
#include
using namespace std;
void lagerst_k(int a[],int s,int e,int k);//在快速排序的基础上增加参数 k
int main()
{
int n,a[100000],k;
cin>>n;
for(int i=0;i<n;++i)
cin>>a[i];
cin>>k;
lagerst_k(a,0,n-1,k);//将最大的k项放在序列最后
sort(a+n-k,a+n);//排序最大的k项
for(int i=n-1;i>n-1-k;--i)
{
cout<<a[i]<<endl;
}
return 0;
}
void lagerst_k(int a[],int s,int e,int k)
{
if(s>=e)
return;
int k1=a[s];
int i=s,j=e;
while(i!=j)
{
while(j>i&&a[j]>=k1)
--j;
swap(a[i],a[j]);//从后往前,比k1小的往前放
while(i<j&&a[i]<=k1)
++i;
swap(a[i],a[j]);//从前往后,比k1大的往后放
}
//a[i]=k1;
//与快排的区别部分
int num=e-i+1;
if(num==k)
return;
if(num<k)
lagerst_k(a,s,i-1,k-num);
if(num>k)
lagerst_k(a,i+1,e,k);
}
在体会分治思想的同时,回顾归并排序与快速排序。