目录
分治算法策略的设计模式
分治思想:
分治算法求解问题的步骤:
设计模式
算法分析
二分查找算法
思考题
计算模型:
时间复杂度分析:
代码:
分治*大数乘法:
【例5-2】设X, Y是两个n位的十进制数,求X*Y
问题分析:
1.1 计算方法:
2.1 计算方法:
思考题:
算法分析:
代码:
思考题:二分治法 和 VS算法 矩阵相乘
算法效率:
代码:
棋盘覆盖问题:
【例5-4】残缺棋盘
问题分析:s=size/2 分治
计算模型
算法分析
算法设计与描述:
代码:
选择性问题:——非等分
一般性描述:
问题分析:
选择性问题:选第k小值
计算模型
算法分析
代码:
思考题:正元素 负元素排序
计算模型:
思考题:用分治法 求 数列的最大子段和
分治算法策略的设计模式
分治思想:
分治算法求解问题的步骤:
设计模式

算法分析

根据主定理有

【子问题平衡原理】划分规模:等长>不等长规模
二分查找算法

思考题
计算模型:
只剩一个数据时 max=x[low]
对元素进行分组,中间下标mid=(low+high)/2,递归使用findmax(x,low,mid,tempmax1) ,findmax(x,mid+1,high,tempmax2) 返回两区间最大值tempmax1 和tempmax2
最大值为tempmax1 和tempmax2 的较大值,
时间复杂度分析:
T(n) = 2*T(n/2) + 5; n>1
T(n)=O(1) ;n=1
设a=2 b=2 f(n)=5=n^(log_2 2-1)=O(1)
根据主定理 T(n)=O(n)
代码:
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
void findmax(int x[],int low,int high,int &max)
{
int tempmax1=0,tempmax2=0;
if(low==high)
{
max = x[low] ;
return ;
}
int mid=(low+high)/2;
findmax(x,low,mid,tempmax1);
findmax(x,mid+1,high,tempmax2);
if(tempmax2< tempmax1)max=tempmax1;
else max=tempmax2;
}
int main()
{
int n=10;
int x[n];
for(int i=0;i<n;i++)
{
x[i]=30+rand()%10;
cout<<x[i]<<" ";
}
int max=0;
findmax(x,0,n-1,max);
cout<<endl<<max<<endl;
return 0;
}
分治*大数乘法:
【例5-2】设X, Y是两个n位的十进制数,求X*Y
问题分析:
1.1 计算方法:
1.2 时间复杂度:
2.1 计算方法:
2.2 时间复杂度:
计算模型
(2)递归结束条件
思考题:

