您的位置:首页 > 其它

算法学习笔记----确定n个元素的任何排列中逆序对的数目

2013-01-29 18:11 435 查看
题目:设A[1...n]是一个包含n个不同数的数组。如果在i<j的情况下,有A[i]>A[j],则(i,j)就称为A中的一个逆序对(inversion)。给出一个算法,它能用Θ(nlgn)的最坏情况运行时间,确定n个元素的任何排列中逆序对的数目。

解题思路:

 ①比较直观的方法是循环从数组中取出一个元素k,然后从k之后的元素中找到比k小的元素个数,最后统计所有的个数即为排列中逆序对的数目。从数组中取元素的次数为n,每次取出一个元素后,需要遍历(n-i)次(i为当前元素的位置),其时间复杂度为:



  时间复杂度为Θ(n^2),不符合题目要求。
  ②利用插入排序的方式来解决。对数组用插入排序来做升序排列时,如果有元素需要移动,则每次移动时都相当于是找到了一个逆序对,因此插入队列中移动元素的次数,也就是排列中逆序对的数量。但插入排序的时间复杂度为Θ(n^2),不符合题目要求。
  ③利用归并排序的递归思想来解决,首先对数组的从中间分开,然后分别计算左半部和右半部的逆序对数,假设和为S,最后在合并过程中如果左半部的元素小于右半部的元素,则相当于是找到了一个逆序对,假设合并时找到的逆序对数为S',则总的逆序对数位S+S'。归并排序的时间复杂度为Θ(nlgn),符合题目要求。其代码实现如下:
#include <stdio.h>
#include <errno.h>

#ifndef INT_MAX
#define INT_MAX ((int)(~0U>>1))
#endif

#define ARRAY_SIZE(__s) (sizeof(__s) / sizeof(__s[0]))

static void merge_inversions(int *a, int start, int mid, int end, int *count)
{
int nl = mid - start + 1;
int nr = end - mid;
int sentinel = INT_MAX;
int left[nl + 1], right[nr + 1];
int i, j, k = start;

for (i = 0; i < nl; ++i) {
left[i] = a[k++];
}
/* Set sentinel */
left[i] = sentinel;

for (j = 0; j < nr; ++j) {
right[j] = a[k++];
}
/* Set sentinel */
right[j] = sentinel;

i = j = 0;
for (k = start; k <= end; ++k) {
if (left[i] <= right[j]) {
a[k] = left[i++];
} else {
(*count)++;
a[k] = right[j++];
}
}
}

static void count_versions(int *a, int start, int end, int *count)
{
int mid;

if ((start >= 0) && (start < end)) {
mid = (start + end) /2 ;

count_versions(a, start, mid, count);
count_versions(a, mid + 1, end, count);

merge_inversions(a, start, mid, end, count);
}
}

int main(void)
{
//int source[] = { 7, 5, 2, 4, 6, 1, 5, 3};
int source[] = {2, 3, 8, 6, 1};
int count = 0;

count_versions(source, 0, ARRAY_SIZE(source) - 1, &count);

printf("Reversions: %d.\n", count);

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: