白话经典算法系列之九 从归并排序到数列的逆序数对(微软笔试题)

首先来看看原题

微软2010年笔试题

在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序数对。一个排列中逆序的总数就称为这个排列的逆序数。如{2,4,3,1}中,2和1,4和3,4和1,3和1是逆序数对,因此整个数组的逆序数对个数为4,现在给定一数组,要求统计出该数组的逆序数对个数。

计算数列的逆序数对个数最简单的方便就最从前向后依次统计每个数字与它后面的数字是否能组成逆序数对。代码如下:

#include <stdio.h>
int main()
{
	printf("     数列的逆序数对 \n");
	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");    

	const int MAXN = 8;
	int a[MAXN] = {1, 7, 2, 9, 6, 4, 5, 3};

	int nCount = 0;
	int i, j;
	for (i = 0; i < MAXN; i++)
		for (j = i + 1; j < MAXN; j++)
			if (a[i] > a[j])
				nCount++;

	printf("逆序数对为: %d\n", nCount);
}

运行结果如下:

这种方法用到了双循环,时间复杂度为O(N^2),是一个不太优雅的方法。因此我们尝试用其它方法来解决。

在《白话经典算法系列之五归并排序的实现》中观察归并排序——合并数列(1,3,5)与(2,4)的时候:

1.先取出前面数列中的1。

2.然后取出后面数列中的2,明显!这个2和前面的3,5都可以组成逆序数对即3和2,5和2都是逆序数对。

3.然后取出前面数列中的3。

4.然后取出后面数列中的4,同理,可知这个4和前面数列中的5可以组成一个逆序数对。

这样就完成了逆序数对的统计,归并排序的时间复杂度是O(N * LogN),因此这种从归并排序到数列的逆序数对的解法的时间复杂度同样是O(N * LogN),下面给出代码:

//从归并排序到数列的逆序数对
#include <stdio.h>
int g_nCount;
void mergearray(int a[], int first, int mid, int last, int temp[])
{
	int i = first, j = mid + 1;
	int m = mid,   n = last;
	int k = 0;

	while (i <= m && j <= n) //a[i] 前面的数  a[j] 后面的数
	{
		if (a[i] < a[j])
			temp[k++] = a[i++];
		else
		{
			temp[k++] = a[j++];
			//a[j]和前面每一个数都能组成逆序数对
			g_nCount += m - i + 1;
		}
	}

	while (i <= m)
		temp[k++] = a[i++];

	while (j <= n)
		temp[k++] = a[j++];

	for (i = 0; i < k; i++)
		a[first + i] = temp[i];
}
void mergesort(int a[], int first, int last, int temp[])
{
	if (first < last)
	{
		int mid = (first + last) / 2;
		mergesort(a, first, mid, temp);    //左边有序
		mergesort(a, mid + 1, last, temp); //右边有序
		mergearray(a, first, mid, last, temp); //再将二个有序数列合并
	}
}

bool MergeSort(int a[], int n)
{
	int *p = new int[n];
	if (p == NULL)
		return false;
	mergesort(a, 0, n - 1, p);
	return true;
}

int main()
{
	printf("     从归并排序到数列的逆序数对 \n");
	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");    

	const int MAXN = 8;
	int a[MAXN] = {1, 7, 2, 9, 6, 4, 5, 3};

	g_nCount = 0;
	MergeSort(a, MAXN);
	printf("逆序数对为: %d\n", g_nCount);
	return 0;
}

运行结果:

好了,介绍到这里后,相信大家对如何求数列的逆序数对已经有了很好的认识,文章中所用到的“知识迁移”这种方法还是不错的,值得大家掌握。

转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/8029996

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://www.cnblogs.com/captainbed

原文地址:https://www.cnblogs.com/heishanglaoyao/p/10173163.html

时间: 2024-10-23 16:42:39

白话经典算法系列之九 从归并排序到数列的逆序数对(微软笔试题)的相关文章

