Algorithm: inversion
inversion就是逆序对
【解法一】
暴力方法最直接,也最容易想到,两层for循环就可以算出来逆序数:每遇到一个元素回头遍历寻找比其大的元素个数即可,当然向后寻找比其小的元素个数也可以,复杂度为O(n^2),代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
int sum = 0; for(int i = 0; i < size; ++i) { for(int j = i+1; j < size; ++j) { if(arr[i] > arr[j]) { ++sum; } } } return sum; |
【解法二】
这种方法最初见于《算法导论》,这里先附上《算法导论》2-4关于逆序对的几点讨论:
1) 如果数组的元素取自集合[1,2,…,n],那么,怎样的数组含有最多的逆序对?它包含多少个逆序对?
析:很容易想到,当一个数列为逆序时,即[n,n-1,n-2,…,1],逆序数是最多的,是多少?当n=1,inversion=0;n=2,inversion在原来基础上增加1;n=3,inversion增加2…n=n时,inversion增加n-1,所以总的inversion为等差求和n(n-1)/2;也可以这样理解,数列[n,n-1,n-2,…,1]中任取两个数皆为逆序对,故逆序数为Cn2,即n(n-1)/2。
2) 插入排序的运行时间与输入数组中逆序对的数量之间有怎样的关系?
析:我们知道插入排序的程序如下
1
2
3
4
5
6
7
8
9
|
for(int i = 1; i < n; ++i) { int key = arr[i]; for(int j = i; j > 0 && arr[j-1] > key; --j) { arr[j] = arr[j-1]; } arr[j] = key; } |
可以看出,每次内层循环都尝试将当前元素放在“合适”的位置上,所以对于内层循环的每一次迭代,都是在消除当前元素相对之前元素所对应的一系列逆序对,故总的循环便是消除数列所有的逆序对;
3) 给出一个算法,它能用O(nlgn)的最坏运行时间,确定n个元素的任何排列中逆序对的数目。(提示:修改合并排序)
《算法导论》在这里直接提示修改合并排序,所以自然就是用到分治归并的思想,那么逆序对是出现在什么时候?出现多少呢?
我们知道归并排序是通过分治,然后归并两个有序的序列成一个有序序列。现假设两段有序序列分别是[beg,mid]和[mid+1,end],在归并过程中(i,j分别为两段序列的下标),如果a[i]<a[j],则不会产生逆序对;但当a[i]>a[j]时,就出现逆序对了,出现了多少?既然[beg,mid]是有序的,那么[i-mid]序列就都能与a[j]构成逆序对,故:mid-i+1
复杂度O(nlgn),代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
#include<iostream> using namespace std; /* 归并求逆序对数, arr 存储最终有序结果 * 在函数外申请一个临时数组作为参数传入 * 避免递归不断创建临时数组的开销 */ int Merge(int * arr, int beg, int mid, int end, int * tmp_arr) { memcpy(tmp_arr+beg,arr+beg,sizeof(int)*(end-beg+1)); int i = beg; int j = mid + 1; int k = beg; int inversion = 0; // 合并过程中的逆序数 while(i <= mid && j <= end) { if(tmp_arr[i] <= tmp_arr[j]) { arr[k++] = tmp_arr[i++]; }else { arr[k++] = tmp_arr[j++]; inversion += (mid - i + 1); } } while(i <= mid) { arr[k++] = tmp_arr[i++]; } while(j <= end) { arr[k++] = tmp_arr[j++]; } return inversion; } int MergeInversion(int * arr, int beg, int end, int * tmp_arr) { int inversions = 0; // 记录倒序数 if(beg < end) { int mid = (beg + end) >> 1; inversions += MergeInversion(arr, beg, mid, tmp_arr); inversions += MergeInversion(arr, mid+1, end, tmp_arr); inversions += Merge(arr, beg, mid, end, tmp_arr); } return inversions; } /* 测试序列 :answer: 10 */ int testPoint[10] = { 1, 4, 2, 9, 48, 15, 13, 44, 6, 90 }; void main() { int arrcopy[10]; // 临时数组 memcpy(arrcopy,testPoint,sizeof testPoint); printf("the num of inversions is: %d\n", MergeInversion(testPoint,0,9,arrcopy)); } |
【解法三】
显得最有技术含量的算法来了。一般来讲,在解决数列的区间问题上,线段树有很高的出镜率。逆序数便是一个“区间和”的问题:对于数列中的每个元素,它对应的逆序数便是之前序列中大于该元素的元素个数和。
先说点题外话,来引出为什么要用线段树,热热身。
线段树这种数据结构有什么用?我们先来考虑一下下面的需求(全部要求在LogN时间内完成):如何知道一个点在一个点集里的大小“排名”?很简单,开一个点数组,排个序,再二分查找就行了;如何在一个点集内动态增删点?也很简单,弄个平衡树就行了(本来平衡树比线段树复杂得多,但自从世界上有了STL set这么个好东东,就……^_^)那如果我既要动态增删点,也要随时查询到一个点的排名呢?那对不起,可能就要出动到我们的“点树”(线段树)了。
其实现原理很简单:每当增加(或删除)一个大小为X的点时,就在树上添加(或删除)一条(X,MaxLen)的线段(不含端点),当要查询一个点的排名时,只要看看其上有多少条线段就可以了。
说到这,有人可能会有疑问了,我们明明是要求逆序对数,这和一个点在一个点集合里面的“排名”有什么关系呢?我要说,怎么没关系!我们在求逆序对的时候,尤其是解法一,暴力求解的原理不就是对数组中的每个元素a[i],检查该元素(即a[i])前面的元素中,有多少个元素比它大,即有多少个逆序对是以元素a[i]收尾的。比如逆序对(a[1], a[i]),(a[3], a[i])等等。其实这不就是在计算元素a[i]的排名嘛。。。只不过暴力的方法是遍历一次去求元素a[i]的排名,其时间复杂度是O(N)。现在我们有了新式武器(线段树),我们去计算元素a[i]的排名的时候,时间复杂度降到O(logn)了,你说我们有没有必要学好线段树~!~当然了,树状数组和线段树非常类似,也可以解决这个问题。
暴力法AC的代码中时间复杂度是O(N^2),主要是集中在最开始计算C的代码: for(i=0;i<N;++i)
{
cin>>Seq[i];
for(j=0;j<i;++j)
if(Seq[j]>Seq[i]) ++C;
}针对这个循环,我们可以用线段树改进这个过程,使其复杂度降到O(nlogn).为了统计C,每次输入a[i]时,我们只需要计算当前线段树中[a[i]+1,n-1]中存在的数目。
#include<iostream>
using namespace std;
/* 线段树求逆序数 */
#define BORDER 100 // 设测试数列最大不超过100
struct Node // 线段树
{
int left;
int right;
int counter;
}segTree[4*BORDER];
/* 构建线段树 根节点开始构建区间[lef,rig]的线段树*/
void construct(int index, int lef, int rig)
{
segTree[index].left = lef;
segTree[index].right = rig;
if(lef == rig) // 叶节点
{
segTree[index].counter = 0;
return;
}
int mid = (lef+rig) >> 1;
construct((index<<1)+1, lef, mid);
construct((index<<1)+2, mid+1, rig);
segTree[index].counter = 0;
}
/* 包含x的区间的值都+1 即对包含x的logn个节点的路径进行更新*/
void insert(int index, int x)
{
++segTree[index].counter;
if(segTree[index].left == segTree[index].right)
{
return;
}
int mid = (segTree[index].left + segTree[index].right) >> 1;
if(x <= mid)
{
insert((index<<1)+1, x);
}else
{
insert((index<<1)+2, x);
}
}
/* 查询点x的逆序数,即查询[x,BORDER]的区间和 */
int query(int index, int left, int right)
{
if(segTree[index].left == left && segTree[index].right == right)
{
return segTree[index].counter;
}
int mid = (segTree[index].left+segTree[index].right) >> 1;
if(right <= mid)
{
return query((index<<1)+1,left,right);
}else if(left > mid)
{
return query((index<<1)+2,left,right);
}
return query((index<<1)+1,left,mid) + query((index<<1)+2,mid+1,right);
}
/* 测试序列 :answer: 10 */
int testPoint[10] = {
1, 4, 2, 9, 48,
15, 13, 44, 6, 90
};
void main()
{
construct(0,0,100); // 构建[0,100]线段树
int reverseSum = 0; // 记录逆序对数
for(int i = 0; i < 10; ++i) // 查询当前逆序数
{
reverseSum += query(0,testPoint[i],BORDER);
printf("num of reverse order is: %d\n", reverseSum);
insert(0,testPoint[i]);
}
}
Algorithm: inversion的更多相关文章
- HDU 1394 Minimum Inversion Number ( 树状数组求逆序数 )
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1394 Minimum Inversion Number ...
- OpenCASCADE Root-Finding Algorithm
OpenCASCADE Root-Finding Algorithm eryar@163.com Abstract. A root-finding algorithm is a numerical m ...
- HDU 1394 Minimum Inversion Number(最小逆序数/暴力 线段树 树状数组 归并排序)
题目链接: 传送门 Minimum Inversion Number Time Limit: 1000MS Memory Limit: 32768 K Description The inve ...
- Inversion Sequence(csu 1555)
Description For sequence i1, i2, i3, … , iN, we set aj to be the number of members in the sequence w ...
- ACM: 强化训练-Inversion Sequence-线段树 or STL·vector
Inversion Sequence Time Limit:2000MS Memory Limit:262144KB 64bit IO Format:%lld & %llu D ...
- ACM Minimum Inversion Number 解题报告 -线段树
C - Minimum Inversion Number Time Limit:1000MS Memory Limit:32768KB 64bit IO Format:%I64d &a ...
- HDU-Minimum Inversion Number(最小逆序数)
Problem Description The inversion number of a given number sequence a1, a2, ..., an is the number of ...
- Evolutionary Computing: 3. Genetic Algorithm(2)
承接上一章,接着写Genetic Algorithm. 本章主要写排列表达(permutation representations) 开始先引一个具体的例子来进行表述 Outline 问题描述 排列表 ...
- STL or 线段树 --- CSU 1555: Inversion Sequence
Inversion Sequence Problem's Link: http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1555 Mean: 给你一 ...
随机推荐
- MySQL---SQL记录
MySQL在已有字段后追加相应的值值 需求:在MySQL中的topic表中,需要将每一个cover字段后添加上: -400.webp update topic set cover = CONCAT(c ...
- 2017.2.21 Java中正则表达式的学习及示例
学习网站:菜鸟教程 http://www.runoob.com/java/java-regular-expressions.html 1 正则表达式的基本使用 (1)类 正则表达式并不仅限于某一种语言 ...
- 转: https 加密通信流程
https 加密通信流程当用户在浏览器中输入一个以https开头的网址时,便开启了浏览器与被访问站点之间的加密通信.下面我们以一个用户访问https://qbox.me为例,给读者展现一下SSL/TL ...
- 用PROXYCHAINS实现SSH全局代理
NUX下可以实现SSH全局代理的软件有tsocks和proxychains两种,但是个人感觉proxychains要更加稳定简单. $ yum install proxychains # vim /e ...
- vue中使用key管理可复用的元素
1.概述 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染. key解决上述问题之外的情景:这两个元素是完全独立的,不要复用它们. 2.示例 <!DOCTYPE html&g ...
- base64编码-----------》struts2(token)利用BigInteger产生随机数
//struts2 源码 public static final Random RANDOM= new Random(); public static String generateGUID(){ r ...
- tomcat启动出现异常 Error filterStart
tomcat启动中出现 Error filterStart异常, 没有任何堆栈信息,如下: SEVERE: Error filterStart Jul 6, 2012 3:39:05 PM org.a ...
- VESA-ADV7123-SOCKIT-DE2115
/*--VGA Timing--Horizontal :-- ______________ _____________-- | | |--_______________| VIDEO |_______ ...
- caffe编译的问题 找不到opencv的 tiff库文件
解决办法: sudo su cmake .. make -j8 make pycaffe make install 问题解决. 看起来是权限问题导致.
- centOS中修改语言环境
在终端中打开 键入 vim ~/.bashrc 在最后一行键入 export LANG="zh_CH.UTF-8"