枚举-递归-回溯

枚举-递归-回溯
枚举-递归-回溯

一、暴力求解法(枚举法/ 穷举法)

概念:什么是枚举法?

在进行归纳推理时,如果逐个考察了某类事件的所有可能情况,因而得出一般结论,那么这结论是可靠的,这种归纳方法叫做枚举法。即,把所要解决问题的所有可能性都列举出来,一一试验。

枚举应用简单举例:求1~100之间的素数;求水仙花数;鸡兔同笼问题;百元买百鸡问题;整数(分数)拆分问题;排列问题……

枚举算法因为要列举问题的所有可能的答案,所有它具备以下几个特点:

1、得到的结果肯定是正确的;

2、可能做了很多的无用功,浪费了宝贵的时间,效率低下。

3、通常会涉及到求极值(如最大,最小,最重等)。

4、数据量大的话,可能会造成时间崩溃。

采用枚举算法解题的基本思路:

(1)确定枚举对象、枚举范围和判定条件;

(2)一一枚举可能的解,验证是否是问题的解

下面我们就从枚举算法的的优化、枚举对象的选择以及判定条件的确定,这三个方面来探讨如何用枚举法解题。

例1:百元买百鸡问题:有一个人有一百块钱,打算买一百只鸡。到市场一看,大鸡三块钱一只,小鸡一块钱三只,不大不小的鸡两块钱一只。现在,请你编一程序,帮他计划一下,怎么样买法,才能刚好用一百块钱买一百只鸡?

算法分析:

我们以三种鸡的个数为枚举对象(分别设为x,y,z),以三种鸡的总数(x+y+z)和买鸡用去的钱的总数(x*3+y*2+z/3)为判定条件,穷举各种鸡的个数。

(1)基本算法:

for (x=0;x<=100;x++)

for (y=0;y<=100;y++)

for(z=0;z<=100;z++)

if(x+y+z==100 && z%3==0 && x*3+y*2+z/3==100)输出x,y,z

(2)优化算法:只需要枚举2种鸡x(x<=33)和y(y<=50),第3种根据约束条件100-x-y可得:

for (x=0;x<=33;x++)

for (y=0;y<=50;y++)

{

Z=100-x-y;

if (z%3==0 && x*3+y*2+z/3==100)输出x,y,z

小结:对于枚举算法,加强约束条件,缩小枚举的范围,是程序优化的主要考虑方向。

1.枚举对象的选择问题:

在枚举算法中,枚举对象的选择是非常重要的,它直接影响着算法的时间复杂度,选择适当的枚举对象可以获得更高的效率。如下例:

例2:将1,2...9共9个数分成三组,分别组成三个三位数,且使这三个三位数构成1:2:3的比例,试求出所有满足条件的三个三位数。例如:三个三位数192,384,576满足以上条件。

我们分别设三个数为x,2x,3x,以x为枚举对象,穷举的范围将大大缩小。

例3. 五猴分桃:五只猴子一起摘了一堆桃子,因为太累了,它们商量决定,先睡一觉再分.一会其中的一只猴子来了,它见别的猴子没来,便将这堆桃子平均分成5份,结果多了一个,就将多的这个吃了,并拿走其中的一份.一会儿,第2只猴子来了,他不知道已经有一个同伴来过,还以为自己是第一个到的呢,于是将地上的桃子堆起来,再一次平均分成5份,发现也多了一个,同样吃了这1个,并拿走其中一份.接着来的第3,第4,第5只猴子都是这样做的.......,

根据上面的条件,问这5只猴子至少摘了多少个桃子?第5只猴子走后还剩下多少个桃子?

算法分析:我们设总的桃子数为S0,五子猴子分得的桃子数分别为S1,S2,S3,S4,S5,则有以下关系式:S0 = 5*S1 + 1;4*S1 = 5*S2 + 1;4*S2 = 5*S3 + 1;4*S3 = 5*S4 + 1;4*S4 = 5*S5 + 1;

我们可以枚举桃子总数S0,从5开始直到满足条件,此时S0的值就是最少的总桃子数。对应程序如下:

int main(void)

{

int s[6] = {0};

int i;

for(s[0]=5; ;s[0]++)

{

s[1] = s[0] - 1;

if (s[1]%5) // (s[0] – 1)要能被5整除

else

s[1] /= 5;

s[2] = 4 * s[1] - 1;

if (s[2]%5) // (4 * s[1] - 1)要能被5整除

continue;

else

s[2] /= 5;

s[3] = 4 * s[2] - 1;

if (s[3]%5)

continue;

else

s[3] /= 5;

s[4] = 4 * s[3] - 1;

if (s[4]%5)

continue;

else

s[4] /= 5;

s[5] = 4 * s[4] - 1;

if (s[5]%5)

continue;

else

s[5] /= 5;

break; //很关键,什么时候结束枚举

}

printf("摘了%d个桃子, 剩下%d个桃子\n", s[0], s[5]*4);

for (i=0; i<6; i++)

printf("%d ", s[i]);

return 0;

}

程序输出:摘了3121个桃子, 剩下765个桃子。

根据程序结果我们知道循环体执行了3116次,同时我们可以知道第5个猴子分得255个桃子,所以如果枚举S5,则循环体只需执行了255次。对应程序如下:

#include

int main(void)

{

int s[6] = {0};

int i;

for(s[5]=1; ;s[5]++)

{

s[4] = 5 * s[5] + 1;

if (s[4]%4)

else

s[4] /= 4;

s[3] = 5 * s[4] + 1;

if (s[3]%4)

continue;

else

s[3] /= 4;

s[2] = 5 * s[3] + 1;

if (s[2]%4)

continue;

else

s[2] /= 4;

s[1] = 5 * s[2] + 1;

if (s[1]%4)

continue;

else

s[1] /= 4;

s[0] = 5 * s[1] + 1;

break;

}

printf("摘了%d个桃子, 剩下%d个桃子\n", s[0], s[5]*4);

return 0;

}

我们可以发现求S4,S3,S2,S1的表达式完全是一样的,所以我们可以用一个函数或者循环来表示,改进后的程序如下:

#include

int main(void)

{

int s[6] = {0};

int i;

for(s[5]=1; ;s[5]++)

{

for (i=4; i>0; i--)

{

s[i] = 5 * s[i+1] + 1;

if (s[i]%4)

break;

else

s[i] /= 4;

}

if (i == 0)

{

s[0] = 5 * s[1] + 1;

break;

}

}

printf("摘了%d个桃子, 剩下%d个桃子\n", s[0], s[5]*4);

return 0;

}

2.如何确定枚举范围?

例4:分数拆分问题(输入正整数k,找到所有的正整数x≥y,使得1/k=1/x+1/y。)

分析:由于x≥y,有1/x≤1/y,因此1/k-1/y≤1/y,即y≤2k。这样只需要在2k范围内枚举y,然后根据y尝试计算出x即可。

3.约束条件的确定

在枚举法解题中,判定条件的确定也是很重要的,如果约束条件不对或者不全面,就穷举不出正确的结果,我们再看看下面的例子。

例3:高斯日记。计算1777年4月30日(高斯出生日)之后第8113天的日期是多少。

例4:大小之差。由6个数字(允许重复)组成的最大6位数与最小6位数之间的差值也是一个6位数,且刚好包含了这6个数字。(2014第五届蓝桥杯校内赛C语言B组)

综合举例1:古堡算式(ABCDE * ?= EDCBA)

(1)枚举对象,可以有两种选择:

a)对每一个字符进行0~9的枚举;

b)利用一个5位数进行10000~99999的枚举;

(2)枚举范围设定

对于用5位数来枚举的方式,根据条件“各字符均不相同”、以及乘上一

个1位数后仍然是5位数的情况,可缩小枚举范围为12345~49876。

(3)约束条件,根据题目可知有两个约束条件:

a)各字符均不相同(即:A!=B && A!=C && A!=D && A!=E && B!=C && B!=D && B!=E && C!=D && C!=E && D!=E)

b)算式(即ABCDE * ?== EDCBA)

下面分别按上述两种枚举对象给出程序代码:

方法一:

#include

int x[5]; //用于存放5个字符对应的5个数字

void calc(int &a, int &b) // 计算5位数的值

