分治法求解问题

一、寻找两个等长有序序列的中位数

1.1 问题描述

  对于一个长度为n的有序序列(假设均为升序序列),a[0...n-1],处于中间位置的元素称为a的中位数,现要求两个等长有序序列合并的中位数。

1.2 求解思路

  对于求两个有序序列a[],b[]的中位数,首先考虑到二路归并后求其中位数c[m](m=(s+t)/2,s为序列左侧编号,t为序列右侧编号,对于偶数位中位数,只考虑下中位),但时间复杂度为O(n),效率比较低,因此这里考虑采用分治法。当两个有序序列元素个数只有一个时,返回较小的那一个;当元素个数不止一个时,分为以下三种情况:
(1)a[m1]与b[m1]相等,a[m1]或b[m1]就是中位数,因为合并后a[m1]和b[m1]必定在中间位置。
(2)a[m1]>b[m1],此时考虑大的舍大的,小的舍小的。即元素大的序列舍弃后半子序列,保留前半子序列;元素小的序列舍弃前半子序列,保留厚板子序列(要求舍弃的长度相等)。
(3)a[m1]>b[m1],同(2)。
在求解过程中,保留前半子序列prepart可以直接t=m;但保留后半子序列postpart时,需要考虑到奇偶位数问题,当元素个数为奇数(s+t)%2==0个时,直接t=m(这样舍弃的位数才相等),当元素个数为偶数(s+t)%2!=0个时,t=m+1(因为偶数个元素时,考虑的是下中位,因此为使舍弃的位数相等,下中位就不能要)。

1.3 详细代码

#include<iostream>
using namespace std;
#define MAXV 51

void prepart(int& s,int& t){//求序列前半部分 
	int m=(s+t)/2;
	t=m;			//包括当前数 
} 

void postpart(int& s,int& t){
	int m=(s+t)/2;
	if((s+t)%2==0){//当前剩余奇数位 
		s=m;
	}
	else{//当前剩余偶数位。(若b保留后半部分,由于a保留的前半部分包含中间位,为保证舍弃长度相等,b需舍弃中间位) 
		s=m+1;
	}
}

int Solve(int a[],int s1,int t1,int b[],int s2,int t2){//采用递归方法 
	int m1,m2;
	if(s1==t1&&s2==t2){
		return a[s1]<b[s2]?a[s1]:b[s2];
	}
	else{
		m1=(s1+t1)/2;
		m2=(s2+t2)/2;
		if(a[m1]==b[m2]){
			return a[m1];
		}
		else if(a[m1]<b[m2]){//a舍弃前半部分,保留后半部分,b舍弃后半部分,保留前半部分(小的舍弃小的,大的舍弃大的) 
			postpart(s1,t1);
			prepart(s2,t2); 
			return Solve(a,s1,t1,b,s2,t2);
		}
		else {				//a保留前半部分,b保留后半部分 
			prepart(s1,t1);
			postpart(s2,t2);
			return Solve(a,s1,t1,b,s2,t2);
		}
	}
}

int main(){
	int n;//序列长度
	int a[MAXV];//第一个序列 
	int b[MAXV];//第二个序列
	cout<<"请输入序列长度:"<<endl;
	cin>>n;
	cout<<"请输入第一个序列:";
	for(int i=0;i<n;i++){
		cin>>a[i];
	}
	cout<<"请输入第二个序列:";
	for(int i=0;i<n;i++){
		cin>>b[i];
	}
	int result=Solve(a,0,n-1,b,0,n-1);
	cout<<"中位数为:"<<result<<endl;
	return 0;
}

1.4 时间复杂度分析

  对于此种算法,每次比较后序列减少一半,当n=1时,T(n)=1;当n>1时,T(n)=T(n/2)+1,因此时间复杂度为O(log₂n)。

二、求解最大连续子序列和问题

2.1 问题描述

  给定一个包含 K个整数的序列 {N1,N2,…,NK}。连续子序列定义为 {Ni,Ni+1,…,Nj},其中 1≤i≤j≤K。最大子序列是指序列内各元素之和最大的连续子序列。规定一个序列的最大连续子序列和至少为0,若小于0,则返回0。

2.2 求解思路

  采用分治法的思想。对于一个序列,将其一分为二,中间元素对应编号mid,序列左侧left,序列右侧right,其最大连续子序列和只可能存在三种位置即mid左边,mid右边以及跨越了mid分布于中间(当序列只有一个元素时,若这个元素大于0,返回这个元素,否则返回0)。因此只需要求左侧最大连续子序列和maxLeftSum,右侧最大连续子序列和maxRightSum以及中间最大连续子序列和maxMidSum,比较最大值返回即可。采用递归思想,每次将序列一分为二,直到递归出口:当前序列只有一个元素。求中间最大连续子序列和的过程可以看做顺序遍历(使用for循环),当加上当前这个元素后值小于当前所记录的最大和时,所记录的最大和不变,只有当加上当前元素值大于最大和时,最大和才变。

