算法设计与分析笔记(一)递归与分治策略

一》递归:直接或间接地调用自身的算法

EG:

1》阶乘定义 n!=n(n-1)! (n>0);

pubic static int factorial(int n ){
   if(n==0)  return 1;
   else
          return n*factorial(n-1);

}

2》FiBonacci数列

public static int fibo(int n){
 if(n<=1)return 1;
 else
       fibo(n-1)+fibo(n-2);
}

3》排列问题(一个集合的全排列)

设p(X)表示集合X的全排列,则rp(X)表示集合{r,X}的全排列,则一个集合的全排列可一次递归到只有一个元素的集合的全排列。

设Ri表示R-{ri},则p(R)=(r1)p(R1),(r2)p(R2),......(rn)p(Rn);

public void perm(char *list,int k,int m){
	//产生list[k:m]的全排列
	if(k==m){	//只剩一个元素的时候
		for(int =0;i<=m;i++){
			printf("%c",list[i]);
		}
		printf("\n");
	}else{	//还有多的元素,递归产生排列
		for(int i=k;i<=m;i++){
			//将集合中每个元素都放到队首排列一次,以此递归。
			swap(list,k,i);	//交换位置
			perm(list,k+1,m);	//递归
			swap(list,k,i);		//恢复原位
		}
	}

}

4》整数划分问题

将正整数n表示为一系列正整数之和,n=n1+n2+.....nk,其中n1>=n2>=....nk>=1,k>1;

这种表示成为正整数n的划分,记为p(n),将最大加数n1不大于m的划分个数记为q(n,m).

分析:

1》若n=1,不论m何值,只有一种划分{1};

2》若m=1,不论n何值,只有一种划分{1,1,1,1,1......};

3》若n<m,因为没有负数的存在,所以f(n,m)=f(n,n);

4》若n=m,则可分两种情况讨论:

a》划分中包含m(=n),所以只有一种划分即{n};

b》划分中不包含m,即最大只能是m-1,即为f(n,m-1);

5》若n>m,也分两种情况讨论

a》划分中包含了m,则为{m,{m1,m2,m3......}},即f(n-m,m);

b》划分中不好m,则为f(n,m-1);

综上:

f(n,m)=1,n=1orm=1

f(n,m)=f(n,n)n<m

f(n,m)=1+f(n,m-1)n=m

f(n,m)=f(n-m,m)+f(n,m-1)n>m

#include<iostream>
using namespace std;

int f(int n,int m){
	if(n==1 ||m==1)
		return 1;
	else if(n<m)
		return f(n,n);
	else if(n==m)
		return (1+f(n,m-1));
	else {
		return (f(n-m,m)+f(n,m-1));
	}

}

int main(){
	int n,m;
	scanf("%d %d",&n,&m);
	printf("result=%d\n",f(n,m));
	return 0;
}

5》汉诺塔问题。

先考虑两层塔的移动,发现需要三个动作。考虑,三层塔的移动,发现出现一定的重复性,将上面两层看成一个整体,又变层两层塔移动,以此类推发现,任何n层次的移动都可以划分为第n层与前面(n-1)层的递归。

public void hanoi(int n,int a,int b,int c){
	//n表示总的层数,从a借助c移动到b
	if(n>0){
		hanoi(n-1,a,c,b);
		move(a,b);
		hanoi(n-1,c,ba);
	}
}

总结:

1》必须有终止条件,否则陷入无限递归,最后栈溢出。

2》考虑是否可以用递归解决时,思考减小问题规模是否可以采取相同的步骤且后一步的结果依赖前一步的结果产生递归。也可以从问题最简单的情况开始,逐渐扩大规模发现规律。

二》分治:将一个规模为n的问题分解为k个规模较小的子问题且互相独立且与原问题相同,递归解决子问题,然后合并得到原问题解。

divide-and-conquer(P){
	if(|P|<=n)  adhoc(P);
	divide P into p1,p2,p3...pk;
	for(int i=1;i<=k;i++)
		yi=divide-and-conquer(pi);
	return merge(y1,y2....yk);
}

其中n时阀值,当问题规模达到阀值时,可直接得到解并返回解

1》二分搜索算法