{

int i;

a=b=0;

for (i=0; i<5; i++)

{

a = 10 * a + x[i];

b = 10 * b + x[5-i-1];

}

return;

}

int main(int argc, char* argv[])

{

int flag[10]={0}, a=0, b=0, t; //flag数组用于表示各数字是否被选用

for (x[0]=0; x[0]<=9; x[0]++)

{

flag[x[0]] = 1;

for (x[1]=0; x[1]<=9; x[1]++)

if (!flag[x[1]])

{

flag[x[1]] = 1;

for (x[2]=0; x[2]<=9; x[2]++)

if (!flag[x[2]])

{

flag[x[2]] = 1;

for (x[3]=0; x[3]<=9; x[3]++)

if (!flag[x[3]])

{

flag[x[3]] = 0;

for (x[4]=0; x[4]<=9; x[4]++)

if (!flag[x[4]])

{

calc(a,b);

for (t=2; t<=9; t++)

if (a*t==b) //约束条件

printf("A=%d\nB=%d\nC=%d\nD=%d\nE=%d\n?

=%d\n", x[0],x[1],x[2],x[3],x[4],t);

}

flag[x[3]] = 0;

}

flag[x[2]] = 0;

}

flag[x[1]] = 0;

}

flag[x[0]] = 0;

}

return 0;

}

方法二:

int main(int argc, char* argv[])

{

int A,B,C,D,E,t,x,y;

for (x=12345; x<=49876; x++)

{

A = x/10000;

B = (x%10000)/1000;

C = (x%1000)/100;

D = (x%100)/10;

E = x % 10;

if (A!=B && A!=C && A!=D && A!=E && B!=C && B!=D && B!=E && C!=D && C!=E && D!=E )

{

y = E*10000+D*1000+C*100+B*10+A;

for (t=2; t<=9; t++)

if (x * t==y)

printf(“A=%d\nB=%d\nC=%d\nD=%d\nE=%d\n?=%d\n",A,B,C,D,E,t);

}

}

return 0;

}

综合举例2:警察抓住A B C D四名罪犯,其中一人是小偷。审问A说:我不是小偷。B说C是小偷。C说小偷肯定是D。D说C在冤枉人。现在已经知道四个人中三人说的是真话,一个人说假话,问小偷到底是谁?

int main()

{

int a, b, c, d; //用a、b、c、d分别表示四个人是否小偷

for (a = 0; a<=1; a+ )

for (b =0; b<= 1; b++ )

for (c=0; c<=1; c++)

for (d=0; d<=1; d++)

if(a+b+c+d==1)//4个人中只有1人是小偷

if ((a==0)+ (c==1)+ (d==1) + (d==0)) ==3 ) //有三人说真话

{

if (a==1) printf(“A\n”);

if (b==1) printf(“B\n”);

if (c==1) printf(“C\n”);

if (d==1) printf(“D\n”);

return 0;

}

习题:

1.已知三位整数x和y满足:x+y=1333,其中x的个位数是y的百位数,y的个位数是x的百位数,而它们的十位数一样。求满足这样条件的x和y。

2. 三人年龄

三个神秘蒙面人来访F博士。

博士询问他们年龄时,他们说:我们中年龄最小的不超过19岁。我们3人年龄总和为70岁。且我们三人年龄的乘积是所有可能情况中最大的。

请帮助F博士计算他们的年龄,从小到大排列,用逗号分开。

3. 农妇卖鸡蛋问题

大数学家欧拉在集市上遇到了本村的两个农妇,每人跨着个空篮子。她们和欧拉打招呼说两人刚刚卖完了所有的鸡蛋。

欧拉随便问:“卖了多少鸡蛋呢?”

不料一个说:“我们两人自己卖自己的,一共卖了150个鸡蛋,虽然我们卖的鸡蛋有多有少,但刚好得了同样的钱数。你猜猜看!”

欧拉猜不出。

另一个补充道:“如果我按她那样的价格卖,可以得到32元;如果她按我的价格卖,可以得到24.5元”。

欧拉想了想,说出了正确答案。

4. 马虎的算式:假设 a b c d e 代表1~9不同的5个数字(注意是各不相同的数字,且不含0)能满足形如:ab * cde = adb * ce 这样的算式一共有多少种呢?(第四届C语言B组预赛真题)

5. 黑洞数:任意一个5位数,比如:34256,把它的各位数字打乱,重新排列,可以得到一个最大的数:65432,一个最小的数23456。求这两个数字的差,得:41976,把这个数字再次重复上述过程(如果不足5位,则前边补0)。如此往复,数字会落入某个循环圈(称为数字黑洞)。

比如,刚才的数字会落入:[82962, 75933, 63954, 61974] 这个循环圈。

请编写程序,找到5位数所有可能的循环圈,并输出,每个循环圈占1行。其中位数全都相同则循环圈为[0],这个可以不考虑。

循环圈的输出格式仿照:

[82962, 75933, 63954, 61974] 其中数字的先后顺序可以不考虑。

二、递归

概念:什么是递归?

是指函数/过程/子程序在运行过程中直接或间接调用自身而产生的重入现象。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

递归算法要求

递归算法所体现的“重复”一般有三个要求:

(1)每次调用在规模上都有所缩小(通常是减半);

(2)相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入);

(3)在问题的规模极小时必须用直接给出解答而不再进行递归调用,因而必须有一个明确的递归结束条件,称为递归出口。

递归技术分为两种类型:

(1)基于归纳的递归;

(2)基于分治的递归。

递归的应用场合:

(1)数据的是按递推方式定义的(Fibonacci函数等)

(2)问题求解过程适于递归(数的排列问题等)

(3)数据或问题对象的结构形式是按递归定义的(树图的遍历,搜索回溯)递归应用举例:求n!;求斐波那契序列;汉诺塔问题;……

递归的缺点:

递归算法解题相对常用的算法如普通循环等,运行效率较低。因此,应该尽量避免使用递归,除非没有更好的算法或者某种特定情况,递归更为适合的时候。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。

(一)数据的定义是按递推定义的

例1:求n!,关键:f(n)=n*f(n-1)

int f(int n)

{

if (n==0 || n==1) return 1;

return n*f(n-1);

}

例2:求斐波那契序列第n项值,关键:F(n)=F(n-1)+F(n-2)

int Fibonacci(int n)

{

if (n==0 || n==1) return n;

return Fibonacci(n-1)+ Fibonacci(n-2);

}

小结:此类问题的解题思路是运用了数学归纳法(什么是数学归纳法?)的基本思想,先求出问题规模n在边界值(通常是最小值)时候的解,再依据f(n)与前项(即f(n-1))的递推关系,给出f(n)的运算式。

例3:计算m个A,n个B可以组合成多少个不同排列的问题。

int f(int m, int n)

{

if(m==0 || n==0) return 1;

return _(m+n)*(m+n-1)/(m*n)f(m-1,n-1); 或

_f(m-1,n)+f(m,n-1);

}

(二)问题的求解过程适于递归

递归的思想关键在于将大任务划分成小任务,要求这些小任务与大任务有相似性。

下面用几个简单的问题来理解递归思想:

例1:输出0~9这10个数

循环法:for ( x = 0; x<=9; x++ ) printf( “%d\n”, x );

递归法:void fp( int begin, int end)

{

printf (“%d\n”, begin); //先打印再递归

if ( begin < end ) fp(begin+1, end);

}

等价于:void fp ( int begin, int end)

{

if (begin > end) return; //递归的出口

fp ( begin, end-1);//先递归再打印

printf (“%d\n”, end);

}

例2:累加求和1+2+3+……+100

循环法:for ( x=1, sum=0; x<=100; x++ ) sum = sum+x;

递归法:int fsum ( int begin, int end)

{

if (begin == end) return begin; //递归的出口

return begin + fp ( begin+1, end);

}

从上述两例可以看出,递归思想的精髓在于将大任务划分为与它相似的小任务,这要如何体现?就体现在函数的“参数”变化上!

例3:汉诺塔问题

递归的思路:是将n个盘的问题转化为n-1个盘的问题,再将n-1个盘的问题转化为n-2个盘……,最后转化为处理1个盘的问题。

void hanoi(int n,char A,char B,char C)

{

if (n==1)

move (A,C);

else

{

hanoi(n-1,A,C,B); //将n-1个盘经过C柱中转搬到B柱

move (A,C); //将第n个盘从A柱搬到C柱

hanoi(n-1,B,A,C);//将n-1个盘经过A柱中转搬到C柱}

}

例4:生成1~n的全排列(分为数字可重复和不可重复)

方法一思路:建立一个由1~n组成的数组,利用任务划分,将n个数的排列问题转化为n-1个数的排列问题。具体做法:将数组中的每个数都分别与第1个数交换,然后再将剩余的数进行全排列。

void Swap(int& a, int& b) // 交换a和b

{

int temp = a;

a = b;

b = temp;

}

void Perm(int list[], int k, int m) //生成list [k:m ]的所有排列方式

{

int i;

if (k == m)

{ //输出一个排列方式

for (i = 0; i <= m; i++)

printf(“%d”,list[i]);

printf(“\n”);

}

else // list[k:m ]有多个排列方式

// 递归地产生这些排列方式

for (i=k; i <= m; i++)

{

Swap (list[k], list[i]); //将当前序列中的每个数与第一个数交换

Perm (list, k+1, m); //进入递归对后序的数列进行全排列

Swap (list [k], list [i]); //恢复交换前的状态

}

}

int main()

{

int s[]={1,2,3};

Perm(s, 0, 2);

return 0;

}

方法二思路:将n位数的排列划分成对每一个1位数的排列问题,对每个位置利用循环来尝试填数;填入一个数后,再利用递归去填入后续位置的数,…直至填完n个位置。

void print_permutation(int n, int *A, int pos)

{

int i,j;

if (pos==n)

for (i=0; i

else

for (i=1; i<=n; i++) //尝试从1~n 个数中找出填入A[pos]的数i { int ok=1;

for(j=0; j

if(ok)

{ A[pos]=i; //对当前位置填数

Print_permutation(n,A,pos+1); //递归,填写后序位置}

}

}

(三)数据的结构形式是按递归定义的

对于数据的结构形式是按递归定义的问题,直接按结构进行递进操作。

例7:二叉树的遍历(按中序遍历)

void InOrderTraverse(BiTree T)

{

if (T)

{

InOrderTraverse(T->lchild);

Visit(T->data);

InOrderTraverse(T->rchild);

}

}

习题:

1. 利用递归方式实现古堡算式。

2. 整数划分问题。任意输入一个正整数,把它的所有划分列出来。

例如:输入5

输出5 4+1 3+2 3+1+1 2+2+1 2+1+1+1 1+1+1+1+1

3.有M个小孩到公园玩,门票是1元。其中N个小孩带的钱为1元,K个小孩带的钱为2元。售票员没有零钱,问这些小孩共有多少种排队方法,使得售票员总能找得开零钱。注意:两个拿一元零钱的小孩,他们的位置互换,也算是一种新的排法。(M<=10)

三、分治法

当求解一个问题规模为n的问题,而n的取值很大时,通常把问题划分为多个子问题,这些子问题在结构上与原问题一样,但规模比原问题小,原问题的解即子问题解的合并,这种方法即为分治法。

分治法典型例子:二分查找、快速排序、归并排序等。

分治法所能解决的问题一般具有以下几个特征:

1) 该问题的规模缩小到一定的程度就可以容易地解决。

2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。

3) 利用该问题分解出的子问题的解可以合并为该问题的解;

4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。

分治法在每一层递归上都有三个步骤:

分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;

解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;

合并:将各个子问题的解合并为原问题的解。

它的一般的算法设计模式如下:

DandC(P)

1. if |P|≤n0

小规模的问题P。

3. 将P分解为较小的子问题 P1 ,P2 ,...,Pk

4. for i←1 to k

5. do yi ← D andC(Pi) //递归解决每个Pi

6. T ← MERGE(y1,y2,...,yk) // 合并子问题

7. return(T)

例题1:最大最小问题

企业老板有一袋金块,他要从中挑选最重的一块给最优秀的员工,挑选最轻的一块给一位普通员工。这个问题实际是从n个元素中查找最大值和最小值。

求解问题的一般算法:

void max_min(int A[], int &max, int &min, int n)

{

int i;

max = A[0]; min = A[0];

for(i=1; i

{

if( A[i]>max ) max = A[i];

if( A[i]

}

}

上述算法当问题规模为n时,比较的次数是2(n-1)。

利用分治法来解决:将原数组划分成两个相同大小的子数组,再对两个子数组分别采用分治法,求出最大值和最小值,然后再对两个最大值进行比较,得出大者;对两个最小值进行比较得出小者。

程序代码如下:

void maxmin(int A[], int &max, int &min, int low, int high)

{

int mid, max1, max2, min1, min2;

if ( high - low <=1 ) //当元素个数小于等2时,直接得出最大最小值if( A[high] > A[low] { max = A[high]; min = A[low]; }

else { max = A[low]; min = A[high]; }

else

{

mid = (low + high)/2; //元素个数大于2时,分两组

maxmin( A, max1, min1, low, mid); //分别对两个小组递归

maxmin( A, max2, min2, mid+1, high);

max = max1>max2 ? max1 : max2;

min = min1

}

}

用分治法解决问题规模为n的数列,比较次数为 3n/2 -2。

例题2:求数列中的最小间距。

对于给定的任意n个数,找出其中差值最小的两个数。

问题分析:传统思路(1)分别求出每个数与其他n-1个数的差值,取最小值;(2)对数列排序,再比较所有相邻两个数的差值。这两种方法对于n较大时效率较低。

分治法:将数列按它们的均值m分为两组S1、S2,S1中元素<=m,S2中元素>m;分别对S1和S2继续划分(递归),直至组中元素个数小于等2,计算两个元素的差值;对比S1和S2中的最小差值,取小者得min;计算S2中的最小值与S1中的最大值之差,若该值

程序部分代码:

typedef struct{

int x, y;

int d;

} point_d;

int partition(int n[], int k, int low, int high)

{

int i=low,j=high, temp;

while (i

{

while (ik) j--;

while (i

temp = n[i]; n[i] = n[j]; n[j] = temp;

}

return i;

}

point_d Cpair(int n[], int low, int high)

{

point_d z1, z2, Min_d={0,0,999999};

int mid, mid_pos, maxs1, mins2;

if ( high-low < 1)

return min_d;

mid = (max(n,low,high) + min(n,low,high))/2; //求数列均值

mid_pos = partition(n,mid,low,high); //按均值分组

z1 = Cpair(n, low, mid_pos); //分别对两组进行递归

z2 = Cpair(n, mid_pos+1, high);

maxs1 = max(n, low, mid_pos);

mins2 = min(n, mid_pos+1, high);

Min_d = z1.d < z2.d ? z1:z2;

if ( mins2 - maxs1 < Min_d.d)

{

Min_d.x = maxs1; Min_d.y = mins2; Min_d.d = mins2-maxs1;

}

return Min_d;

} 习题:

1.求区间第K 大值。

2.子集和问题(两个元素的和):给定一个由n 个实数构成的集合S 和另一个实数X ,判断S 中是否有两个元素的和等于X 。(提示:利用二分查找)

思考题:棋盘覆盖问题

有一个2k *2k 的方格棋盘,恰有一个方格是黑色的,其他为白色。任务是用包含3个方格的L 型牌覆盖所有白色方格。注:黑色方格不能被覆盖,且任意一个白色方格不能同时被两个或更多牌覆盖。L 型牌的4种旋转方式如下:

分析:当k=1时,选用一块牌就够了;对于2k *2k 的棋盘,可以切分为4块,每块大小为2k-1*2k-1,则对有黑格的那块可以用递归来实现;问题是其他3块呢?可以在切分边界构造黑格,进而用递归解决问题。

四、回溯法

回溯法可以看作是带优化的穷举法。它的基本思想是在一棵含有问题全部可能解的状态空间树上进行深度优先搜索。搜索过程中,每到达一个结点,则判断以该结点为根的子树是否含有问题的解,如果确定其不含问题的解,则退回到上层父结点。回溯法的应用范围:只要能把待求解的问题分成不太多的步骤,每个步骤又只有不太多的选择,都可以考虑应用回溯法。

回溯法求解步骤:

(1) 针对所给问题,定义问题的解空间;

(2) 确定易于搜索的解空间结构(子集树和排列树);

(3) 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜

索。 常用的剪枝函数:

(1) 用约束条件在扩展结点处剪去不满足约束的子树; (2) 用限界函数剪去得不到最优解的子树。

例1:经典回溯法解决的问题——四皇后(八皇后)问题。在4×4(8×8)棋盘上放置4(8)个皇后,使她们互不攻击(皇后的攻击范围为同行同列和同对

角线)。

void search(int cur)

{

int i,j;

if(cur==n) tot++;

else for(i=0;i

{

int ok=1;

C[cur]=i;

for(j=0;j

if(C[cur]==C[j]||cur-C[cur]==j-C[j]||cur+C[cur]==j+C[j])

{ ok=0; break;}

If(ok) search(cur+1);

}

}

例2:子集和问题。S是一个由n个正整数组成的集合{x

1,x

2

,…,x

n

},c是

一个正整数。子集和问题是判定是否存在一个S的子集S1,使得其中的元素之和等于c。

说明:cw为当前元素和;r为剩余元素之和;w数组记录集合中各元素值;x 数组描述对应位置的元素是否选中

int backtrack(int i) //i从1开始

{

if (i>n)

{

if (cw==c) return 1;

else return 0;

}

r -= w[i]; //计算剩余大小

if (cw+w[i]<=c) //判断当前和是否小于等于c

{

x[i]=1; //选中第i个元素

cw += w[i];

if (backtrack(i+1)) return 1;

cw -= w[i];

}

if (cw+r >= c) //判断当前和加上剩余元素和是否能满足大于等c

{

x[i] = 0; //不选第i个元素

if ( backtrack(i+1)) return 1;

}

r += w[i];

return 0;

}

注意:上述代码这种递归方式只能找出一个解,不会列出所有可能的解,因为该函数带有返回值,当找到一个解后则层层返回,不会再继续遍历下去。

习题:

1. 有5个砝码,重量为1,3,9,27,81,可以组合成1-121之间的任意整数。对于用户给定的重量,给出砝码方案。

例如:输入5

输出 9-3-1

输入19

输出 27-9+1

2. 马的遍历问题。在N*M的棋盘中,马只能走日字。马从位置(x,y)处出发,把

棋盘的每一格都走一次,且只走一次。找出所有路径。

算法实验 递归回溯解八皇后问题

深圳大学实验报告 课程名称:算法分析与复杂性理论 实验项目名称:八皇后问题 学院:计算机与软件学院 专业:软件工程 指导教师:杨烜 报告人:学号:班级:15级软工学术型实验时间:2015-12-08 实验报告提交时间:2015-12-09 教务部制

一.实验目的 1.掌握选回溯法设计思想。 2.掌握八皇后问题的回溯法解法。 二.实验步骤与结果 实验总体思路: 根据实验要求,通过switch选择八皇后求解模块以及测试数据模块操作,其中八皇后模块调用摆放皇后函数模块,摆放皇后模块中调用判断模块。测试数据模块主要调用判断模块进行判断,完成测试。用一维数组保存每行摆放皇后的位置,根据回溯法的思想递归讨论该行的列位置上能否放置皇后,由判断函数Judge()判断,若不能放置则检查该行下一个位置。相应结果和过程如下所示(代码和结果如下图所示)。 回溯法的实现及实验结果: 1、判断函数 代码1: procedure BTrack_Queen(n) //如果一个皇后能放在第K行和X(k)列,则返回true,否则返回false。 global X(1:k);integer i,k i←1 while i0 do X(k)←X(k)+1 //移到下一个位置 while X(k)<=n and not Judge(k) do //判断能否放置皇后 X(k)←X(k)+1 repeat if X(k)<=n //找到一个位置 then if k=n //是一个完整的解吗

2013-2014编译原理考试

编译程序是对高级语言程序进行翻译 在规范规约中,用句柄来刻画可规约串 动态存储分配是指在运行阶段为源程序的数据对象分配存储空间 词法分析的输出结果是单词的种别编码和自身值 正规式等价是指所识别的语言集相等 优化可生成运行时间短且占内存少的代码程序 【一】文法和语言 编译程序和翻译程序的区别 编译程序是整体编译完了,生成目标代码,再一次性执行。 解释程序是一边解释,一边执行,并不形成目标程序。 文法 文法是描述语言的语法结构的形式规则(即语法规则)。文法G:定义为四元组(VN,VT,P,S)VN为非终结符号的集合,VT为终结符号的集合,P为产生式的集合,S为开始符号,是一个非终结符,至少要在一条规则中作为左部出现。 文法分类 0型文法短语文法递归可枚举语言 1型文法上下文有关文法上下文有关语言 2型文法上下文无关文法上下文无关语言 3型文法正规文法正规语言 句型 假定G是一个文法,S是它的开始符号。如果S?*(表示从S出发,经0步或若干步可推出α),则称α是一个句型。 句子 仅含终结符号的句型是一个句子。 语言 由文法G生成的语言记为L(G),它是文法G的一切句子的集合 推导直接推导 规约直接规约 直接推导:仅当A —>γ是一个产生式,有v =αAβ,w= αγβ,αAβ?αγβ,称v直接推导到w, 记作v ? w,也称w直接归约到v。 最左推导又称为规范推导。

语法树 语法树的根结由开始符号所标记。 随着推导的展开,当某个非终结符被它的某个候选式所替换时,这个非终结符的相应结就产生了下一代新结。每个新结和其父亲结间都有一条连线。 在一棵语法树生长过程中的任何时刻,所有那些没有后代的端末结自左至右排列起来就是一个句型。 二义文法 如果一个文法存在某个句子对应两棵不同的语法树,则称这个文法是二义的。也就是说,若一个文法存在某个句子,它有两个不同的最左(最右)推导,则这个文法是法是二义的。 文法的二义性证明:找出一个句子,它有两个不同的最左推导或最右推导

八皇后之递归算法、回溯算法、穷举算法

VAR CONT,I:INTEGER; A:ARRAY[1..N] OF BYTE;{存放正确的一组解} C:ARRAY[1..N] OF BOOLEAN;{存放某一列放皇后的情况,用于判断是否有同列的情况} L:ARRAY[1-N..N-1] OF BOOLEAN;{存放某一斜线上放皇后的情况,用于判断是否有同斜线的情况;斜线的方向为\} R:ARRAY[2..2*N] OF BOOLEAN;{存放某一斜线上放皇后的情况,用于判断是否有同斜线的情况;斜线的方向为/} PROCEDURE PR; VAR I:INTEGER; BEGIN FOR I:=1 TO N DO WRITE(A[I]:4); INC(CONT); WRITELN(' CONT=',CONT); END; PROCEDURE TRY(I:INTEGER); VAR J:INTEGER; PROCEDURE ERASE(I:INTEGER); BEGIN C[J]:=TRUE; L[I-J]:=TRUE; R[I+J]:=TRUE; END; BEGIN FOR J:=1 TO N DO

IF C[J] AND L[I-J] AND R[I+J] THEN BEGIN A[I]:=J; C[J]:=FALSE; L[I-J]:=FALSE; R[I+J]:=FALSE; IF I

图灵机概述

图灵机 英国数学家A.M.图灵提出的一种抽象计算模型,用来精确定义可计算函数。图灵机由一个控制器、一条可无限延伸的带子和一个在带子上左右移动的读写头组成。这个在概念上如此简单的机器,理论上却可以计算任何直观可计算的函数。图灵机作为计算机的理论模型,在有关计算理论和计算复杂性的研究方面得到广泛的应用。 研究简况由于图灵机以简明直观的数学概念刻划了计算过程的本质,自1936年提出以来,有关学者对它进行了广泛的研究。C.E.仙农证明每一个图灵机等价于仅有两个内部状态的图灵机,王浩证明每个图灵机可由具有一条只读带和一条只有两个符号的存储带的图灵机模拟。人们还证明,图灵机与另一抽象计算模型──波斯特机器在计算能力上是等价的(见波斯特对应问题)。 人们还研究了图灵机的各种变形,如非确定的图灵机、多道图灵机、多带图灵机、多维图灵机、多头图灵机和带外部信息源的图灵机等。除极个别情形外,这些变形并未扩展图灵机的计算能力,它们计算的函数类与基本图灵机是相同的,但对研究不同类型的问题提供了方便的理论模型。例如,多带图灵机是研究计算复杂性理论的重要计算模型。人们还在图灵机的基础上提出了不同程度地近似于现代计算机的抽象机器,如具有随机访问存储器的程序机器等。 基本结构和功能图灵机(见图)的控制器具有有限个状态。其中有两类特殊状态:开始状态和结束状态(或结束状态集合)。图灵机的带子分成格子,右端可无限延伸,每个格子上可以写一个符号,图灵机有有限个不同的符号。图灵机的读写头可以沿着带子左右移动,既可扫描符号,也可写下符号。 在计算过程的每一时刻,图灵机处于某个状态,通过读写头注视带子某一格子上的符号。根据当前时刻的状态和注视的符号,机器执行下列动作:转入新的状态;把被注视的符号换成新的符号;读写头向左或向右移动一格。这种由状态和符号对偶决定的动作组合称为指令。例如指令q1a i│a j q2L表示当机器处在状态q1下注视符号a i时,将a i换成符号a j,转入新的状态q2,读写头左移一格。决定机器动作的所有指令表称为程序。结束状态或指令表中没有的状态、符号对偶,将导致停机。 在每一时刻,机器所处状态、带子上已被写上符号的所有格子以及机器当前注视的格子位置,统称为机器的格局。图灵机从初始格局出发,按程序一步步把初始格局改造为格局的序列。此过程可能无限制继续下去,也可能遇到指令表中没有列出的状态、符号组合或进入结束状态而停机。在结束状态下停机所达到的格局是最终格局,此最终格局(如果存在)就包

回溯法与分支限界法的分析与比较

回溯法与分支限界法的分析与比较 摘要:通过对回溯法与分支限界法的简要介绍,进一步分析和比较这两种算法在求解问题时的差异,并通过具体的应用来说明两种算法的应用场景及侧重点。 关键词:回溯法分支限界法n后问题布线问题 1、引言 1.1回溯法 回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。这种以深度优先方式系统搜索问题解的算法称为回溯法。 1.2分支限界法 分支限界法是以广度优先或以最小耗费优先的方式搜索解空间树,在每一个活结点处,计算一个函数值,并根据函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间上有最优解的分支推进,以便尽快地找出一个最优解,这种方法称为分支限界法。 2、回溯法的基本思想 用回溯法解问题时,应明确定义问题的解空间。问题的解空间至少应包含问题的一个解。之后还应将解空间很好的组织起来,使得能用回溯法方便的搜索整个解空间。在组织解空间时常用到两种典型的解空间树,即子集树和排列树。确定了解空间的组织结构后,回溯法从开始结点出发,以深度优先方式搜索整个解空间。这个开始结点成为活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应往回移动至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法以这种工作方式递归的在解空间中搜索,直至找到所要求的解或解空间中已无活结点时为止。 3、分支限界法的基本思想 用分支限界法解问题时,同样也应明确定义问题的解空间。之后还应将解空间很好的组织起来。分支限界法也有两种组织解空间的方法,即队列式分支限界法和优先队列式分支限界法。两者的区别在于:队列式分支限界法按照队列先进先出的原则选取下一个节点为扩展节点,而优先队列式分支限界法按照优先队列

枚举-递归-回溯

一、暴力求解法(枚举法/ 穷举法) 概念:什么是枚举法? 在进行归纳推理时,如果逐个考察了某类事件的所有可能情况,因而得出一般结论,那么这结论是可靠的,这种归纳方法叫做枚举法。即,把所要解决问题的所有可能性都列举出来,一一试验。 枚举应用简单举例:求1~100之间的素数;求水仙花数;鸡兔同笼问题;百元买百鸡问题;整数(分数)拆分问题;排列问题…… 枚举算法因为要列举问题的所有可能的答案,所有它具备以下几个特点: 1、得到的结果肯定是正确的; 2、可能做了很多的无用功,浪费了宝贵的时间,效率低下。 3、通常会涉及到求极值(如最大,最小,最重等)。 4、数据量大的话,可能会造成时间崩溃。 采用枚举算法解题的基本思路: (1)确定枚举对象、枚举范围和判定条件; (2)一一枚举可能的解,验证是否是问题的解 下面我们就从枚举算法的的优化、枚举对象的选择以及判定条件的确定,这三个方面来探讨如何用枚举法解题。 例1:百元买百鸡问题:有一个人有一百块钱,打算买一百只鸡。到市场一看,大鸡三块钱一只,小鸡一块钱三只,不大不小的鸡两块钱一只。现在,请你编一程序,帮他计划一下,怎么样买法,才能刚好用一百块钱买一百只鸡? 算法分析: 我们以三种鸡的个数为枚举对象(分别设为x,y,z),以三种鸡的总数(x+y+z)和买鸡用去的钱的总数(x*3+y*2+z/3)为判定条件,穷举各种鸡的个数。 (1)基本算法: for (x=0;x<=100;x++) for (y=0;y<=100;y++) for(z=0;z<=100;z++) if(x+y+z==100 && z%3==0 && x*3+y*2+z/3==100)输出x,y,z (2)优化算法:只需要枚举2种鸡x(x<=33)和y(y<=50),第3种根据约束条件100-x-y可得:

第2章 程序语言基础知识(文法-正规式-有限自动机

第2章程序语言基础知识 编译原理2-78 1.文法 认识终结符(不可拆分,小写)和非终结符(可拆分,大写) 终结符不可单独置前 eg:有文法G2[S]为: S->Ap S->Bq A->a A->cA B->b B->dB 则:S为开始符,S,A,B为非终结符,p,q,a,b,c,d为终结符 文法的类型 0型文法(限制最少的一个) 设G=(V N,V T ,P,S),如果它的每个产生式α---→β是这样结构: α属于(V N并V T)*(闭包)且至少含有一个非终结符,而β属于(V N并V T)*,则G是一个0型文法。0型文法也称短语文法。一个非常重要的理论结果是:0型文法的能力相当于图灵机(Turing)。或者说,任何0型语言都是递归可枚举的,反之,递归可枚举集必定是一个0型语言。 1型文法 也叫上下文有关文法,此文发对应于线性有界自动机。它是在0型文法的基础上每一个α---→β,都有|β|>=|α|。这里的|α|表示的是α的长度。 注意:虽然要求|β|>=|α|,但有一特例:α---->空也满足1型文法。 如有A->Ba 则|β|=2,|α|=1 符合1型文法要求。反之,如aA->a,则不符合1型文法要求。 2型文法 也叫上下文无关文法,它对应于下推自动机。2型文法是在1型文法的基础上,再满足每一个α-→β 都有α是非终结符。如A->Ba,符合2型文法要求。如Ab->Bab虽然符合1型文法要求,但是不符合2型文法要求,其中α=Ab,Ab不是一个非终结符。 3型文法 也叫正规文法,它对应于有限状态自动机。它是在2型文法满足的基础上满足: A->α|αB(右线性)或A->α|Bα(左线性) 如:A->a,A->aB,B->a,B->cB,则符合3型文法的要求。 但如果推导为:A->ab,A->aB,B->a,B->cB 或:A->a,A->Ba,B->a,B->cB则不符合3型文法的要求。 如何判断一个串是否为某个文法的句型 Eg1:已知文法G[S ] : S->A0|B1 ,A->S1|1 ,B->S0|0 :该文法属于桥穆斯定义的___( 1 )___文法,它不能产生串___( 2 )__。 (1) A.0型 B.1型 C.2型 D.3型 (2) A.0011 B.1010 C.1001 D.0101 S->A0 S->B1 A->S1 A-> 1

回溯搜索算法

补充2 回溯法 解回溯法的深度优先搜索策略 z理解回溯法的深度优先搜索策略。 z掌握用回溯法解题的算法框架 (1)递归回溯 (2)迭代回溯 (3)子集树算法框架 (4)排列树算法框架 通过应用范例学习回溯法的设计策略 z通过应用范例学习回溯法的设计策略。

Sch2-1z Sch2-1 方法概述搜索算法介绍 (1)穷举搜索 (2)盲目搜索 —深度优先(DFS)或回溯搜索( Backtracking); —广度优先搜索( BFS ); (Branch &Bound) —分支限界法(Branch & Bound);—博弈树搜索( α-βSearch) (3)启发式搜索 —A* 算法和最佳优先( Best-First Search ) —迭代加深的A*算法 —B*AO*SSS*等算法B , AO , SSS 等算法 —Local Search, GA等算法

Sch2-1z Sch2-1 方法概述搜索空间的三种表示: —表序表示:搜索对象用线性表数据结构表示; —显示图表示:搜索对象在搜索前就用图(树)的数据结构表示; —隐式图表示:除了初始结点,其他结点在搜索过程中动态生成。缘于搜索空间大,难以全部存储。 z 搜索效率的思考:随机搜索 —上世纪70年代中期开始,国外一些学者致力于研究随机搜索求解困难的组合问题,将随机过程引入搜索; —选择规则是随机地从可选结点中取一个从而可以从统计角度分析搜选择规则是随机地从可选结点中取一个,从而可以从统计角度分析搜索的平均性能; —随机搜索的一个成功例子是:判定一个很大的数是不是素数,获得了第个多式时算法 第一个多项式时间的算法。

计算理论试卷04年解析

(a)(T) Every context-free language is recursive. 解析:上下文无关语言包含于递归语言,所以正确,反之不成立,另外正则语言包含于上下文无关语言。所以说正则的也是递归的也是正确的。 (b) (T) Language is context free. 解析:因为证明语言是上下文无关的,可以根据引理 3.4.2 ,构造一个下推自动机来接受它,或者,利用定理3.5.1,上下文无关语言对于并封闭,把原来的语言分解为几个语言的并,证明这几个语言是上下文无关,并回来也是context free。这条题目可以用第一个方法。遇到a和b都入栈,当然不按顺序就要拒绝了,然后遇到一个c就出栈三个,如果字符串读完之后栈不空,那么就接受。于是得证。 (c) (T) Every language in NPis recursive. 解析:因为递归语言包含了NP语言,所以正确。 (d) (F) All languages on an alphabet are recursively enumerable. 解析:存在非递归可枚举的语言。例如课本上P164 的H1的补,它不是递归可枚举的。因此,不能说所有字母表上的语言都是递归可枚举的。 (e) (F) There's a language L such that L is undecidable, yet L and its complement are both semi-decided by the some Turing machine. 解析:定理5.7.1 语言是递归的当且仅当它和它的补都是递归可枚举的。所以如果L和其补都可能被图灵机半判定,等价于存在TM判定L。 (f) (T) There's a function φ s uch that φ can be computed by some Turing machines, yet φ is not a primitive recursive function. 解析:原始递归函数真包含于μ递归的,μ递归的函数就是递归的函数,可以被图灵机计算。这句话的意思是,存在非原始递归的可计算函数,因此是正确的。 (g) (F) Let be languages, recursive function t is a reduction from L1 to L2, if L1 is decidable, then so is L2. 解析:如果L1通过一个递归函数归约到L2,那么如果L2是递归的则L1也是递归的。归约和判断的方向是相反的。另外可以有逆否命题,如果L1不是递归的那么L2也不是递归的 (h) (F) A language L is recursive if and only if it is Turing-enumerable. 解析:定理5.7.2 ,语言是递归可枚举的当且仅当它是Turing可枚举的。所以这题错误。另外,定理5.7.3 语言是递归的当且仅当它是以字典序Turing可枚举的。 (i) (F) Suppose A, B are two languages and there is a polynomial-time reductions from A to B. If A is NP-complete, then B is NP-complete. 解析:应该是反过来说。如果A多项式时间归约到B,那么如果B是NPC,那么A也是NPC。把NPC换成P或NP同理。另外根据定义7.1.2 ,可以说若B是NPC的,那么A是

搜索与回溯算法介绍

搜索与回溯算法介绍 一、概述: 计算机常用算法大致有两大类:一类叫蛮干算法,一类叫贪心算法。前者常使用的手段就是搜索,对全部解空间进行地毯式搜索,直到找到指定解或最优解。后者在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解。 二、搜索与回溯: 这里着重介绍搜索与回溯。当很多问题无法根据某种确定的计算法则来求解时可以利用搜索与回溯的技术求解。回溯是搜索算法中既带有系统性又带有跳跃性的一种控制策略。它的基本思想是:为了求得问题的解,先选择某一种可能情况向前探索。在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,然后继续向前探索,如此反复进行,直至得到解或证明无解。如迷宫问题:进入迷宫后,先随意选择一个前进方向,一步步向前试探前进。如果碰到死胡同,说明前进方向已无路可走,这时,首先看其它方向是否还有路可走,如果有路可走,则沿该方向再向前试探;如果已无路可走,则返回一步,再看其它方向是否还有路可走;如果有路可走,则沿该方向再向前试探。按此原则不断搜索回溯再搜索,直到找到新的出路或从原路返回入口处无解为止。 【建立解空间】 问题的解应该如何描述,如何建立呢?问题的解空间:应用回溯法解问题时,首先应明确定义问题的解空间。问题的解空间应到少包含问题的一个(最优)解。借助图论的思想,可以用图来描述,图的定义为G,由顶点集和边集构成,顶点即实实在在的数据、对象,而边可以抽象为关系,即顶点间的关系,这种关系不一定非要在数据结构上表现出来,用数据结构的语言来描述,如果关系是一对一,则为线性表,如果关系是一对多,则为树,如果关系是多对多,则为图,如果完全没有关系,则为集合。但在数据结构中这种关系不一定非要在数据的存储性质上一开始就表现出来,譬如,你可以用一个数组表示一个线性表,也可以表示完全二叉树,同样也可以用邻接表表示一个图,对于关系的描述不是数据结构本身的描述,而是算法的描述,正如数据结构是离不开特定的算法一样,不可分开单独而谈。 确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向

计算理论复习

计算理论复习题 1、 什么是图灵机,图灵机的构造技术以及三种描述方式是什么? (1)图灵机:一个图灵机是一个7元组(Q ,∑,,Γ,δ,0q ,accept q ,reject q ),其中 ,,Q ∑Γ都是有穷集合,并且○ 1Q 是状态集;○2∑是输入字母表,不包括特殊空白符号︼;○3 Γ是字母表,其中:︼∈Γ∑?Γ,;○4:{,}Q Q L R δ?Γ→?Γ?;○50 q Q ∈是起始状态;○6accept q Q ∈是接受状态;○7reject q Q ∈是拒绝状态,且reject accept q q ≠。 (2)构造技术:○ 1有限控制器的存储构造TM ,检查第一个输入是不是出现在输入的其他地方;○2多道;○3查询符号(打标记);○4移动:如把一个字符串整体后移; ○5调用子程序。 (3)描述方式:○ 1形式描述,即详尽地写出图灵机的状态、转移函数等,这是最低、最详细程度的描述;○ 2实现描述,这种方法使用日常语言来描述图灵机的运行,如怎么移动读写头,怎么在带上存储数据等,没有给出 状态和转移函数的细节;○ 3高水平描述,它也是使用日常语言来描述算法,但忽略了实现的模型,不再需要提及机器是怎么管理它的带子或读写头。 2、什么是图灵机的格局,图灵可识别,图灵可判定? (1)图灵机计算过程中,当前状态、当前带内容和读写头当前位置组合在一起,称为图灵机的格局,也即是瞬间状态。 (2)如果一个语言能被某一图灵机识别,则称该语言是图灵可识别的。 (3) 如果一个语言能被某一图灵机判定,则称它是图灵可判定的。 3、什么是多带图灵机,非确定型图灵机,枚举器,递归可枚举语言? (1)多带图灵机很像普通图灵机,只是有多个带子,每个带子都有自己的读写头,用于读和写。开始时,输入出现在第一个带子上,其它的带子都是空白。转移函数改为允许同时进行读、写和移动读写头,其形式为:δ:Q ?Γ k →Q ?Γk ?},{R L k 此处k 是带子的个数。表达式δ( q i ,a 1, ,a k )=(q j ,b 1, ,b k ,L ,R , ,L )指的是:若机器处于状态 q i ,读写头1到k 所读的符号分别是a 1, ,a k ,则转移到状态q j ,写下符号b 1, 。,b k ,且按此式所指示的那样移动每个读写头。 推论1:每个多带图灵机都有一个与之等价的单带图灵机。 推论2:一个语言是图灵可识别的,当且仅当有多带图灵机识别它。 (2)非确定型图灵机:非确定型图灵机在计算的任何时刻,机器可以在多种可能性中选择一种继续进行。它的转移函数具有如下形式:δ:Q ?Г→P 3 (Q ?Г?{L ,R ,S}) 其计算是一棵树,不同分枝对应着机器不同的可能性。如果计算的某个分枝导致接受状态,

编译原理复习要点

翻译程序 把某一种语言程序(称为源语言程序)等价地转换成另一种语言程序(称为目标语言程序)的程序 ?编译程序(Complier) 将某种高级语言(如FORTRAN、Pascal、C等)程序翻译为对应的低级语言(如汇编语言或机器语言)程序。

。 要在某一台机器上为某种语言构造一个编译程序,必须掌握下述三方面的内容: 1、源语言,对被编译的源语言,要深刻理解其结构(语法)和含义(语义); 2、目标语言,假定目标语言是机器语言,那么,就必须搞清楚硬件的系统结构和操作系统 的功能; 3、编译方法,把一种语言程序翻译为另一种语言程序方法很多,但必须准确地掌握一二。

0型文法(短语文法,图灵机) :对文法G,如果它的每个产生式α→β是这样的一种结构:α∈(V N∪V T)* 且至少含有一个非终结符 β∈(V N ∪V T)* 0型文法相应的语言为0型语言,或称递归可枚举集,它的识别系统是图灵(Turing)机。如文法G,其中V N={A,B,S} V T={0,1} P={ S→0AB 1B→0 B→SA|01 A1→SB1 A0→S0B } 1型文法(上下文有关):它是0型文法的特例,对P中的任一产生式α→β,都|β|≥|α|,仅仅S→ε除外,但S不得出现在任何产生式的右部。 1型文法相应的语言称为1型语言或上下文有关语言,它的识别系统是线性有界自动机。 例文法G[S]: S→aSBE S→aBE EB→BE aB→ab bB→bb bE→be eE→ee ?2型文法(上下文无关文法):它是1型文法的特例,对任一产生式α→β,都有α∈V N,β∈(V N∪V T)* ?2型文法相应的语言称为2型语言或上下文无关语言。它的识别系统是下推自动机。 ?例文法G[S]:S→AB A→BS|0 B→SA|1?2型文法产生式的一般形式是: A→β,它表示不管A的上下文如何都可把A替换成β,因此被称为上下文无关文法。 ?3型文法(正规文法):它是2型文法的特例,任一产生式α→β的形式都为A→aB 或A→a,其中A ,B∈V N ,a∈V T ?这种形式的3型文法也叫右线性文法。3型文法还有一种形式,限定P中的每一个产生式形如A→Bα或A→α,称为左线性文法。 ?3型文法描述的语言称为3型语言或正规语言(也称正规集),由有穷自动机识别。 例如文法G[S]:S→0A|1B|0 A→0A|1B|0S B→1B|1|0

递推-递归-分治-回溯

递推算法 在程序编辑过程中,我们可能会遇到这样一类问题,出题者告诉你数列的前几个数,或通过计算机获取了数列的前几个数,要求编程者求出第N项数或所有的数列元素(如果可以枚举的话),或求前N项元素之和。这种从已知数据入手,寻找规则,推导出后面的数的算法,称这递推算法。 典型的递推算法的例子有整数的阶乘,1,2,6,24,120…,a[n]=a[n-1]*n(a[1]=1);前面学过的2n,a[n]=a[n-1]*2(a[1]=1),菲波拉契数列:1,2,3,5,8,13…,a[n]=a[n-1]+a[n-2](a[1]=1,a[2]=2)等等。 在处理递推问题时,我们有时遇到的递推关系是十分明显的,简单地写出递推关系式,就可以逐项递推,即由第i项推出第i+1项,我们称其为显示递推关系。但有的递推关系,要经过仔细观察,甚至要借助一些技巧,才能看出它们之间的关系,我们称其为隐式的递推关系。 下面我们来分析一些例题,掌握一些简单的递推关系。 例如阶梯问题:题目的意思是:有N级阶梯,人可以一步走上一级,也可以一步走两级,求人从阶梯底走到顶端可以有多少种不同的走法。 这是一个隐式的递推关系,如果编程者不能找出这个递推关系,可能就无法做出这题来。我们来分析一下:走上第一级的方法只有一种,走上第二级的方法却有两种(两次走一级或一次走两级),走上第三级的走法,应该是走上第一级的方法和走上第二级的走法之和(因从第一级和第二级,都可以经一步走至第三级),推广到走上第i级,是走上第i-1级的走法与走上第i-2级的走法之和。很明显,这是一个菲波拉契数列。到这里,读者应能很熟练地写出这个程序。在以后的程序习题中,我们可能还会遇到菲波拉契数列变形以后的结果:如f(i)=f(i-1)+2f(i-2),或f(i)=f(i-1)+f(i-2)+f(i-3)等。 我们再来分析一下尼科梅彻斯定理。定理内容是:任何一个整数的立方都可以写成一串连续的奇数和,如:43=13+15+17+19=64。 从键盘输入一个整数n,要求写出其相应的连续奇数。 我们不妨从简单入手,枚举几个较小的数据: 13=1 23=3+5 33=7+9+11 43=13+15+17+19 53=21+23+25+27+29 根据上面的例子,读者不难看出: (1)输入为n时,输出应有n项。 (2)输入分别为1,2,3…时,则输出恰好为连续奇数,1,3,5,7,9,11…即下一行的首项比上一行的末项大2。 经上面的分析,原本看不出递推关系的问题,呈现出递推关系。在趣的是,这个例子的递推过程,可以有多种算法。 算法一:将所有奇数逐项例举出来,然后将其分段,即: 1; 3 5; 7 9 11; 13 15 17 19; 21… 1 2 3 4 5… 算法二、设输入为n时的输出第一项为a[n],则a[n]=a[n-1]-n+1; 于是我们推出首项后,则输出为a[n]+a[n]+2+…+a[n]+2(n-1) 算法三、进一步总结,不难得出,若输入为n时,首项a[n]=n2-n+1,其余同算法二。下面我们来分析两个与动物有关的趣题。

计算机考博试题

计算理论 字母表:一个有穷的符号集合。 字母表上的字符串是该字母表中的符号的有穷序列。 一个字符串的长度是它作为序列的长度。 连接反转Kleene星号L* ,连接L中0个或多个字符串得到的所有字符串的集合。 有穷自动机:描述能力和资源极其有限的计算机模型。 有穷自动机是一个5元组M=(K,∑,δ,s,F),其中 1)K是一个有穷的集合,称为状态集 2)∑是一个有穷的集合,称为字母表 3)δ是从KX∑→K的函数,称为转移函数 4)s∈K是初始状态 5)F?K是接收状态集 M接收的语言是M接收的所有字符串的集合,记作L(M). 对于每一台非确定型有穷自动机,有一台等价的确定型有穷自动机有穷自动机接受的语言在并、连接、Kleene星号、补、交运算下是封闭的。 每一台非确定型有穷自动机都等价于某一台确定型有穷自动机。一个语言是正则的当且仅当它被有穷自动机接受。 正则表达式:称R是一个正则表达式,如果R是 1)a,这里a是字母表∑中的一个元素。 2)ε,只包含一个字符串空串的语言

3) ,不包含任何字符串的语言 4)(R1∪R2),这里R1和R2是正则表达式 5)(R10R2),这里R1和R2是正则表达式 6)(R1*),这里R1*是正则表达式 一个语言是正则的当且仅当可以用正则表达式描述。 2000年4月 1、根据图灵机理论,说明现代计算机系统的理论基础。 1936年,图灵向伦敦权威的数学杂志投了一篇论文,题为《论数字计算在决断难题中的应用》。在这篇开创性的论文中,图灵给“可计算性”下了一个严格的数学定义,并提出著名的“图灵机”(Turing Machine)的设想。“图灵机”不是一种具体的机器,而是一种思想模型,可制造一种十分简单但运算能力极强的计算机装置,用来计算所有能想像得到的可计算函数。这个装置由下面几个部分组成:一个无限长的纸带,一个读写头。(中间那个大盒子),内部状态(盒子上的方块,比如A,B,E,H),另外,还有一个程序对这个盒子进行控制。这个装置就是根据程序的命令以及它的内部状态进行磁带的读写、移动。工作带被划分为大小相同的方格,每一格上可书写一个给定字母表上的符号。控制器可以在带上左右移动,它带有一个读写出一个你期待的结果。这一理论奠定了整个现