2.3 代码如下:

#include<iostream>
#include<algorithm>
using namespace std;

int Solve(int a[],int left,int right){
	if(left==right){
		if(a[left]>0){
			return a[left];
		}
		else return 0;
	}
	int mid=(left+right)/2;
	int maxLeftSum,maxRightSum;//左右序列中最大子序列
	int leftBorderSum,maxLeftBorderSum;//跨过中间序列时,左边已有序列以及最大连续子序列 
	int rightBorderSum,maxRightBorderSum;
	maxLeftSum=Solve(a,left,mid);//求左边序列最大连续子序列和 
	maxRightSum=Solve(a,mid+1,right);//求右边序列最大连续子序列和 
	leftBorderSum=0;maxLeftBorderSum=0;
	for(int i=mid;i>=left;i--){//跨越中间时求中间左边最大连续子序列和 
		leftBorderSum+=a[i];
		if(leftBorderSum>maxLeftBorderSum){
			maxLeftBorderSum=leftBorderSum;
		}
	}
	rightBorderSum=0;maxRightBorderSum=0;
	for(int i=mid+1;i<=right;i++){//跨越中间时求中间右边最大连续子序列和 
		rightBorderSum+=a[i];
		if(rightBorderSum>maxRightBorderSum){
			maxRightBorderSum=rightBorderSum;
		}
	}
	int maxMidSum=maxLeftBorderSum+maxRightBorderSum;
	return max(maxMidSum,max(maxLeftSum,maxRightSum));
}

int main(){
	int a[]={-2,11,-4,13,-5,-2},n=6;
	int b[]={-6,2,4,-7,5,3,2,-1,6,-9,10,-2},m=12;
	cout<<"a序列的最大连续子序列和为:"<<Solve(a,0,n-1)<<endl;
	cout<<"b序列的最大连续子序列和为:"<<Solve(b,0,m-1)<<endl;
	return 0;
} 

2.4 时间复杂度分析

  使用递归方法,递归模型为:当n==1时,T(n)=1;当n>1时,T(n)=T(n/2)+n。由此可得时间复杂度为O(nlog₂n)。

2.5 穷举法求最大连续子序列和(O(n))

  使用穷举法求最大连续子序列和时,基本思路为:遍历序列,用max记录最大连续子序列和,now记录当前连续子序列和,当加了一个元素后now>max,就设max=now;当加了一个元素后now<0,就设now=0,从当前元素的下一个元素重新开始记录。代码如下:

int Solve(int a[],int n){
	int max=-INF,now=0;
	for(int i=0;i<n;i++){
		now+=a[i];
		if(now<0){//重新开始下一个子序列 
			now=0;
		}
		if(max<now){//比较求最大连续子序列和 
			max=now;
		}
	}
	return max;
}

  由于在算法中,只对序列遍历了一遍,因此时间复杂度为O(n)。

2.6 算法题acwing-1479.最大子序列和

  问题描述:此题与例题不同之处在于此题要求输出最大连续子序列和最左边元素和最右边元素。求解此题时,采用穷举法,设置变量temp存储开始位置(初始时为0)。for循环遍历,当加了一个元素后now>max,就设max=now,left=temp,right=i;当加了一个元素后now<0,就设now=0,从当前元素的下一个元素重新开始记录即temp=i+1。代码如下:

void Solve(vector<int> v,int n){
    int i,now=0,max=-INF;
    int left=0,right=0;
    int temp=0;
    for(i=0;i<n;i++){
        now+=v[i];
        if(now<0){
            now=0;
            temp=i+1;
        }
        else{
            if(now>max){
                max=now;
                left=temp;
                right=i;
            }
        }
    }
    if(max<0){//此时表示没有最大连续子序列
        max=0;
        left=0;
        right=n-1;
    }
    cout<<max<<" "<<v[left]<<" "<<v[right];
}

三、求解大整数乘法问题

3.1 问题描述

  设X和Y都是n(为了简单,假设n为2的幂,且X,Y均为正数)位的二进制整数,现在要计算它们的乘积X×Y。当位数n很大时可以用传统方法来设计一个计算乘积X×Y的算法,但是这样做计算步骤太多,显得效率低,此时可以采用分治法设计一个更有效的大整数乘积算法。

