求排列的逆序数与输出前k大的数(分治算法)

分治把一个任务分解成多个规模更小的任务,能显著改善解决问题的效率。归并排序与快速排序是其典型应用,变形问题有排列的逆序数与输出前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);
}

在体会分治思想的同时,回顾归并排序与快速排序。

你可能感兴趣的:(初学算法)