回溯算法的应用

回溯算法的应用 课程名称:算法设计与分析 院系:************************ 学生姓名:****** 学号:************ 专业班级:***************************** 指导教师:****** 2013年12月27日

回溯法的应用 摘要:回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。 回溯法,其意义是在递归直到可解的最小问题后,逐步返回原问题的过程。而这里所说的回溯算法实际是一个类似枚举的搜索尝试方法,它的主题思想是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。 回溯算法是尝试搜索算法中最为基本的一种算法,其采用了一种“走不通就掉头”的思想,作为其控制结构。在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。 全排列和求最优解问题是比较经典的问题,我们可以采用多种算法去求解此问题,比如动态规划法、分支限界法、回溯法。在这里我们采用回溯法来解决这个问题。 关键词:回溯法全排列最优值枚举

回溯算法的一些例题

回溯算法 搜索与回溯是计算机解题中常用的算法,很多问题无法根据某种确定的计算法则来求解,可以利用搜索与回溯的技术求解。回溯是搜索算法中的一种控制策略。它的基本思想是:为了求得问题的解,先选择某一种可能情况向前探索,在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,继续向前探索,如此反复进行,直至得到解或证明无解。如迷宫问题:进入迷宫后,先随意选择一个前进方向,一步步向前试探前进,如果碰到死胡同,说明前进方向已无路可走,这时,首先看其它方向是否还有路可走,如果有路可走,则沿该方向再向前试探;如果已无路可走,则返回一步,再看其它方向是否还有路可走;如果有路可走,则沿该方向再向前试探。按此原则不断搜索回溯再搜索,直到找到新的出路或从原路返回入口处无解为止。 递归回溯法算法框架[一] procedure Try(k:integer); begin for i:=1 to 算符种数 Do if 满足条件 then begin 保存结果 if 到目的地 then 输出解 else Try(k+1); 恢复:保存结果之前的状态{回溯一步} end; end; 递归回溯法算法框架[二] procedure Try(k:integer); begin if 到目的地 then 输出解 else for i:=1 to 算符种数 Do if 满足条件 then begin 保存结果 Try(k+1); end; end;

