算法学习笔记----确定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为当前元素的位置),其时间复杂度为:
![](http://img.my.csdn.net/uploads/201301/29/1359454346_3515.PNG)
时间复杂度为Θ(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;
}
解题思路:
①比较直观的方法是循环从数组中取出一个元素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;
}
相关文章推荐
- 题目2.给出一个算法,它能用O(nlgn)的最坏情况运行时间,确定n个元素的任何排列中逆序对的数目
- [置顶] 算法笔记 //05_有重复元素的排列问题(针对字母排序)
- C++学习笔记26——泛型算法之容器元素排序(sort unique)
- 算法学习笔记----判断集合S中是否存在有两个其和等于x的元素
- 确定n个元素的任何排序中逆序对数量
- 算法学习笔记(二)字符串根据逆序排序
- 算法导论习题---求n个元素任何排列中逆序对的数量
- 算法笔记_158:算法提高 逆序排列(Java)
- 【算法学习笔记】13.暴力求解法01 枚举排列
- 【学习笔记】《STL使用入门教程》第六讲:容器使用时机、算法、删除元素的注意点
- 【算法学习笔记】20.算法设计初步 归并排序 求逆序数
- 【算法学习笔记】13.暴力求解法01 枚举排列
- 算法竞赛入门经典 第三章 学习笔记
- 算法笔记--排列组合
- 【算法导论】学习笔记第一章:算法在计算中的作用
- 数据结构 学习笔记(九):图(下):最小生成树(Prim,Kruskal 算法),拓扑排序 AOV,关键路径 AOE
- 【算法学习笔记】75. 动态规划 棋盘型 期望计算 1390 畅畅的牙签盒(改)
- 算法学习之旅,初级篇(9)--字符串逆序
- 【学习笔记】斯坦福大学公开课(机器学习)题外篇:感知机学习算法
- 初学ML笔记N0.2——生成学习算法