oj题目 P1750 求逆序对,第二种算法可运行通过

题目描述

第一种解法:暴力求解,直接遍历所有可能的逆序对,再看其是否满足逆序对的要求即可,这种做法只需要两层for循环就可以完成,但是当序列长度很长时其运行出正确结果所花费的时间很长,算法效率很低,oj上面有运行时间限制,用它所给的测试集测试程序,必须在1秒内运行出正确的结果,所以这种解法虽然可以得出正确结果但是在oj上面运行不通过,但是对于初学编程语言的人来说这种解法比较容易理解,如下是用暴力求解的方法来求解此问题的代码 :

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define N 100

int main()
{
	int n;       //表示序列中元素的个数
	int count;     //记录逆序对的数目
	int array[N];
	while (scanf("%d", &n) != EOF)
	{
		for (int k = 0; k < n; k++)
		{
			scanf("%d", &array[k]);
		}
		count = 0;
		for (int i = 0; i < n; i++)
		{
			for (int j = i + 1; j < n; j++)
			{
				if (array[i] > array[j])
				{
					count++;
				}

			}
		}
		printf("%d\n", count);
	}

	return 0;



	
} 


运行结果截图:

 第二种方法:分治法,这道题和用分治法实现二路归并排序的算法思路可以说是一摸一样,对于二路归并排序而言,其需要进行一一比较,而求逆序对也需要进行一一比较,在一一比较的过程中看每一对是否满足逆序对的要求,满足的话逆序对的个数就增一,所以利用这种相似性我们可以将二路归并排序的算法思想应用于求逆序对这道题,对于二路归并排序算法不了解或者对于分治法不了解的话可以看这篇博文,看完之后再来看用归并排序思想实现求逆序对算法的这道程序才可以看得懂,博客链接:http://t.csdn.cn/W8W98

算法思路:

先定义一个函数Numcloseseq,用于求两个相邻的子序列中逆序对的个数,但是这个算法是基于左子序和右子序均为有序序列的基础上的,在左子序和右子序均有序的情况下,若左子序中下标为i的位置处的数大于右子序中下标为j的位置上的数,那么左子序中位置在i位置之后的元素均大于右子序中位置位于j以前的元素,所以这样就不用像暴力求解那样每种可能的逆序对都要进行比较,算法效率就会提高,当序列中的元素个数较多时这种算法的时间效率就会比暴力求解的算法高效很多,所以在这个比较的过程中,我们不仅需要比较记录两个子序列中的逆序对的个数,而且还要将这两个子序列合成一个更大的有序的序列,以方便下一次要用到的时候其还是有序的。

//该函数用于求两个相邻序列逆序对的个数,并将逆序对的个数利用函数返回值返回
void NumCloseSeq(unsigned long *array, unsigned low, unsigned mid, unsigned high, unsigned n)                             //该算法的基础是基于两个子序列有序的基础上进行的,所以还是需要对两个序列进行排序
{
	unsigned k = 0,i,j;
	unsigned right = 0;        //记录右子序中有多少元素已经进入temp所指向的地址之中;
	for ( i = low, j = mid + 1; i <= mid && j <= high;)
	{
		if (array[i] > array[j])
		{
			temp[k] = array[j];
			right++;
			j++;
			k++;
			
		}
		else
		{
			temp[k] = array[i];
			count = count + right;
			i++;
			k++;
		}
	}
	if (i > mid)
	{
		for (; j <= high; j++)
		{
			temp[k] = array[j];
			k++;
		}
	}
	else if (j > high)
	{
		for (; i <= mid; i++)
		{
			temp[k] = array[i];
			k++;
			count = count + right;
		}
	}
	
	for ( i = low, j = 0; i <= high; i++, j++)
	{
		array[i] = temp[j];
	}

}

在定义一个函数用于记录一趟归并排序中逆序对的个数:

