Processing math: 100%
007's Studio.

算法导论思考题2-4

字数统计: 211阅读时长: 1 min
2019/07/30 41 Share

思考题2-4(逆序对) 假设A[1..n]是一个有n个不同数的数组,若iA[j],则对偶(i,j)称为A的一个逆序对(inversion)。

a.列出数组<2,3,8,6,1>的5个逆序对。

 逆序对有(1,5),(2,5),(3,4),(3,5),(4,5)


b.有集合{1,2,,n}中的元素构成的什么数组具有最多的逆序对?它有多少逆序对?

  当数组为 {1,2,,n}为逆序时,即{n,n1,,1}时逆序对最多。对于 i,j[1,n],(i,j)均为逆序对。此时共有逆序对(2n)=n(n1)/2


c.插入排序的运行时间与输入数组中逆序对的数量之间是什么关系?证明你的回答。

 假设数组A初始时存在逆序对(k,j),那么有$kkeywhileA[i]jj$个元素的逆序对。对于7-9的一次循环,相当于消除一对逆序对。因此,插入排序的运行时间和数组的逆序对之间呈线性关系。


d.给出一个确定在n个元素的任何排列中逆序对数量的算法,最坏情况需要Θ(nlgn)的时间。

 对于这个问题,可以采用归并排序统计逆序对量,或者采用线段树。下面给出具体思路

1、归并排序

计算数组的逆序对数量,可以采用归并排序二分的思想。对于一个数组来说逆序对的来源可以分为3种情况。

对于一个数组 A[left,right],mid=(left+right)/2
(1) 两个都在左半数组,leftijmidA[i]>A[j]
(2) 两个都在右半数组,mid+1ijright,A[i]>A[j]
(3) 左右各一个,leftimid<jright,A[i]>A[j]

根据上面的思路,只需要对归并排序的代码稍微改动即可。下面是我的实现:

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<iostream>
#include<cstdio>
using namespace std;
const int INF=0x3f3f3f3f;
class InversionCount
{
public:
/*
总的逆序对等于左子数组的内部逆序对+右子数组的内部逆序对+来自左和右形成的逆序对
*/
static int MergeSort(int A[],int l,int r)
{
int cnt=0;
if(l<r)
{
int m=(l+r)/2;
cnt+=MergeSort(A,l,m);//第1种情况
cnt+=MergeSort(A,m+1,r);//第2种情况
cnt+=Merge1(A,l,m,r);//第3种情况
}
return cnt;
}
//书种归并排序采用哨兵的版本
static int Merge1(int A[],int l,int m,int r){
int inv_cnt=0;
int *L=new int[m-l+2];
int *R=new int[r-m+1];
for(int i=l,k=0;i<=m;)
L[k++]=A[i++];
for(int i=m+1,k=0;i<=r;)
R[k++]=A[i++];
int LenA=m-l+1;//左子数组的长度
L[m-l+1]=R[r-m]=INF;//设置哨兵
for(int k=l,i=0,j=0;k<=r;)
{
if(L[i]<=R[j])//i<j且A[i]<=A[j],没有逆序对
A[k++]=L[i++];
else{//i<j且A[i]>A[j]
if(L[i]!=INF){//这里有可能左侧数组先达到尾部,此时防止INF引起额外累加。
//大于A[j]的数量=LenA-i(小于A[i]的数量,数组从0开始)
inv_cnt+=LenA-i;
}
A[k++]=R[j++];
}
}
// printf("l = %d r = %d cnt = %d\n",l,r,inv_cnt);
return inv_cnt;
}
//不用哨兵处理会更方便点
static int Merge2(int A[],int l,int m,int r){
int *B=new int[r-l+1];
int i=l,j=m+1,k=0;
int inv_cnt=0;
while(i<=m&&j<=r)
{
if(A[i]<=A[j])
B[k++]=A[i++];
else
{
inv_cnt+=m-i+1;
B[k++]=A[j++];
}
}
while(i<=m)
B[k++]=A[i++];
while(j<=r)
B[k++]=A[j++];
k=0;
for(int i=l;i<=r;)
{
A[i++]=B[k++];
}
return inv_cnt;
}

};
int main()
{
int A[]={1,2,3,4,5,6,7,8};
int B[]={8,7,6,5,4,3,2,1};
int C[]={6,6,6,6,6,5};
int D[]={2,3,8,6,1};
cout << InversionCount::MergeSort(A,0,7)<<endl;
cout << InversionCount::MergeSort(B,0,7)<<endl;
cout << InversionCount::MergeSort(C,0,5)<<endl;
cout << InversionCount::MergeSort(D,0,4)<<endl;
}

2、线段树

可以采用线段树来处理,这里不打算具体展开线段树的内容,后面有时间再整理一个关于线段树的专题。下面介绍算法思路。

1、为数组A[1,n]建立线段树,区间统计[left,rigth]0,如果数据范围较大数据比较分散,可以先进行一次离散化。

2、依次对A[1,n][min,man]的每个元素进行以下操作,假设A[i]=k,则对线段树查找区间[k+1,max]的大小,加入逆序对数量中,然后令A[k]+1

上面的操作相当于遍历数组A,对于每一个元素,查找当前在它前面大于它的元素个数,然后累加起来,得到逆序对。遍历数组时间为O(n),每进行一次查找操作为O(lgn),总的的时间复杂度为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
64
#include<iostream>
#include<algorithm>
using namespace std;
#define lchild (x<<1)
#define rchild (x<<1|1)
const int N=100;
int A[N<<2|1];
void update(int x)
{
A[x]=A[lchild]+A[rchild];
}
//建立线段树
void build(int left,int right,int x)
{
if(left==right){
A[x]=0;
return;
}
int mid=(left+right)>>1;
build(left,mid,lchild);
build(mid+1,right,rchild);
update(x);
}
//查询区间[L,R]
int query(int left,int right,int L,int R,int x)
{
if(L<=left&&right<=R)
return A[x];
int ret=0;
int mid=(left+right)>>1;
if(L<=mid)
ret+=query(left,mid,L,R,lchild);
if(R>mid)
ret+=query(mid+1,right,L,R,rchild);
return ret;
}
//修改 A[x]=k的结点+1
void modify(int i,int left,int right,int x)
{
if(left==right){
++A[x];
return;
}
int mid=(left+right)>>1;
if(i<=mid)
modify(i,left,mid,lchild);
else
modify(i,mid+1,right,rchild);
update(x);
}

int main()
{
int arr[]={10,9,8,7,6,5,4,3,2,1};
build(1,10,1);
int cnt=0;
cout << "Hello" << endl;
for(auto i:arr)
{
cnt+=query(1,10,i+1,10,1);
modify(i,1,10,1);
}
cout << cnt << " ";
}

原文作者:007havegone

原文链接:http://007havegone.github.io/2019/07/30/算法导论思考题2-4/

发表日期:July 30th 2019, 5:41:22 pm

更新日期:July 30th 2019, 10:42:17 pm

版权声明:‘原创内容,转载时请附上出处’

CATALOG
  1. 1. 思考题2-4(逆序对) 假设A[1..n]是一个有n个不同数的数组,若iA[j],则对偶(i,j)称为A的一个逆序对(inversion)。