第二節課(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矩陣乘法算法的基礎)

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