//该函数用于求一次归并排序中逆序对的个数
void OneMergeNum(unsigned long *array, unsigned length, unsigned n)
{
	//在该次归并排序中所需要进行比较的子序列的个数为n/length或者n/length+1,还需要注意的是当要进行比较的子序列的个数为奇数时,最后一个子序列不参与比较
	//从左至右两两比较,则需要循环的次数为需要进行比较的子序列的个数除以2向下取整
	unsigned low, mid, high, numsubseq;
	if (n % length)
	{
		numsubseq = n / length + 1;

	}
	else
	{
		numsubseq = n / length;
	}
	for (int i = 0; i < numsubseq / 2; i++)
	{
		low = i * 2 * length;
		mid = low + length - 1;
		high = low + 2 * length - 1;
		if (high >= n)
		{
			high = n - 1;
		}
		NumCloseSeq(array, low, mid, high, n);
	}
}

最后定义一个函数用于记录完成整个归并排序时所有的逆序对的个数,这里需要用到前面定义的两个函数的功能来辅助完成此函数的定义:

//该函数用于完成全部的归并排序,在此过程中记录逆序对的个数,需要用到前面定义的两个函数的功能来辅助完成

void NumMerge(unsigned long *array, unsigned n)
{
	unsigned length;
	for (length = 1; length < n; length = 2 * length)
	{
		OneMergeNum(array, length, n); 
	}
}

完整的程序源代码以及运行结果截图:

程序源代码:

//利用归并排序思想完成求逆序对,排序过程是两两比较的过程,而求逆序对也需要进行两两比较

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>


double count = 0;
unsigned long array[500000] = { 0 };
unsigned long temp[500000] = { 0 };

//该函数用于求两个相邻序列逆序对的个数,并将逆序对的个数利用函数返回值返回
void NumCloseSeq(unsigned long *array, unsigned low, unsigned mid, unsigned high, unsigned n)                             //该算法的基础是基于两个子序列有序的基础上进行的,所以还是需要对两个序列进行排序
{
	unsigned k = 0,i,j;
	unsigned right = 0;        //记录右子序中有多少元素已经进入temp所指向的地址之中;
	for ( i = low, j = mid + 1; i <= mid && j <= high;)
	{
		if (array[i] > array[j])
		{
			temp[k] = array[j];
			right++;
			j++;
			k++;
			
		}
		else
		{
			temp[k] = array[i];
			count = count + right;
			i++;
			k++;
		}
	}
	if (i > mid)
	{
		for (; j <= high; j++)
		{
			temp[k] = array[j];
			k++;
		}
	}
	else if (j > high)
	{
		for (; i <= mid; i++)
		{
			temp[k] = array[i];
			k++;
			count = count + right;
		}
	}
	
	for ( i = low, j = 0; i <= high; i++, j++)
	{
		array[i] = temp[j];
	}

}

//该函数用于求一次归并排序中逆序对的个数
void OneMergeNum(unsigned long *array, unsigned length, unsigned n)
{
	//在该次归并排序中所需要进行比较的子序列的个数为n/length或者n/length+1,还需要注意的是当要进行比较的子序列的个数为奇数时,最后一个子序列不参与比较
	//从左至右两两比较,则需要循环的次数为需要进行比较的子序列的个数除以2向下取整
	unsigned low, mid, high, numsubseq;
	if (n % length)
	{
		numsubseq = n / length + 1;

	}
	else
	{
		numsubseq = n / length;
	}
	for (int i = 0; i < numsubseq / 2; i++)
	{
		low = i * 2 * length;
		mid = low + length - 1;
		high = low + 2 * length - 1;
		if (high >= n)
		{
			high = n - 1;
		}
		NumCloseSeq(array, low, mid, high, n);
	}
}

//该函数用于完成全部的归并排序,在此过程中记录逆序对的个数,需要用到前面定义的两个函数的功能来辅助完成

void NumMerge(unsigned long *array, unsigned n)
{
	unsigned length;
	for (length = 1; length < n; length = 2 * length)
	{
		OneMergeNum(array, length, n); 
	}
}







int main()
{
	unsigned n;
	scanf("%u", &n);

	count = 0;
	for (unsigned int i = 0; i < n; i++)
	{
		scanf("%lu", &array[i]);

	}
	NumMerge(array, n);
	printf("%.0lf\n", count);
}

运行结果截图:

 对于博文中出现的算法题目的实现思路看不懂的都可以私信问我,我看到后都会回复!!!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值