算法描述
输入:两个n位十进制数x,y
核心操作:三个乘法
时间复杂度:T(n)=3*T(n/2)+c*n
设a=3,b=2 ,f(n)=cn, ,
有f(n)=取
=log_2 3/2 >0 符合主定理(1),因此
算法分析:
算法设计与描述 | 算法分析 |
输入:两个n位十进制数 | |
输出:乘积 | |
Karatsuba(x,y) { if(x==0||y==0)return 0; if(n==1)return x*y; n<- n/2; x1<- x / (int)pow(10,n) ,x0<- x % (int)pow(10,n); y1<- y/ (int)pow(10,n),y0<- y % (int)pow(10,n); //计算 xy1<- Karatsuba(x1,y1); xy0<- Karatsuba(x0,y0); sum<- (x1-x0)*(y0-y1); return xy1*pow(10,(2*half) ) + (sum+xy1+xy0)*pow(10,half) +xy0; } | (1)输入两个n位十进制数, (2)核心语句:将两数分为前后两部分x1 x0 y1 y0,递归计算 (3) 时间复杂度:T(n)=3*T(n/2)+c*n 设a=3,b=2 ,f(n)=cn, 有f(n)= |
代码:
#include<bits/stdc++.h>
using namespace std;
/*
unsigned int 0~4294967295
int -2147483648~2147483647
unsigned long 0~4294967295
long -2147483648~2147483647
long long的最大值:9223372036854775807
long long的最小值:-9223372036854775808
unsigned long long的最大值:18446744073709551615
__int64的最大值:9223372036854775807
__int64的最小值:-9223372036854775808
unsigned __int64的最大值:18446744073709551615
*/
//Karatsuba方法 将大数拆分 两数位数相同
//分治
long long Karatsuba(long long num1,long long num2)
{
// cout<<num1<<" "<<num2<<endl;
if(num1<10 ||num2<10)return num1*num2;
int size1=ceil(log(num1)/log(10));//位数
int size2=ceil(log(num2)/log(10));
int half=max(size1,size2)/2;
// cout<<size1<<" h "<<half<<endl;
//拆分为 abcd
long long x1=num1 / (int)pow(10,half) ,x0= num1 % (int)pow(10,half);
long long y1=num2/ (int)pow(10,half),y0=num2 % (int)pow(10,half);
// cout<<x1<<" "<<x0<<endl;
//计算
long long xy1=Karatsuba(x1,y1);
long long xy0=Karatsuba(x0,y0);
long long sum= (x1-x0)*(y0-y1);
// cout<<pow(10,5)<<endl;
return xy1*pow(10,(2*half) ) + (sum+xy1+xy0)*pow(10,half) +xy0;
}
int main()
{
cout<<endl<<"Karatsuba:"<<Karatsuba(155,999);//但是要注意 long long的数字限制
return 0;
}
思考题:二分治法 和 VS算法 矩阵相乘
算法效率:
普通的二分治算法:
T(n)=O(1) n=2
T(n)=8*T(n/2)+(n^2) n>2
设a=8,b=2,f(n)=(n^2),n^(log_2 8 -1)=n^2.
有f(n)=(log_2 8 -1)=
(n^2) 取
=1>0
根据主定理 T(n)=(n^3)
VS算法:【8次->7次乘法 m不唯一,但也增加了加法运算量】
T(n)=O(1) n=2
T(n)=7*T(n/2)+(n^2)
设a=7,b=2,f(n)=(n^2),n^(log_2 7 -1)=n^2.
有f(n)=(log_2 7 -0.8)=
(n^2) 取
=0.8>0
根据主定理 T(n)=(n^2.8)
7个乘法,18个加法
因此VS算法比普通二分治算法效率高
代码:
#include<iostream>
using namespace std;
class Mat{
public:
int **m;
int n;
Mat(int nn){
// cout<<"Mat:"<<nn<<endl;
n=nn;
m=new int*[n];
for(int i=0;i<n;i++)
{
m[i]=new int[n];
}
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
m[i][j]=0;
}
}
// cout<<"over"<<endl;
}
void input01(int** x)
{
int n=this->n;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
this->m[i][j]=x[i][j];
}
}
}
void show()
{
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
cout<<m[i][j]<<"\t";
}
cout<<endl;
}cout<<endl<<endl;
}
Mat operator + (Mat &a)// 定义重载运算符"+"的友元函数
{
Mat c(a.n);
for(int i=0;i<a.n;i++)
{
for(int j=0;j<n;j++)
{
c.m[i][j]=a.m[i][j]+this->m[i][j];
}
}
return c;
}
Mat operator - (Mat &a)// 定义重载运算符"-"的友元函数
{
Mat c(a.n);
for(int i=0;i<a.n;i++)
{
for(int j=0;j<n;j++)
{
c.m[i][j]=this->m[i][j] -a.m[i][j];
}
}
return c;
}
};
int ** change(int x[][4])
{
int n=4;
// int **xx=malloc(4*sizeof(int*));
int **xx=new int*[4];
for(int i=0;i<n;i++)
{
xx[i]=new int[4];
for(int j=0;j<n;j++)
{
xx[i][j]=x[i][j];
cout<<xx[i][j]<<"\t";
}
cout<<endl;
}
cout<<endl<<endl;
return xx;
}
int a[4][4]={
1,0,2,1,
4,1,1,0,
0,1,3,0,
5,0,2,1
};
int **aa=change(a);
int b[4][4]={
0,1,0,1,
2,1,0,4,
2,0,1,1,
1,3,5,0
};
int **bb=change(b);
int n=4;
Mat ma(n),mb(n),mc01(n),mc02(n);
//二分治法
void bs(int n,Mat a,Mat b,Mat c)
{
// cout<<endl<<endl<<"in vs:"<<n<<endl;
if(n==1)
{
c.m[0][0]=ma.m[0][0] * mb.m[0][0];
// cout<<"n==1"<<c.m[0][0];
return ;
}
else if(n==2)
{
// cout<<"n==2"<<endl;
c.m[0][0]=a.m[0][0]*b.m[0][0]+a.m[0][1]*b.m[1][0];
c.m[0][1]=a.m[0][0]*b.m[0][1]+a.m[0][1]*b.m[1][1];
c.m[1][0]=a.m[1][0]*b.m[0][0]+a.m[1][1]*b.m[1][0];
c.m[1][1]=a.m[1][0]*b.m[0][1]+a.m[1][1]*b.m[1][1];
// cout<<"n==2"<<c.m[0][0];
return ;
}
//划分为4个矩阵
Mat a00(n/2),a01(n/2),a10(n/2),a11(n/2);
Mat b00(n/2),b01(n/2),b10(n/2),b11(n/2);
// cout<<"划分"<<endl;
for(int i=0;i<n/2;++i)
{
for(int j=0;j<n/2;++j)
{
//左上
// cout<<"左上"<<i<<" "<<j<<endl;
a00.m[i][j]=a.m[i][j];
b00.m[i][j]=b.m[i][j];
// cout<<"右上"<<endl;
//右上
a01.m[i][j]=a.m[i][j+n/2];
b01.m[i][j]=b.m[i][j+n/2];
//左下
a10.m[i][j]=a.m[i+n/2][j];
b10.m[i][j]=b.m[i+n/2][j];
//右下
a11.m[i][j]=a.m[i+n/2][j+n/2];
b11.m[i][j]=b.m[i+n/2][j+n/2];
}
}
// cout<<"递归"<<endl;
//递归求四个方阵
Mat ab0000(n/2);
bs(n/2,a00,b00,ab0000);
Mat ab0110(n/2);
bs(n/2,a01,b10,ab0110);
Mat ab0001(n/2);
bs(n/2,a00,b01,ab0001);
Mat ab0111(n/2);
bs(n/2,a01,b11,ab0111);
Mat ab1000(n/2);
bs(n/2,a10,b00,ab1000);
Mat ab1110(n/2);
bs(n/2,a11,b10,ab1110);
Mat ab1001(n/2);
bs(n/2,a10,b01,ab1001);
Mat ab1111(n/2);
bs(n/2,a11,b11,ab1111);
Mat c00(n/2),c01(n/2),c10(n/2),c11(n/2);
c00=ab0000+ab0110;
c01=ab0001+ab0111;
c10=ab1000+ab1110;
c11=ab1001+ab1111;
// c00 =m1+m4-m5+m7;
// c01 =m3+m5;
// c10=m2+m4;
// c11=m1+m3-m2+m6;
//合并
for(int i=0;i<n/2;++i)
{
for(int j=0;j<n/2;++j)
{
c.m[i][j]=c00.m[i][j];
c.m[i][j+n/2]=c01.m[i][j];
c.m[i+n/2][j]=c10.m[i][j];
c.m[i+n/2][j+n/2]=c11.m[i][j];
}
}
}
//VS算法
void vs(int n,Mat a,Mat b,Mat c)
{
// cout<<endl<<endl<<"in vs:"<<n<<endl;
if(n==1)
{
c.m[0][0]=ma.m[0][0] * mb.m[0][0];
// cout<<"n==1"<<c.m[0][0];
return ;
}
else if(n==2)
{
// cout<<"n==2"<<endl;
c.m[0][0]=a.m[0][0]*b.m[0][0]+a.m[0][1]*b.m[1][0];
c.m[0][1]=a.m[0][0]*b.m[0][1]+a.m[0][1]*b.m[1][1];
c.m[1][0]=a.m[1][0]*b.m[0][0]+a.m[1][1]*b.m[1][0];
c.m[1][1]=a.m[1][0]*b.m[0][1]+a.m[1][1]*b.m[1][1];
// cout<<"n==2"<<c.m[0][0];
return ;
}
//划分为4个矩阵
Mat a00(n/2),a01(n/2),a10(n/2),a11(n/2);
Mat b00(n/2),b01(n/2),b10(n/2),b11(n/2);
// cout<<"划分"<<endl;
for(int i=0;i<n/2;++i)
{
for(int j=0;j<n/2;++j)
{
//左上
// cout<<"左上"<<i<<" "<<j<<endl;
a00.m[i][j]=a.m[i][j];
b00.m[i][j]=b.m[i][j];
// cout<<"右上"<<endl;
//右上
a01.m[i][j]=a.m[i][j+n/2];
b01.m[i][j]=b.m[i][j+n/2];
//左下
a10.m[i][j]=a.m[i+n/2][j];
b10.m[i][j]=b.m[i+n/2][j];
//右下
a11.m[i][j]=a.m[i+n/2][j+n/2];
b11.m[i][j]=b.m[i+n/2][j+n/2];
}
}
// cout<<"递归"<<endl;
//递归求四个方阵
int half=n/2;
//m1=(a00+a11)*(b00+b11)
Mat m1(n/2);
vs(n/2,a00+a11,b00+b11,m1);
// cout<<"m1"<<endl;
//m2=(a10+a11)*(b00)
Mat m2(n/2);
vs(n/2,a10+a11,b00,m2);
//m3=(a00)*(b01-b11)
Mat m3(n/2);
vs(n/2,a00,b01-b11,m3);
//m4=(a11)*(b10-b00)
Mat m4(n/2);
vs(n/2,a11,b10-b00,m4);
//m5=(a00+a01)*(b11)
Mat m5(n/2);
vs(n/2,a00+a01,b11,m5);
//m6=(a10-a00)*(b00+b01)
Mat m6(n/2);
vs(n/2,a10-a00,b00+b01,m6);
//m7=(a01-a11)*(b10+b11)
Mat m7(n/2);
vs(n/2,a01-a11,b10+b11,m7);
// cout<<"finish m1-7"<<endl;
Mat c00(n/2),c01(n/2),c10(n/2),c11(n/2);
c00 =m1+m4-m5+m7;
c01 =m3+m5;
c10=m2+m4;
c11=m1+m3-m2+m6;
//合并
for(int i=0;i<n/2;++i)
{
for(int j=0;j<n/2;++j)
{
c.m[i][j]=c00.m[i][j];
c.m[i][j+n/2]=c01.m[i][j];
c.m[i+n/2][j]=c10.m[i][j];
c.m[i+n/2][j+n/2]=c11.m[i][j];
}
}
}
int main()
{
ma.input01(aa);
mb.input01(bb);
vs(n,ma,mb,mc01);
mc01.show();
bs(n,ma,mb,mc02);
mc02.show();
return 0;
}
/*
1 0 2 1
4 1 1 0
0 1 3 0
5 0 2 1
0 1 0 1
2 1 0 4
2 0 1 1
1 3 5 0
5 4 7 3
4 5 1 9
8 1 3 7
5 8 7 7
5 4 7 3
4 5 1 9
8 1 3 7
5 8 7 7
*/
棋盘覆盖问题:
【例5-4】残缺棋盘
问题分析:s=size/2 分治
如何确定棋盘,如何区分棋盘?
——用棋盘左上角进行定位,用棋盘边长划定范围。
计算模型

