第二节课(1)

Counting Inversions(逆序计数)

音乐网站试图将你的听歌喜好与其他人相匹配:

  • 你的排前n的歌
  • 音乐网站从数据库中找到与你有相似品味的人

相似性度量(similarity metric):两个排列间的逆序的数量

  • 我的排名:1,2,...,n.
  • 你的排名:a_{1},a_{2},...,a_{n}..
  • 如果i < j,但 a_{i} > a_{j},歌i和j是逆序
  A B C D E
Me 1 2 3 4 5
You 1 3 4 2 5

 

暴力求解

列出每一对 i<j 并计数逆序的数量。c++实现:

/***************************************************************
	暴力求解(Brute Force):
	列出每一对i<j并计数逆序,返回逆序对数量。
	复杂度:O(n^2) 
****************************************************************/
int CI_BF(vector<int>& v)
{
	int r = 0;
	int sz = v.size();
	for(int i = 0; i < sz; ++i)
		for(int j = i + 1; j < sz; ++j)
			if(v[i] > v[j])
				++r;
	return r;
}

分而治之

在归并排序的基础上加了计数逆序 。c++实现:

/****************************************************************
	分而治之(Divide-and-Conquer) :
	在归并排序的基础上加了计数逆序 
	复杂度:O(nlogn) 
	备注:向量区间是左闭右开,即[lo,hi) 
*****************************************************************/
int MergeAndCount(vector<int>& v, int lo, int mi, int hi)
{
	int r = 0;	
	vector<int> v1(v.begin()+lo,v.begin()+mi);
	int sz = v1.size();
	int j = 0;	//v1的索引 
	while(j < sz && mi < hi){
		if(v1[j] <= v[mi]){
			v[lo++] = v1[j++];
		}
		else{
			v[lo++] = v[mi++];
			r += (sz - j);	//计数 
		}
	}
	while(j < sz){
		v[lo++] = v1[j++];
	}
	while(mi < hi){
		v[lo++] = v[mi++];
	}
	return r;
}
int SortAndCount(vector<int>& v, int lo, int hi)
{
	if(hi - lo < 2)
		return 0;	//递归基:单个元素自然有序,且逆序对为0 
	int mi = (lo + hi) / 2;
	int r1 = SortAndCount(v,lo,mi);
	int r2 = SortAndCount(v,mi,hi);
	int rm = MergeAndCount(v,lo,mi,hi);
	return r1+r2+rm; 
}
int CI_DC(vector<int>& v)
{
	return SortAndCount(v,0,v.size()); 
} 

运行效果:

图1 逆序计数程序运行效果

 

 

Ploynomial Multiplication(多项式乘法)

 A(x) = \sum_{i=0}^{n-1}a_{i}x^{i}

B(x) = \sum_{i = 0}^{m-1}b_{i}x^{i}

C(x) = A(x)B(x) = \sum_{i = 0}^{n+m-2}c_{k}x^{k}

Then

c_{k} = \sum _{0\leq i< n,0\leq j<m,i+j=k}a_{i}b_{j},for all 0 \leq k \leq n+m-2

定义:向量(c_{0},c_{1},...,c_{n+m-2})是向量(a_{0},a_{1},...,a_{n-1})和向量(b_{0},b_{1},...,b_{m-1})的卷积。(这是数字信号处理中的主要问题。)

暴力求解

直接使用定义公式求解。c++实现:

/***************************************************************
	暴力求解(Brute Force):
	按定义直接求解。
	复杂度:O(n^2) 
****************************************************************/
void PM_BF(vector<int>& A, vector<int>& B, vector<int>& C)
{
	int n = A.size();
	int m = B.size();
	C.clear();
	C.assign(n+m-1,0);
	for(int i = 0; i < n; ++i){
		for(int j = 0; j < m; ++j){
			C[i+j] += A[i] * B[j];
		}
	}
	return;
}

第一种分而治之

假定n为2的幂

A_{0}(x) =a_{0} + a_{1}x+...+a_{\frac{n}{2}-1}x^{\frac{n}{2}-1}

A_{1}(x) = a_{\frac{n}{2}} + a_{\frac{n}{2}+1} x+ ... +a_{n-1}x^{\frac{n}{2}-1}

A(x) = A_{0}(x) + A_{1}(x)x^{\frac{n}{2}}

同理,定义B_{0}(x),B_{1}(x)

