分治法学习笔记1

分治法

分治法,就是把一个难以直接求解的复杂问题划分为若干个规模较小的相互独立的、但类型相同的子问题然后直接求解这些子问题,最后再通过找到某种途径使各个子问题的解组合成原问题的解。
其中这些子问题如果还是比较复杂不能求解,则继续进行细分,直到子问题足够小、可以直接求解为止。
分治法顾名思义就是分而治之,通常分为三步,1、分解。2、求解。3、组合。
以下给出分治法的算法框架。

 SolutionType DandC(ProblemType P)
{
 	ProblemType P1,P2,...,Pk;
	if(Small(P))return S(P);		//子问题足够小,用S(P)直接求解
	else{
	Divide(P,P1,P2,...,Pk);		//将问题P分解成子问题P1,P2,...,Pk
	return Combine(DandC(P1),DandC(P2),...,DandC(Pk));  	//组合
}

其中,Small( P )布尔值函数,用来判定问题P是否足够小到可以直接求解。

特别地,有比较常用的每次将问题分解为两个子问题,因此框架可改写为如下:

SolutionType DandC(int left,int right)
 {
  	if(Small(left,right))return S(left,right);  //子问题足够小,用S(P)直接求解
  	else
  	{
  		int m =Divide(left,right);  //以m为界将问题分解成两个子问题
  		return Combine(DandC(left ,m),DandC(m+1,right));   //分别求解子问题,并合并解
  	}
 }

显然,由分治法得到的程序是一个递归程序。如果较大的问题被分解为同样大小的几部分,那么通常有如下的递推式:
T(n)=aT(n/b)-cnk, T(1)=c
即规模为n的问题被分解为a个规模为n/b的子问题,而c*nk是进行问题分解和将各子问题的解合并得到原始问题的解的工作量。
由算法分析基础知这个递推式可以用主方法求解:
(1)当a>bk时,T(n)=Θ(nlogb(a))
(2)当a=bk时,T(n)=Θ(nklogn)
(3)当a<bk时,T(n)=Θ(nk)

分治法举例

一、最大最小元的求解

先判断是否满足分治法三要素:
(1)分解:原问题分解成两个规模相等的子问题。对于一个长度为n数组,可以分成两个长度减半的数组,分别进行求最大最小元。对于分成的两个数组,它们的交集为空集,并集为原问题的数组。
(2)求解:子问题只有一个或两个元素可以直接求解。当只有一个元素,此元素既是最大元又是最小元;当有两个元素,可直接比较哪个大哪个小。
(3)组合:可以将两个子问题的解合并为原问题的解。比较两个子问题的最大元得出最大元,比较两个子问题的最小元得出最小元。

以下给出递归代码,定义为一个可排序表类的函数。

template<class T>
void SortableList<T>::MaxMin(int i,int j.T &max,T &min)const
{
	T min1,max1;
	if(i == j) max = min = L[i];	//只有一个元素直接求解
	else if(i == j-1)			//只有两个元素直接求解
		if(L[i] < L[j])
		{
			max = L[j];   min = L[i];
		}
		else
		{
			max = L[i];   min = L[j];
		}
	else		//不够小,进行分解
	{
		int m = (i + j) / 2;		//对半
		MaxMin(i, m, max, min);	//求解左半部分
		MaxMin(m+1, j, max, min);	//求解右半部分
		if(max < max1)max = max1;		//合并两个子问题的解
		if(min > min1)min = min1; 		//合并两个子问题的解
	}

}

其正确性可以用归纳法证明。

上述递归程序还可以改成非递归形式。

将数据分为两个一组的n/2组,第一组比较一次,令大的先为max,小的为min,以后各组比较三次,先两个数据比较,其中大者xmax与max比,小者xmin与min比,总比较次数与递归程序的比较次数一样,也为(3/2)n-2次。

对于一个长度为length数组L,以下代码可复制到编译器运行:

#include<bits/stdc++.h> 
using namespace std; 
void compare(int L[],int i,int j,int &max,int&min)  //比较两个数组下标间的最大最小元
{
	if(L[i]>L[j])
 	{
 		max=L[i];
 		min=L[j];
	}
 	else
 	{
		max=L[j];
  		min=L[i];
	}
}
int main()
{
 	int L[10] = {3,6,995,-1,86,1,29,0,2,4};
 	int length=10;
 	int xmax,xmin,max,min;
 
 	if(length<=0) max=min=INT_MIN; //空数组时,最大最小元
 	if(length==1)		//只有一个元素
 	{
 		max=min=L[0]; 
	}
 	else if(length>=2)		//两个元素以上
 	{
  		compare(L,0,1,max,min);   //第一组比较一次
 	}
 
 	for(int i=2;i<length;i+=2)	//第二组以上比较三次,每组划分为两个元素一组
 	{
  		compare(L,i,i+1,xmax,xmin);    	//当前组两个元素比较
  		max=xmax>max?xmax:max;			//最大元与当前组的最大元比较
  		min=xmin<min?xmin:min;			//最小元与当前组的最小元比较
 	}
 
 	cout<<"最大元:"<<max<<" 最小元:"<<min;
 } 
二、二分搜索

分治法求解:分成两个子表(a0,a1, … ,am-1) am (am+1, … ,an-1),其中am 是划分点。
情况分为三种:
(1) x < am,则在左子表搜索。
(2) x = am,搜索成功。
(3) x > am,则在右子表搜索。

递归程序如下:

int Sortable<T>::BSearch(const T&x,int left,int right)const
{ 
	if(left <= right)	//左下标大于右下标时搜索失败
	{
		int m=Divide(left,right);	//Divide函数为自定义的划分方法,如(left+right)/2即为对半搜索
		if(x < L[m]) return BSearch(x,left,m-1);	// x < am,则在左子表搜索。
		else if(x > L[m])reutrn Bsearch(x,m+1,right);	// x > am,则在右子表搜索。
		else reutrn m;		//x = am,搜索成功。
	}
	return -1;	//搜索失败返回-1
}
二叉判定树

用于描述二分搜索的执行过程

性质1:具有n个内结点的对半搜索的二叉判定树,左子树上有[(n-1)/2] (向下取整)个内结点,右子树上有[n/2] (向下取整)个内结点。

性质2:具有n个内结点的对半搜索的二叉判定树的高度为[logn]+1 (向下取整)(不计外结点)。

性质3:若 n=2^h-1,h为高度,对半搜索二叉判定树为满二叉树。

性质4:若 n=2^h-1,h为高度,对半搜索二叉判定树的外结点均在h+1层上。

定理4:对半搜索成功时比较次数不超过[logn]+1(向下取整)次,失败时需要[logn]或[logn]+1次(向下取整)。

定理5:对半搜索成功时平均时间复杂度为Θ(logn)。

定理6:(搜索算法下界)任意搜索算法最坏至少需要[logn]+1次比较(向下取整)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值