思想:在一个有序数组中搜索一个元素,通过一次比较(取值范围的中间数,即(left+right)/2  )将范围缩小一半,直到找出元素或无解。因为每次搜索都减小一半规模,所以复杂度为O(logn).

(规模减小,求解步骤重复,阀值确定)

//二分查找
//a为有序升序数组,k为查找的元素,n为数组大小
int f(int *a,int k,int n){
	int left=0,right=n-1;
	while(left<=right){
		int mid=(left+right)/2;
		if(k == a[mid]){
			return mid;
		}
		else if(k<a[mid]){
			right=mid-1;
		}else{
			left=mid+1;
		}
	}

	return -1;	//未找到
}

2》合并算法(归并算法)

思想:将待排序元素氛围两个大小大致相同的2个子集合,分别对两个子集合排序,最终将排序好的子集合合并为所要的排序集合。(将规模减小了一半n/2,且小规模的求解同上,直到只剩一个元素)

void MerSort(int *a,int n){	//n为数组长度
	int *p=(int *)malloc(sizeof(int)*(n+1));
//问题的解决需要一个临时数组,临时数组只分配一次,减小开销
	mergeSort(a,0,n-1,p);
}
//分治
void mergeSort(int *a,int left,int right,int *p){	//left,right都表示下标
	if(left<right){
		int mid=(left+right)/2;
		mergeSort(a,left,mid,p);
		mergeSort(a,mid+1,right,p);
		merge(a,left,mid,right,p);
	}else{

	}
}
//合并
void merge(int *a,int left,int mid,int right,int *p){
	int i=left,j=mid+1;
	int pp=left;
	while(i<=mid && j<=right){
		if( a[i]<a[j]){
			p[pp++]=a[i];
			i++;
		}else {
			p[pp++]=a[j];
			j++;
		}
	}

	while(i<=mid){
		p[pp++]=a[i++];
	}
	while(j<=right){
		p[pp++]=a[j++];
	}
/*	测试验证
	for(int h=left;h<=right;h++){
		printf("%d,",p[h]);
		a[h]=p[h];
	}
	printf("\n");
*/
}

3》快速排序

思想:选取待排序的某个元素做划分,比它大的放右边,比它小的放左边,剩下的空位就是它的位置。不断减小问题规模

int p(int *a,int left,int right){
	int key=a[left];
//	printf("**** %d ***   ",a[left]);
	while(left<right){
		//比key小的都移到左边
		while(left<right && a[right]>=key)
			right--;
		a[left]=a[right];
		//比key大的都移到右边
		while(left<right && a[left]<=key)
			left++;
		a[right]=a[left];
	}
	//当left==rght时的位置即为key的位置
//	printf("a[%d]=%d\n",left,key);
	a[left]=key;
	return left;
}

void qSort(int *a,int left,int right){
	if(left<right){<span style="white-space:pre">		</span>//递归终止条件一定不要忘记,惨痛教训
		int pp=p(a,left,right);
		qSort(a,left,pp-1);
		qSort(a,pp+1,right);
	}
}

4》大整数的乘法

思路:将大整数分为两段,每段n/2位,则x=A*2^n/2+B,y=C*2^n/2+D,相乘后去括号可得到XY=AC*2^n+(AD+BC)*2^n/2+BD;

T(n)={O(1),n=1;   4T(n/2)+O(n),n>1;}

进过数学家的改进发现XY=AC2n+[(A-B)(D-C)+AC+BD]2n/2+BD

T(1)=1

T(n)=3T(n/2)+cn.

#define sign(num) (num)>0?1:-1;

//x与y都是n位十进制的大整数乘法模型
//要实现真正的大整数,要把数据结构改为数组存储
int Mul(int x,int y,int n){
	int s=sign(x)*sign(y);
	int x=abs(x);
	int y=abs(y);

	if(x==0 || y== 0)
		return 0;
	if(n==1){
		return x*y;
	}else{
		int xl=x/(int )pow(10,(int)n/2);
		int xr=x-x1*(int )pow(10,(int)n/2);
		int yl=y/(int )pow(10,(int)n/2);
		int yr=y-y1*(int )pow(10,(int)n/2);

		int m1=Mul(xl,yl,n/2);
		int m2=Mul(xr,yr,n/2);
		int m3=Mul(xl-xr,yl-yr,n/2);
		return s*(m1*(int)pow(10,n)+m3*m1*m2*(int)pow(10,n/2)+m2);
	}

}