3.2 求解思路

  首先看算法求解过程和改进过程:

  通过此过程可以看出,我们需要求四次乘法,分别是AC,AD,BC,BD,因此可以考虑使用分治法,每次将两个数一分为二,采用递归算法,直到一分为二后只剩一位,此时为递归出口,由于是二进制相乘,因此只需判断0和1即可。最后递归出来可以求得最外层AC,AD,BC,BD,将其转化为十进制,带入方程即可求得它们乘积的解,最后将其转换为二进制即为结果。

3.3 详细代码

#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 20

void left(int a[],int n,int b[]){//求高n/2位 
	for(int i=0;i<MAXN;i++){
		b[i]=0;
	}
	for(int i=n/2;i<=n;i++){
		b[i-n/2]=a[i]; 
	}
}

void right(int a[],int n,int b[]){//求低n/2位 
	for(int i=0;i<MAXN;i++){
		b[i]=0;
	}
	int i;
	for(i=0;i<n/2;i++){
		b[i]=a[i];
	}
	b[i]='\0';
}

long Trans2to10(int a[]){//将二进制转换为十进制 
	int s=a[0],x=1;
	for(int i=1;i<MAXN;i++){
		x=2*x;
		s+=a[i]*x;
	}
	return s;
}

void Trans10to2(int x,int a[]){//将十进制转换为二进制 
	int i,j=0;
	while(x>0){
		a[j]=x%2;//求余数 
		j++;
		x=x/2;
	}
	for(i=j;i<MAXN;i++){
		a[i]=0;
	}
}

void MULT(int X[],int Y[],int Z[],int n){
	long e1,e2,e3,e4,e;
	int A[MAXN],B[MAXN],C[MAXN],D[MAXN];
	int m1[MAXN],m2[MAXN],m3[MAXN],m4[MAXN];
	for(int i=0;i<MAXN;i++){
		Z[i]=0;
	}
	if(n==1){
		if(X[0]==1&&Y[0]==1) Z[0]=1;
		else Z[0]=0;
	}
	else{
		left(X,n,A);	//求X高n/2位 
		right(X,n,B);	//求X低n/2位 
		left(Y,n,C);	//求Y高n/2位 
		right(Y,n,D);	//求Y低n/2位
		MULT(A,C,m1,n/2); //m1=AC
		MULT(A,D,m2,n/2); //m2=AD
		MULT(B,C,m3,n/2); //m3=BC
		MULT(B,D,m4,n/2); //m4=BD 
		e1=Trans2to10(m1); //将e1转换为十进制数 
		e2=Trans2to10(m2);
		e3=Trans2to10(m3);
		e4=Trans2to10(m4);
		e=e1*(int)pow(2,n)+(e2+e3)*(int)pow(2,n/2)+e4;//求解 
		Trans10to2(e,Z);		//将e转换成二进制数 
	}
}

void disp(int a[]){
	for(int i=MAXN-1;i>=0;i--){
		cout<<a[i];
	}
	cout<<endl;
}

void trans(char a[],int n,int X[]){//将字符串转化为数组(注意:低位在前,高位在后) 
	for(int i=0;i<n;i++){
		X[i]=(int)(a[n-i-1]-'0');
	}
	for(int i=n;i<MAXN;i++){
		X[i]=0;
	}
}

int main(){
	
	long e;
	char a[]="10101100";
	char b[]="10010011";
	int X[MAXN],Y[MAXN],Z[MAXN];
	int n=8;
	trans(a,n,X);//将字符串X转换为数组
	trans(b,n,Y);//将字符串Y转换为数组
	cout<<"X:";disp(X);
	cout<<"Y:";disp(Y);
	cout<<"Z=X*Y"<<endl;
	MULT(X,Y,Z,n);//求得Z 
	cout<<"Z:";disp(Z);
	e=Trans2to10(Z);
	cout<<"Z对应的十进制数:"<<e<<endl;
	cout<<"验证正确性:"<<endl;
	long x,y;
	x=Trans2to10(X);
	y=Trans2to10(Y);
	cout<<"X对应的十进制数为:"<<x<<endl;
	cout<<"Y对应的十进制数为:"<<y<<endl;
	cout<<"z=x*y"<<endl;
	cout<<"求解结果:"<<x*y<<endl;
	return 0;
} 

3.4 时间复杂度分析

  根据递归模型可知,最后时间复杂度为O(n^2);

3.5 运行结果

3.6 注意事项

  在求解过程中注意二进制写法左边是高位,右边是低位;而数组存储时,从小到大是从低位到高位,分开存储时要注意顺序。代码中变量比较多,注意区分。十进制转二进制,二进制转十进制要熟练。

posted @   jzhszy  阅读(406)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示