例 1:素数环:把从1到20这20个数摆成一个环,要求相邻的两个数的和是一个素数。【算法分析】非常明显,这是一道回溯的题目。从1 开始,每个空位有 20(19)种可能,只要填进去的数合法:与前面的数不相同;与左边相邻的数的和是一个素数。第 20个数还要判断和第1个数的和是否素数。 〖算法流程〗1、数据初始化; 2、递归填数: 判断第J种可能是否合法; A、如果合法:填数;判断是否到达目标(20个已填完):是,打印结果;不是,递归填下一个; B、如果不合法:选择下一种可能; 【参考程序】 program z74;框架[一] var a:array[0..20]of byte; b:array[0..20]of boolean; total:integer; function pd(x,y:byte):boolean; var k,i:byte; begin k:=2; i:=x+y; pd:=false; while (k<=trunc(sqrt(i)))and(i mod k<>0) do inc(k); if k>trunc(sqrt(i)) then pd:=true; end; procedure print; var j:byte; begin inc(total);write('<',total,'>:'); for j:=1 to 20 do write(a[j],' '); writeln; end; procedure try(t:byte); var i:byte; begin for i:=1 to 20 do if pd(a[t-1],i)and b[i] then begin a[t]:=i; b[i]:=false; if t=20 then begin if pd(a[20],a[1]) then print;end

