【算法设计zxd】第5章分治法

目录

分治算法策略的设计模式

分治思想:

分治算法求解问题的步骤:

设计模式

算法分析

二分查找算法

思考题

计算模型:

时间复杂度分析:

代码:

分治*大数乘法:

【例5-2】设X, Y是两个n位的十进制数,求X*Y

 问题分析:

1.1 计算方法:

 2.1 计算方法:

思考题:

 算法分析:

代码:

思考题:二分治法 和 VS算法 矩阵相乘

算法效率:

代码:

 棋盘覆盖问题:

【例5-4】残缺棋盘

问题分析:s=size/2 分治

计算模型​

算法分析

算法设计与描述:​

代码: 

 选择性问题:——非等分

一般性描述:

问题分析:

选择性问题:选第k小值

计算模型

算法分析

代码:

思考题:正元素 负元素排序

计算模型:

思考题:用分治法 求 数列的最大子段和


分治算法策略的设计模式

分治思想:

把一个较大的问题分解成几个与原问题相似的 子问题,
找到求出这几个子问题的解法后,
再以适当的方法组织,把它们合成求整个问题的解。

分治算法求解问题的步骤:

(1) 分解:将要解决的问题划分成若干规模较小的同类问题;
(2) 求解:当子问题划分得足够小时,用较简单的方法解决;
(3) 合并:按原问题的要求,将子问题的解逐层合并构成原问题的解。

设计模式

【算法设计zxd】第5章分治法_第1张图片

算法分析

用DC求解规模为n0 的问题耗费1个单位时间,用Merge合并 k个子问题的所需的时间为f(n)
,则分治算法的时间复杂度 为:

 根据主定理有

 

原问题分为多少个子问题比较合适?每个子问题是否规模要多大?

【子问题平衡原理】划分规模:等长>不等长规模 

二分查找算法

【例5-1】 设有n个元素的集合a[0]~a[n-1]是有序的,设计算
法从这n个元素中找出值为x的特定元素。
问题分析
思想:将n个元素分成个数大致相同的两半,取a[n/2]与x作
比较。如果x=a[n/2],则找到x,算法终止。如果x
在集合的前半部分继续查找,否则在后半部分查找。
计算模型
(1)中间下标为mid =(left+right)/2。
(2)找到待查数据,即有x==a[mid];
(3)暂未查到数,若xa[mid],则
left=mid+1。
算法描述
输入:x和n个元素的数组a[n]
输出:与x匹配的数组元素下标或未找到(下标为-1)
【算法设计zxd】第5章分治法_第2张图片

【算法设计zxd】第5章分治法_第3张图片

思考题

 【算法设计zxd】第5章分治法_第4张图片

计算模型:

只剩一个数据时 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
#include
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

分治*大数乘法:

【例5-2】设X, Y是两个n位的十进制数,求X*Y

 

 问题分析:

1.1 计算方法

 1.2 时间复杂度:

 【算法设计zxd】第5章分治法_第5张图片

 2.1 计算方法:

 

 2.2 时间复杂度:

【算法设计zxd】第5章分治法_第6张图片

 

 计算模型

(1) 计算公式

 (2)递归结束条件

当x=0或y=0时,结果为0。
当n=1时,返回x*y 。

思考题:

算法描述

输入:两个n位十进制数x,y 

核心操作:三个乘法

时间复杂度:T(n)=3*T(n/2)+c*n 

设a=3,b=2 ,f(n)=cn, n^{log_2 3-log_2 3/2} = n,

有f(n)=\Theta ( n^{log_2 3-log_2 3/2} )= \Theta (n),\varepsilon=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, n^{log_2 3-log_2 3/2} = n,

有f(n)=\Theta ( n^{log_2 3-log_2 3/2} )= \Theta (n),\varepsilon=log_2 3/2 >0 符合主定理(1),因此

 

代码:

#include
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<

思考题:二分治法 和 VS算法 矩阵相乘

【算法设计zxd】第5章分治法_第7张图片

算法效率:

普通的二分治算法:

T(n)=O(1) n=2

T(n)=8*T(n/2)+\Theta(n^2) n>2

设a=8,b=2,f(n)=\Theta(n^2),n^(log_2 8 -1)=n^2.  

有f(n)=\Theta(log_2 8 -1)=\Theta(n^2) 取 \varepsilon=1>0 

根据主定理 T(n)=\Theta(n^3) 

VS算法:【8次->7次乘法 m不唯一,但也增加了加法运算量】

T(n)=O(1) n=2

T(n)=7*T(n/2)+\Theta(n^2) 

设a=7,b=2,f(n)=\Theta(n^2),n^(log_2 7 -1)=n^2.  

有f(n)=\Theta(log_2 7 -0.8)=\Theta(n^2) 取\varepsilon =0.8>0  

根据主定理 T(n)=\Theta(n^2.8)

7个乘法,18个加法 

【算法设计zxd】第5章分治法_第8张图片

因此VS算法比普通二分治算法效率高

代码:

#include
using namespace std;

class Mat{
	
public:
	int **m;
	int n;
	