B(x) = B_{0}(x) + B_{1}(x)x^{\frac{n}{2}}

A(x)B(x) = A_{0}(x)B_{0}(x) + A_{0}(x)B_{1}(x)x^{\frac{n}{2}} +A_{1}(x)B_{0}(x)x^{\frac{n}{2}} +A_{1}(x)B_{1}(x) x^{n}

输入规模为n的原始问题被分为输入规模为n/2的4个子问题。

c++实现:

/********************************************************************************************************
	分而治之(Divide-and-Conquer) 第一种:
	将两个多项式都分为两块,将整个问题分为四个子问题分别解决 
	复杂度:O(n^2) 
	备注:假定n为2的幂
*********************************************************************************************************/
void PM_DC_1_Recur(vector<int>& A, int la, int ha, vector<int>& B, int lb, int hb, vector<int>& C)
{
	int n = ha - la;	//n为2的幂 
	int h = n/2;
	C.clear();
	C.assign(2*n-1,0);
	if(n == 1){	//递归基 
		C[0] = A[la] * B[lb];
		return;
	}
	vector<int> U,V,W,Z;
	PM_DC_1_Recur(A,la,la+h,B,lb,lb+h,U);
	PM_DC_1_Recur(A,la,la+h,B,lb+h,hb,V);
	PM_DC_1_Recur(A,la+h,ha,B,lb,lb+h,W);
	PM_DC_1_Recur(A,la+h,ha,B,lb+h,hb,Z);
	for(int i = 0; i < n-1; ++i){
		C[i] += U[i];
		C[i+h] += V[i] + W[i];
		C[i+n] += Z[i];
	}
	return;
}
void PM_DC_1(vector<int>& A, vector<int>& B, vector<int>& C)
{
	PM_DC_1_Recur(A,0,A.size(),B,0,B.size(),C);
	return;
}

第二种分而治之

事实上。我们只需三项:A_{0}B_{0},{\color{Red} A_{0}B_{1}+A_{1}B_{0}},A_{1}B_{1}

且,我们只需三次多项式乘法就可以得到前述三项:

Y = (A_{0} + A_{1})(B_{0}+B_{1})

U = A_{0}B_{0}

Z=A_{1}B_{1}

可得{\color{Red} A_{0}B_{1}+A_{1}B_{0}} = Y - U - Z

c++实现:

/**********************************************************************************************************
	分而治之(Divide-and-Conquer) 第二种:
	将两个多项式都分为两块,将整个问题分为三个子问题分别解决 
	复杂度:O(n^log3) 
	备注:假定n为2的幂
***********************************************************************************************************/
void PM_DC_2_Recur(vector<int>& A, int la, int ha, vector<int>& B, int lb, int hb, vector<int>& C)
{
	int n = ha - la;	//n为2的幂 
	int h = n/2;
	C.clear();
	C.assign(2*n-1,0);
	if(n == 1){	//递归基 
		C[0] = A[la] * B[lb];
		return;
	}
	vector<int> U,Z,Y;
	PM_DC_2_Recur(A,la,la+h,B,lb,lb+h,U);
	PM_DC_2_Recur(A,la+h,ha,B,lb+h,hb,Z);
	vector<int> A1(h,0);
	vector<int> B1(h,0);
	for(int i = la,j = 0; j < h; ++i,++j){
		A1[j] = (A[i] + A[i+h]);
		B1[j] = (B[i] + B[i+h]);
	} 
	PM_DC_2_Recur(A1,0,h,B1,0,h,Y);
	for(int i = 0; i < n-1; ++i){
		C[i] += U[i];
		C[i+h] += Y[i] - U[i] - Z[i];
		C[i+n] += Z[i];
	}
	return;
}
void PM_DC_2(vector<int>& A, vector<int>& B, vector<int>& C)
{
	PM_DC_2_Recur(A,0,A.size(),B,0,B.size(),C);
	return;
}

运行效果:

图2 多项式乘积代码运行效果

分析总结

  1.  分而治之的方法并不总是最好的解决方法。(第一种分而治之算法与暴力求解方法时间复杂度相同)

  2. 事实上,有O(nlogn)的解决多项式乘积的办法。(使用了傅里叶变换FFT)

  3. 在大整数乘法中也使用了三个乘法代替四个乘法的想法。(这个想法同样是经典的Strassen矩阵乘法算法的基础)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章