数理逻辑复习10级

可计算性概念: 可枚举与不可枚举。 能行可计算的:按照一组给定指令能够原则上确定从Z到Z的函数f在任意自变量n处的值f(n),则f是能行可计算的。 不可计算性。对角线函数d和停机函数h都是不可计算的。 递归→算盘可计算→图灵可计算→递归 图灵论题:能行可计算的函数都是图灵可计算的。 丘奇论题:能行可计算的函数都是递归的。 原始递归:由基本函数(零函数,后继函数,恒同函数)出发通过复合和递归能够得到的函数为原始递归函数。 递归函数:由基本函数(z,s,id)出发经过Cn,Pr,Mn能得到的函数称为递归函数。 递归集合/递归关系:它的特征函数是(原始)递归的,则它是(原始)递归的。 (原始)递归关系的封闭性:代入,图关系,否定,合取,析取,有界全称量化,有界存在量化,有界极小化,有界极大化 通用函数/通用关系/通用图灵机。存在k元递归函数的通用函数,其图是半递归的。 半递归关系等价于能行半可判定关系。n元能行半可判定关系为从n+1元能行可判定关系通过(无界)存在量化得到的关系。 半递归关系的封闭性:递归都是半递归的;代入;合取,析取;有界全称量化,存在量化半递归集与递归可枚举集(r.e.)是等价的。 逻辑概念: (开/闭)项,(开/闭)公式,语句,自由/约束变元,解释,蕴涵/推论,有效,可满足/不可满足的,等价,逻辑等价 模型,同构,等价,型构 语法概念语义概念 推演deduction 推论consequence 反驳refutation 不可满足unsatisfiability 证明demonstration 有效性validity 可推导的derivable ?安全的secure =>:可靠性定理 <=:哥德尔完备性定理 推导derivation,可推演的,可反驳的,可证的,协调的,不协调的 证明,定理,理论,可公理化,可有限公理化,完全的,协调的,可判定的 算术的arithmetically,初等公式,存在-初等公式 存在-初等公式的封闭性:初等公式=存在-初等公式;合取析取;有界量化;无界存在量化可定义,可表示。它们在TA中等价。 极小算术Q。递归函数在Q中可表示;递归关系在Q中可定义。 Q+归纳公理=P(Peano算术) 一些逻辑的重要否定结果,包括: 塔斯基定理:TA*不是算术可定义的。 算术的不可判定性:TA*不是递归的。 实质不可判定性:Q的协调扩张(包括Q)都不是可判定的。 丘奇定理:有效语句集合不可判定。 哥德尔第一不完全性定理:不存在Q的协调的,完全的,可公理化的扩张。