	Mat(int nn){
//		cout<<"Mat:"<n;
		for(int i=0;im[i][j]=x[i][j];
			}
		}
	}
	void show()
	{
		for(int i=0;im[i][j];
			}
		}
		return c;
	}
	Mat operator - (Mat &a)// 定义重载运算符"-"的友元函数
	{
		Mat c(a.n);
		for(int i=0;im[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

 棋盘覆盖问题

【例5-4】残缺棋盘

残缺棋盘是一个有2^ k ×2^ k (k≥1)个方格的棋盘,其中恰
有一个方格残缺。如图给出k=1时各种可能的残缺棋盘,其
中残缺的方格用阴影表示。

【算法设计zxd】第5章分治法_第9张图片

这样的棋盘称作“三格板”,残缺棋盘问题就是要用这四
种三格板覆盖更大的残缺棋盘。在此覆盖中要求:
1)两个三格板不能重叠
2)三格板不能覆盖残缺方格,但必须覆盖其他所有的方格
在这种限制条件下,所需要的三格板总数为(2^ k ×2^ k -1 )/3。【(方格数-1)/3】

问题分析:s=size/2 分治

【算法设计zxd】第5章分治法_第10张图片

分4种情况:
1. 若坏格子在左上角,则把图5-2中①号放置在二分后的中间
位置,如图(2)
2. 若坏格子在右上角,则把图5-2中②号放置在二分后的中间
位置,如图(3)
3. 若坏格子在左下角,则把图5-2中③号放置在二分后的中间
位置,如图 (4)
4. 若坏格子在右下角,则把图5-2中④号置在二分后的中间位
置,如图(5)

如何确定棋盘,如何区分棋盘?

——用棋盘左上角进行定位,用棋盘边长划定范围。

计算模型【算法设计zxd】第5章分治法_第11张图片

(1)棋盘:用二维数组CBoard[n][n]存储
(2)棋盘的识别
• tr 棋盘中左上角方格所在行。
• tc 棋盘中左上角方格所在列。
• dr 残缺方块所在行。
• dc 残缺方块所在列。
• size 棋盘的行数或列数。
覆盖残缺棋盘所需要的三格板数目为:( size^ 2 -1 ) / 3
将这些三格板编号为1到( size^ 2 -1 ) / 3。则将残缺棋
盘三格板编号的存储在数组 CBoard[n][n] 的对应位置中

(3)计算过程
设s=size/2,当s或size<2时,覆盖结束。
• 当 行坐标dr时,坏格在左上子棋盘
中,用①(tr+s-1, tc+s);(tr+s, tc+s-1) ; (tr+s, tc+s)。//红色部分

• 当dr

中,用② (tr+s-1, tc+s-1); (tr+s, tc+s-1) ; (tr+s, tc+s)。
• 当dr≥tr+s and dc
中,用③(tr+s-1, tc+s-1); (tr+s-1, tc+s) ; (tr+s, tc+s)。
【算法设计zxd】第5章分治法_第12张图片

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

用④ (tr+s-1, tc+s-1); (tr+s-1, tc+s) ; (tr+s, tc+s-1)。

【算法设计zxd】第5章分治法_第13张图片

算法分析

【算法设计zxd】第5章分治法_第14张图片

 

 【算法设计zxd】第5章分治法_第15张图片

算法设计与描述:
【算法设计zxd】第5章分治法_第16张图片

代码: 

#include
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=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:"<

 选择性问题:——非等分

选最大值、最小值、选中位数、选第二大值

一般性描述:

设A是含有n个元素的集合,从A中选出第k小的元素,其中
1≤k≤n。这里的第k小是指当A中元素从小到大排序之后,
第k个位置的元素,当k=1时,选出的是A中的最小值,当
k=n时,选出的就是最大值。

问题分析:

(1)排序--找第k小的元素,时间渐近复杂度为O(nlogn)——高级排序算法 极限
(2)蛮力算法--当k=1或k=n时【最大或最小】,一趟即可找到解,时间渐近复杂度只有O(n)。
(3)二分治策略--当k=2时,将原数列分为两个子集,每个子 集各选出一个最小值和第二小值,从这个4个数字里可找到当前的最小值

选择性问题:选第k小值

计算模型

选择pivot=a[left]值,j=right对集合进行二分
(1) j-left=k-1,则分界数据就是选择问题的答案
(2) j-left>k-1,则选择问题的答案继续在左子集中找,问题规模变小了。
(3) j-left

算法分析

(1)最坏情况下的复杂性是O(n 2 ),left 总是为空,第k个元素总是位于 right
子集中。
(2)设n=2 k ,算法的平均复杂性是O (n+logn)。若仔细地选择分界元素,则
最坏情况下的时间开销也可以变成O(n)。
(3)它与本章第一个例子非常相似,只对一半子集进行搜索,所不同的时,
由于pivot点选择的不同,多数情况下它是非等分的。
算法设计与分析
输入
输出

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]         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
using namespace std;

void show(int a[])
{
	cout<<"a[]:";
	for(int i=0;i<8;i++)
		cout<=right) {
		cout<<"left>=right a[left]"<pivot);
		cout<<"now pivot is "<=j){
			cout<<"i>=j break;"<

思考题:正元素 负元素排序

【算法设计zxd】第5章分治法_第17张图片

问题分析:
使数组中所有负数位于正数之前,空间效率:原数组的空间不可改变,临时变量尽可能少,在原数组上改变 不增加结果数组。时间效率:运算次数尽可能少。

一定会遍历一遍数组,且每个元素与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) {
        return ;
    }
    while(a[left]<0) ++left;
    
    while(a[right]>0) --right;

    if(left     {
        t=a[left];
        a[left]=a[right];
        a[right]=t;
        fun(a,left,right);
    }
    else return ;
}

(1)输入规模n
(2)核心语句:比较和交换语句

(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;

 【算法设计zxd】第5章分治法_第18张图片

思考题:用分治法 求 数列的最大子段和

你可能感兴趣的:(算法zxd,算法,动态规划,算法)