白话经典算法系列之五 归并排序的实现(转)

归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法(Divide and Conquer)的一个非常典型的应用. 首先考虑下如何将将二个有序数列合并.这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数.然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可. //将有序数组a[]和b[]合并到c[]中 void MemeryArray(int a[], int n, int b[], int m, int c[]) { int

白话经典算法系列之七 堆与堆排序

堆排序与高速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先解说下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是全然二叉树或者是近似全然二叉树. 二叉堆满足二个特性: 1.父结点的键值总是大于或等于(小于或等于)不论什么一个子节点的键值. 2.每一个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆). 当父结点的键值总是大于或等于不论什么一个子节点的键值时为最大堆.当父结点的键值总是小于或等于不论什么一个子节点的键值时为最小堆.下图展示一个最小堆

白话经典算法系列之七 堆与堆排序(转)

堆排序与快速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先讲解下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是完全二叉树或者是近似完全二叉树. 二叉堆满足二个特性: 1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值. 2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆). 当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆.当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆.下图展示一个最小堆: 由于其它几

【转】白话经典算法系列之七 堆与堆排序

堆排序与快速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先讲解下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是完全二叉树或者是近似完全二叉树. 二叉堆满足二个特性: 1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值. 2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆). 当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆.当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆.下图展示一个最小堆: 由于其它几

【白话经典算法系列之十七】 数组中只出现一次的数 其他三次

本文地址:http://blog.csdn.net/morewindows/article/details/12684497转载请标明出处,谢谢. 欢迎关注微博:http://weibo.com/MoreWindows 首先看看题目要求: 数组A中,除了某一个数字x之外,其他数字都出现了三次,而x出现了一次.请给出最快的方法找到x. 这个题目非常有意思,在本人博客中有<位操作基础篇之位操作全面总结>这篇文章介绍了使用位操作的异或来解决——数组中其他数字出现二次,而x出现一次,找出x.有<

三白话经典算法系列 Shell排序实现

山是包插入的精髓排序排序.这种方法,也被称为窄增量排序,因为DL.Shell至1959提出命名. 该方法的基本思想是:先将整个待排元素序列切割成若干个子序列(由相隔某个"增量"的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序. 由于直接插入排序在元素基本有序的情况下(接近最好情况),效率是非常高的,因此希尔排序在时间效率上比前两种方法有较大提高. 以n=10的一个数组49, 38, 65, 97

白话经典算法系列之四 直接选择排序及交换二个数据的正确实现

分类: 白话经典算法系列 2011-08-09 11:15 16682人阅读 评论(29) 收藏 举报 算法面试c 直接选择排序和直接插入排序类似,都将数据分为有序区和无序区,所不同的是直接播放排序是将无序区的第一个元素直接插入到有序区以形成一个更大的有序区,而直接选择排序是从无序区选一个最小的元素直接放到有序区的最后. 设数组为a[0…n-1]. 1.      初始时,数组全为无序区为a[0..n-1].令i=0 2.      在无序区a[i…n-1]中选取一个最小的元素,将其与a[i]交

白话经典算法系列之三 希尔排序的实现

分类: 白话经典算法系列 2011-08-08 11:41 47406人阅读 评论(46) 收藏 举报 算法shell优化c 希尔排序的实质就是分组插入排序,该方法又称缩小增量排序,因DL.Shell于1959年提出而得名. 该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的 元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序.因为 直接插入排序在元素基本有序的情况下(接近最好情

白话经典算法系列之二 直接插入排序的三种实现

分类: 白话经典算法系列 2011-08-06 19:27 52070人阅读 评论(58) 收藏 举报 算法 直接插入排序(Insertion Sort)的基本思想是:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中的适当位置,直到全部记录插入完成为止. 设数组为a[0…n-1]. 1.      初始时,a[0]自成1个有序区,无序区为a[1..n-1].令i=1 2.      将a[i]并入当前的有序区a[0…i-1]中形成a[0…i]的有序区间. 3.      i+