用递归回溯解决迷宫问题

扬州大学试题纸 (2015-2016 学年第二学期) ACM竞赛辅导论文报告 学院:信息工程学院 班级:计科1302 学号:131404211 姓名:盛晓伟 指导老师:徐晓华

用递归回溯解决迷宫问题 盛晓伟(扬州大学计算机科学与技术系扬州225100) SHENG Xiao-Wei, School of Information Science and Technology, Yangzhou University, Yangzhou 225100, China) For many practical problems, the solution is to move along a series of orderly decision-making points, each of the choices made at each decision point will lead along a path. If the choice is correct, the problem will be resolved. On the other hand, if you go into a dead end or find that the choice at a certain point is wrong, then you must return to the previous decision point and try another route.. This algorithm is called backtracking algorithm.. If the backtracking algorithm is considered as a continuous process of trying to solve the problem, the process seems to be iterative. However, for most of this problem, the recursive method is more easily solved. The recursive principle point of view is very simple; for a backtracking problem, if and only if at least a word problem solutions can be solved, and these subproblems generated source in the initial of each a possible choice. Key words: backtracking algorithm: Recursive; Iterative;Maze 摘要:对许多实际问题而言,其解决过程就是沿着一系列有序的决策点前进,在每个决策点所做的每一个选择都会导向沿着某一路径前行。如果选择是正确的,问题将得到解决。从另一方面讲,如果走进了死胡同,或者发现在某个决策点的选择是错误的,那么必须返回到以前的决策点,尝试另一条路径。这中方式的算法称为回溯算法。 如果吧回溯算法看成是一个不断地进行各种尝试直至解决问题的过程,那么这个过程似乎具有迭代性。然而,对大多数这种形式的问题,采用递归方法更容易解决。采用递归原理的着眼点很简单;对于一个回溯问题而言,当且仅当至少有一个字问题有了解决方案后才可能得到解

相关文档
最新文档