分治算法——求逆序对数

分治求逆序对数

问题描述

在Internet上的搜索引擎经常需要对信息进行比较,比如可以通过某个人对一些事物的排名来估计他对各种不同信息的兴趣,从而实现个性化服务。对于不同的排名结果可以用逆序来评价他们之间的差异。考虑1,2,……,n的排列i1,i2,……in,如果其中存在ij,ik使得j<k但是i1>ik,那么就称ij,ik是这个排列的一个逆序。一个排列含有逆序的个数称为这个排列的逆序数。设计和实现统计逆序的分治算法,并对算法进行时间复杂度分析。

解决思路

寻找数组中的逆序对数,暴力的做法就是两层遍历,比较每两组数的大小,但暴力做法的时间复杂度达到了O(n^2),这显然是难以让人接受的。逆序对的定义是在一对数满足ij>ik并且j<k,这样的数对我们应该很熟悉,在任何一个待排数组中很容易见到,那么我们可不可以借鉴排序的方式求出逆序数呢?在归并排序中,我们需要将两个有序数组合并成一个有序数组,那么对于两个有序数组来说,他们各自的逆序数应当为0,那么我们就只需要考虑两个部分组成的逆序数。例如对于两个有序数组a1 = {0,4,6,8,9,10},a2 = {2,3,6,7} ,我们可以仿照归并排序同时对两个数组进行遍历,如果a1[i] <= a2[j],那么就把a1[i]插入到新的有序数组后,如果a1[i] > a2[j],那么a1[i]就能与a2[j]组成逆序对,又因为两个数组都是有序的,a1[i]后面的数都大于等于a1[i],所以a1中剩下的数都能与a2[j]组成逆序对,因此逆序数需要加上a1.length-i,并将a2[j]插入到新的有序数组后,当两个数组都扫描完时,将得到的新数组赋给原数组即可。我们可以用递归的方式求出整个数组的逆序对数。

算法代码

int merge(int a[], int start, int mid, int end)  
{  
	int count = 0;  
	int i = start, j = mid + 1;  
	int *temp = new int[end - start + 1];//用于存储排序后的元素  
	int k = 0;  
	while (i <= mid && j <= end)//归并  
	{  
		if (a[i] > a[j])//若左边元素大于右边元素,则存在逆序对,左边剩余元素可以和右边元素组成逆序对  
		{  
			temp[k++] = a[j++];  
			count += mid - i + 1;  
		}  
		else  
			temp[k++] = a[i++];  
	}  
	while (i <= mid)temp[k++] = a[i++];//合并剩余元素  
	while (j <= end)temp[k++] = a[j++];  
	for (int i = start,j = 0; i <= end; i++,j++)a[i] = temp[j];//将排序后的数组赋给原数组  
	delete[] temp;  
	return count;  
}  
  
int inversion_pairs(int a[], int start, int end)  
{  
	if (start == end)return 0;  
	int count = 0;  
	int mid = (start + end) / 2;  
	count += inversion_pairs(a, start, mid);//左半部分的逆序数  
	count += inversion_pairs(a, mid + 1, end);//右半部分的逆序数  
	count += merge(a, start, mid, end);//跨界的逆序数  
	return count;  
} 

算法分析

本算法借鉴了归并算法的思想,仅在归并排序的过程中加入计算逆序数的语句,因此它的时间复杂度与归并排序一致,为O(nlogn),我用相同的数据将暴力算法与归并算法进行了比较,结果如下:

数据规模归并算法暴力算法
10001ms5ms
50004ms66ms
100008ms263ms
5000031ms6495ms
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值