详细内容参考博客:http://blog.chinaunix.net/uid-20563078-id-1636245.html

PS:对于不是分治算法的解法,我们一般采用链表或数组存储数据,模仿手工竖式计算方法,但是每次仅2个一位十进制数相乘的效率太低,改进版本一是采用一种列表法(参考博客:http://blog.csdn.net/zxasqwedc/article/details/12399543

可以采用千位进制的乘法,即将大整数分为每个元素是一个不超过1000的三位十进制数,每次相乘是三位数三位数相乘,具体查看这个博客http://www.xuebuyuan.com/1601102.html

待解决问题:将大整数分为多段,而不是2段,复杂性有何变化?是否优于2段????

5》Stassen矩阵乘法

高深的数学问题,写出来也只是搬运工,直接贴参考博客的连接:http://blog.sina.com.cn/s/blog_6eea1bc20100mfp3.html

6》棋盘覆盖

问题描述

在一个2^k×2^k 个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。在棋盘覆盖问题中,要用图示的4种不同形态的L型骨牌覆盖给定的特殊棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。

思路:这个提供关键还是要懂一点小技巧,把两个子问题变成一个子问题。按分治思想又是正方形,理当将其分成四个小正方形,即4个2^k-1*2^k-1的子棋盘。特殊方格位于其中一个,其余三个无特方格,为了将其转化成特殊方格,可以用一个L型骨牌覆盖3个棋盘的汇合处,从而转化成4个特殊方格棋盘的覆盖问题。当递归到最后一个棋盘只有一个方格时则停止递归返回。为了表示出覆盖方法,将骨牌编号。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define SIZE 100

int tile=0;
int board[SIZE][SIZE];	//棋盘

//tr,tc是棋盘左上角行号,列号。dr,dc是特殊方格行号、列号
//size是棋盘大小
void chessBoard(int tr,int tc,int dr,int dc,int size){

	if(size == 1)	//停止递归
		return;

	int t=tile++;	//骨牌标号
	int s=size/2;	//分割棋盘

	//左上角棋盘
	if(dr<tr+s && dc<tc+s){
		//特殊方格在此
		chessBoard(tr,tc,dr,dc,s);
	}else{
		board[tr+s-1][tc+s-1]=t;	//添加特殊方格
		chessBoard(tr,tc,tr+s-1,tc+s-1,s);
	}

	//右上角棋盘
	if(dr<tr+s && dc>=tc+s){
		chessBoard(tr,tc+s,dr,dc,s);
	}else{
		board[tr+s-1][tc+s]=t;
		chessBoard(tr,tc+s,tr+s-1,tc+s,s);
	}

	//左下角
	if(dr>=tr+s && dc<tc+s){
		chessBoard(tr+s,tc,dr,dc,s);
	}else{
		board[tr+s][tc+s-1]=t;
		chessBoard(tr+s,tc,tr+s,tc+s-1,s);
	}

	//右下角
	if(dr>=tr+s && dc>=tc+s){
		chessBoard(tr+s,tc+s,dr,dc,s);
	}else{
		board[tr+s][tc+s]=t;
		chessBoard(tr+s,tc+s,tr+s,tc+s,s);
	}
}

void main(){
	memset(board,0,sizeof(board));
	board[3][3]=100;
	chessBoard(1,1,3,3,16);
	for(int i=1;i<=16;i++){
		for(int j=1;j<=16;j++)
			printf("%4d",board[i][j]);
		printf("\n");
	}
	printf("\ntile=%d\n",tile);

}

7》循环赛日程表

设有n=2k个选手参加比赛,要求设计一个满足一下要求的比赛日程表:

(1)每个选手必须与其他的n-1个选手个比赛一次;

(2)每个选手每天只能赛一次 。

按此要求可以把比赛日程表设计成一个n行n-1列的二维表,其中第i行第j列表示第i个选手在 第j天比赛的选手。

思路仍然是要发现能重复解决的办法,这个也是看题解的

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define SIZE 100
int a[SIZE][SIZE];

void table(int k,int a[][SIZE]){
	int temp;
	int n=2;	//一开始n=2,两个人的情况
	a[1][1]=1;	a[1][2]=2;
	a[2][1]=2;	a[2][2]=1;

	for(int t=0;t<k;t++){
		temp=n;
		n=n*2;

		int i,j;
		//添加左下角数据
		for(i=temp+1;i<=n;i++){
			for(j=1;j<=temp;j++){
				a[i][j]=a[i-temp][j]+temp;
			}
		}
		//添加右下角数据
		for(i=temp+1;i<=n;i++){
			for(j=temp+1;j<=n;j++){
				a[i][j]=a[i-temp][j-temp];
			}
		}
		//添加右上角数据
		for(i=1;i<=temp;i++){
			for(j=temp+1;j<=n;j++){
				a[i][j]=a[i+temp][j-temp];
			}
		}

	}
}

void main(){
	table(3,a);
	for(int i=1;i<=8;i++){
		for(int j=1;j<=8;j++)
			printf("%4d",a[i][j]);
		printf("\n");
	}
}

附上两个参考博客,写法略有不同,还没有I完全搞懂

1:递归写法http://www.2cto.com/kf/201411/351886.html

2:http://blog.csdn.net/liufeng_king/article/details/8488421

下列代码详解请看博客链接2

思路:这个算法将整个安排从前面的三步变成了两步:算法一本来是填充好两个人比赛的安排,然后开始1)左上角复制到右下角,2)左上角值增加temp后复制到左下角,3)左下角复制到右上角。算法二现将第一行数据填充好,依次按1)左上角复制到右下角,2)右上角复制到左下角。