• 当dr<tr+s and dc≥tc+s时,坏格在右上子棋盘


• 当dr≥tr+s and dc≥tc+s时,坏格在右下子棋盘 中,
用④ (tr+s-1, tc+s-1); (tr+s-1, tc+s) ; (tr+s, tc+s-1)。

算法分析

算法设计与描述:

代码:
#include<iostream>
using namespace std;
int n=8;//需要是2^x
int amount=1;
//棋盘
int CBoard[100][100];
//坏格子坐标
//覆盖之后棋盘
int chessboard[100][100];
//左上角方格所在行int tr,左上角 列int tc, (tr.tc)
//残缺 行int dr,残缺 列int dc (dr,dc )
//棋盘的行数or列数int size)
void CBCover(int CBoard[][100],int tr,int tc,int dr,int dc,int size)
{
if(size<2)return;
int t=amount;
amount++;//所使用的三格板的数目
//二分
int s=size/2;//子问题的棋盘
//残缺方格位于左上棋盘 1号隔板
if(dr<tr+s && dc<tc+s )
{
//递归
CBCover(CBoard,tr,tc,dr,dc,s);
//到最接近的 结束
//覆盖这三块方格 1号隔板
CBoard[tr+s-1][tc+s]=t;//注意是相对于隔板的标记坐标
CBoard[tr+s][tc+s-1]=t;//不是相对于残格的坐标
CBoard[tr+s][tc+s]=t; //
//覆盖其余部分
//下侧棋盘 覆盖的那块隔板 会在新的棋盘上占用一个方格 也就是一个坏块
CBCover(CBoard,tr,tc+s,tr+s-1,tc+s,s);
//右侧
CBCover(CBoard,tr+s,tc,tr+s,tc+s-1,s);
//右下侧
CBCover(CBoard,tr+s,tc+s,tr+s,tc+s,s);
}
//残缺方格位于右上棋盘 2号隔板
else if(dr<tr+s && dc>=tc+s )
{
//递归
CBCover(CBoard,tr,tc+s,dr,dc,s);
CBoard[tr+s-1][tc+s-1]=t;//覆盖2号三格板
CBoard[tr+s][tc+s-1]=t;
CBoard[tr+s][tc+s]=t;
//覆盖其余部分
CBCover(CBoard,tr,tc,tr+s-1,tc+s-1,s);
CBCover(CBoard,tr+s,tc,tr+s,tc+s-1,s);
CBCover(CBoard,tr+s,tc+s,tr+s,tc+s,s);
}
//残缺方格位于左下棋盘 3号隔板
else if(dr>= tr+s && dc < tc+s )
{
//递归
CBCover(CBoard,tr+s,tc,dr,dc,s);
CBoard[tr+s-1][tc+s-1]=t;//上
CBoard[tr+s-1][tc+s]=t;//右上
CBoard[tr+s][tc+s]=t;//右
//覆盖其余部分
CBCover(CBoard,tr,tc,tr+s-1,tc+s-1,s);
CBCover(CBoard,tr,tc+s,tr+s-1,tc+s,s);
CBCover(CBoard,tr+s,tc+s,tr+s,tc+s,s);
}
//残缺方格位于右下棋盘 4号隔板
else if(dr>=tr+s && dc>=tc+s )
{
//递归
CBCover(CBoard,tr+s,tc+s,dr,dc,s);
CBoard[tr+s-1][tc+s-1]=t;//覆盖2号三格板
CBoard[tr+s-1][tc+s]=t;
CBoard[tr+s][tc+s-1]=t;
//覆盖其余部分
CBCover(CBoard,tr,tc,tr+s-1,tc+s-1,s);
CBCover(CBoard,tr,tc+s,tr+s-1,tc+s,s);
CBCover(CBoard,tr+s,tc,tr+s,tc+s-1,s);
}
}
void output(int a[][100])
{
cout<<"a:"<<endl;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
cout<<a[i][j]<<"\t";
}
cout<<endl;
}
}
int main()
{
output(CBoard);
CBCover(CBoard,0,0,0,0,n);
output(CBoard);
return 0;
}
选择性问题:——非等分
一般性描述:
问题分析:
选择性问题:选第k小值
计算模型
算法分析
算法设计与分析 | |
输入 | |
输出 | |
int select(int a[],int left,int right,int k) { int i,j,pivot,t; if(left>=right)return a[left]; i=left; j=right+1; pivot=a[left]; while(true) { do{ i++; } while(a[i]<pivot); if(i>=j)break; t=a[i]; a[i]=a[j]; a[j]=t; } if(j-left+1==k) return pivot; a[left]=a[j]; a[j]=pivot; if(k<(j-left+1))select(a,left,j-1,k); else select(a,j+1,right,k-j-j+left); } |
代码:
#include<iostream>
using namespace std;
void show(int a[])
{
cout<<"a[]:";
for(int i=0;i<8;i++)
cout<<a[i]<<"\t";
cout<<endl;
}
int select(int a[],int left,int right,int k)
{
cout<<endl<<"select:left:"<<left<<" right:"<<right<<" k: "<<k<<endl;
cout<<"left不变 right不变"<<endl;
show(a);
int i,j,pivot,t;
if(left>=right) {
cout<<"left>=right a[left]"<<a[left]<<endl;
return a[left];
}
i=left;
j=right+1;
pivot=a[left];
cout<<"left:"<<left<<" a[left] = pivot: "<<pivot<<endl;
while(true)
{
cout<<endl<<"in while:"<<endl;
do{
i++;
}
while(a[i]<pivot);
do{
j--;
}while(a[j]>pivot);
cout<<"now pivot is "<<pivot<<" 从left到i都小于pivot "<<i<<":"<<a[i]<<" 从j到right都大于pivot "<<j<<":"<<a[j]<<endl;
if(i>=j){
cout<<"i>=j break;"<<endl;
break;
}
t=a[i];
a[i]=a[j];
a[j]=t;
cout<<"swap "<<i<<" "<<j<<endl;
show(a);
}
cout<<endl<<"while结束 此时 j left k 是"<<j<<" "<<left<<" "<<k<<endl;
cout<<endl<<"最开始right-left+1等于总的元素个数,现在j往前移动 也就是比pivot大的数有(right-j)个"<<endl
<<"选第k小的元素 应该是有sum1=k-1个数比x小,有sum2=right-left+1-k个数比x大"<<endl
<<"此时 sum2=(right-j) 那么k=right-left+1-(right-j) =j-left+1 也就是此时是第(j-left+1)小 "<<endl
<<"需要让此时的 (j-left+1)==k 就求出了答案 "<<endl<<endl;
if(j-left+1==k) return pivot;
cout<<"然而 (j-left+1)!=k 需要继续寻找 "<<endl;
cout<<"a[left]是基准元素,j小于等于基准,因此交换 "<<endl ;
cout<<"交换 left j 的元素"<<endl;
a[left]=a[j];
a[j]=pivot;
show(a);
if(k<(j-left+1))
{
cout<<endl<<"左侧";
select(a,left,j-1,k);
}
else
{
cout<<endl<<"右侧";
select(a,j+1,right,k-j-1+left);
}
}
int a[]={1,4,5,3,2,6,8,7};
int k=2;
int main()
{
int left=0;
int right=7;
int res= select(a,left,right,k);
cout<<res<<endl;
return 0;
}
思考题:正元素 负元素排序
问题分析:
使数组中所有负数位于正数之前,空间效率:原数组的空间不可改变,临时变量尽可能少,在原数组上改变 不增加结果数组。时间效率:运算次数尽可能少。
一定会遍历一遍数组,且每个元素与0进行比较,因此从两侧开始,若左侧小于0 不变,大于0需要后移,右侧大于0不变,小于0需要后移。因此找到两位置left right ,进行交换。再以此为范围进行下一轮递归,直到left>=right 结束递归。
计算模型:
从[left,right]范围内进行二分。
(1)left>=right 结束循环
(2)从left开始找到第一个大于0的元素下标,更新left
(3)从right开始找到第一个小于0的元素下标,更新right
(4)若left>=right 结束循环
(5)否则从新的【left,right】范围进行递归排序
算法设计与分析
算法设计与描述 | 算法分析 |
输入:数组a[] | |
输出:排序后数组 | |
void fun(int a[],int left,int right) if(left<right) | (1)输入规模n (3) 时间复杂度 按最坏情况 每交换一次 都只能纠正2个元素的位置,则需要递归n/2次 : T(n)= T(n-2) + c =T(n-4) + 2*c =T(n-6)+3*c 共n/2次 T(n)=O(n) 空间复杂度:则S(n)= n/2 +2; |