比较:算法一更为直观,便于理解。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#define SIZE 100

int a[SIZE][SIZE];

void delay(int a[][SIZE],int k){
	int n=1;
	for(int i=1;i<=k;i++)	//棋盘总大小
		n=n*2;
	for(i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
<span style="white-space:pre">			if(a[i][j]!=0)</span>
			<span style="white-space:pre">	</span>printf("%4d",a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

void table(int k,int a[][SIZE]){
	int n=1;
	for(int i=1;i<=k;i++)	//棋盘总大小
		n=n*2;

	for(i=1;i<=n;i++)	//填充第一行
		a[1][i]=i;

	int m=1;
	for(int s=1;s<=k;s++){	//每次按2倍增长,只有k次即可填充完
		n=n/2;	

		for(int t=1;t<=n;t++){	//每行每次填充m列数据,共需要n次
			for(int i=m+1;i<=2*m;i++){
				for(int j=m+1;j<=2*m;j++){
					a[i][j+(t-1)*2*m]=a[i-m][j+(t-1)*2*m-m];	//将左上角数据填充到右下角
					a[i][j+(t-1)*2*m-m]=a[i-m][j+(t-1)*2*m];	//右上角数据填充到左下角
				}
			}
		//此段代码帮助理解算法运算过程
		printf("t=%d\n",t);
		delay(a,k);
		}
		m=m*2;	//倍增

	}

}

void main(){
	table(3,a);
}

8》线性时间选择

9》最接近点对问题

时间: 2024-10-20 13:09:14

算法设计与分析笔记(一)递归与分治策略的相关文章

算法设计与分析基础(第3版)读书笔记(及几处翻译上的错误~~)

算法设计与分析基础(第3版) p16 in-place翻译为'在位'?'就地'更合适点 p38 amortized应翻译为'均摊','摊销'这个词简直莫名其妙(可能因为翻译是做算法交易导致的?) p64 迭代优于递归(迭代始终是增量式的,而递归就没办法增量了,除非能够dump整个运行时栈) p73 通过算法可视化得到一个更好的非递归算法(人的图像认知直觉思维?) p79 验证一个拓扑是环.星.还是团?(这个地方有点意思,因为我想到了动态的Verify) p87 凸包问题:从数据结构上讲,Set<

递归与分治策略(一)---算法设计与分析

递归与分治策略(一) 简而言之,递归就是自己调用自己. 递归算法:直接或者间接地调用自身的算法. 递归函数:用函数自身给出定义的函数. 注意:每个递归函数都必须有非递归定义的初始值,以确保递归函数完成计算. 下面通过两个例子来介绍递归的特点 例1 阶乘函数 阶乘函数递归地定义为: n!=1   (n=0) 或者 n!=n(n-1)!  (n>0) 下面用一段简单的Java代码实现 这里是递归实现: public static int facterial(int n) { if (n == 0)

计算机算法设计与分析之递归与分治策略——二分搜索技术

递归与分治策略 二分搜索技术 我们所熟知的二分搜索算法是运用分治策略的典型例子,针对这个算法,先给出一个简单的案例. 目的:给定已排好序的n个元素a[0:n-1],现要在这n个元素中找出一特定的元素x. 我们首先想到的最简单的是用顺序搜索方法,逐个比较a[0:n-1]中元素,直至找出元素x或搜索遍整个数组后确定x不在其中.这个方法没有很好地利用n个元素已排好序的这个条件,因此在最坏的情况下,顺序搜索方法需要O(n)次比较. 而二分搜索方法充分利用了元素间的次序关系,采用分治策略,可在最坏情况下用

(转)常用的算法设计与分析-一夜星辰的博客

算法设计与分析 分治法 思想 1. 将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同.递归地解这些子问题,然后将各子问题的解合并得到原问题的解. 2. divide-and-conquer(P) { if(|P| <= n0)adhoc(P); divide P into samller subinstances P1,P2...,Pk; for(int i = 1;i < k;i++) { yi = divide-and-conquer(Pi); } retu

算法设计与分析 ------最近对问题与8枚硬币问题

利用减治法实现8枚硬币问题: 参考资料:http://blog.csdn.net/wwj_748/article/details/8863503    算法设计--八枚硬币问题 1 #include "stdafx.h" 2 #include <iostream> 3 #include <stdio.h> 4 using namespace std; 5 6 7 void eightcoin(int arr[]); 8 void compare(int a,in

算法设计与分析(屈婉玲)pdf

下载地址:网盘下载 算法设计与分析本教材为计算机科学技术专业核心课程"算法设计与分析"教材.<算法设计与分析>以算法设计技术和分析方法为主线来组织各知识单元,主要内容包括基础知识.分治策略.动态规划.贪心法.回溯与分支限界.算法分析与问题的计算复杂度.NP完全性.近似算法.随机算法.处理难解问题的策略等.书中突出对问题本身的分析和求解方法的阐述,从问题建模.算法设计与分析.改进措施等方面给出适当的建议,同时也简要介绍了计算复杂性理论的核心内容和处理难解问题的一些新技术. &

《计算机算法设计与分析》v4 第1章 算法概述 算法实现题答案

博主今年刚上大三,正好开算法这门课.由于博主本人比较喜欢算法但又比较懒,啃不动算法导论,所以决定拿这本书下手. 这本书是王晓东的第四版<计算机算法设计与分析>.初步打算将每章后面的算法题都用代码实现. 有些题跟某个ACM题目很像,我会把该ACM题的链接贴上.有的题没OJ交所以可能是错的.如有发现,还望指出. 1-1 统计数字问题 http://poj.org/problem?id=2282 这个题要按位分解,一位一位的来处理. #include<iostream> #include

【通知】《算法设计与分析》实验课、理论课补课、考试时间、加分等安排 及 个人目标设定

Logistic回归为概率型非线性回归模型,是研究二分类观察结果与一些影响因素之间关系的一种多 变量分析方法.通常的问题是,研究某些因素条件下某个结果是否发生,比如医学中根据病人的一些症状来判断它是 否患有某种病. 在讲解Logistic回归理论之前,我们先从LR分类器说起.LR分类器,即Logistic Regression Classifier. 在分类情形下,经过学习后的LR分类器是一组权值,当测试样本的数据输入时,这组权值与测试数据按 照线性加和得到 这里是每个样本的个特征. 之后按照s

算法设计与分析——回溯法算法模板

以深度优先方式系统搜索问题解的算法称为回溯法.在回溯法中,解空间树主要分为了四种子集树.排列树.n叉树和不确定树. 在<算法设计与分析课本>中介绍了11个回溯法的问题样例,这里根据解空间树的类型做一个分类. 子集树 装载问题 符号三角形问题 0-1背包问题 最大团问题 算法模板: void backtrack(int t) { if(搜索到叶子结点) { return; } for(i=0; i<=1; i++) //01二叉树 { if(满足约束函数和限界函数)//剪枝 { backt