| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 1327 人关注过本帖
标题:[分享]回朔,看到蛮多题目可以用这个解,找来个,大家看看.
只看楼主 加入收藏
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
结帖率:50%
收藏
 问题点数:0 回复次数:7 
[分享]回朔,看到蛮多题目可以用这个解,找来个,大家看看.

算法设计(回溯法1)
(转帖)

回溯法也称为试探法,该方法首先暂时放弃关于问题规模大小的限制,并将问题的候选解按某种顺序逐一枚举和检验。当发现当前候选解不可能是解时,就选择下一个候选解;倘若当前候选解除了还不满足问题规模要求外,满足所有其他要求时,继续扩大当前候选解的规模,并继续试探。如果当前候选解满足包括问题规模在内的所有要求时,该候选解就是问题的一个解。在回溯法中,放弃当前候选解,寻找下一个候选解的过程称为回溯。扩大当前候选解的规模,以继续试探的过程称为向前试探。

回溯法的一般流程和技术

在用回溯法求解有关问题的过程中,一般是一边建树,一边遍历该树。在回溯法中我们一般采用非递归方法。下面,我们给出回溯法的非递归算法的一般流程:

在用回溯法求解问题,也即在遍历状态空间树的过程中,如果采用非递归方法,则我们一般要用到栈的数据结构。这时,不仅可以用栈来表示正在遍历的树的结点,而且可以很方便地表示建立孩子结点和回溯过程。

【问题】 组合问题

问题描述:找出从自然数1,2,…,n中任取r个数的所有组合。

采用回溯法找问题的解,将找到的组合以从小到大顺序存于a[0],a[1],…,a[r-1]中,组合的元素满足以下性质:

(1) a[i+1]>a[i],后一个数字比前一个大;

(2) a[i]-i<=n-r+1。

按回溯法的思想,找解过程可以叙述如下:

首先放弃组合数个数为r的条件,候选组合从只有一个数字1开始。因该候选解满足除问题规模之外的全部条件,扩大其规模,并使其满足上述条件(1),候选组合改为1,2。继续这一过程,得到候选组合1,2,3。该候选解满足包括问题规模在内的全部条件,因而是一个解。在该解的基础上,选下一个候选解,因a[2]上的3调整为4,以及以后调整为5都满足问题的全部要求,得到解1,2,4和1,2,5。由于对5不能再作调整,就要从a[2]回溯到a[1],这时,a[1]=2,可以调整为3,并向前试探,得到解1,3,4。重复上述向前试探和向后回溯,直至要从a[0]再回溯时,说明已经找完问题的全部解。按上述思想写成程序如下:

【程序】

# define MAXN 100

int a[MAXN];

void comb(int m,int r)

{ int i,j;

i=0;

a[i]=1;

do {

if (a[i]-i<=m-r+1

{ if (i==r-1)

{ for (j=0;j<r;j++) printf(“%4d”,a[j]);

printf(“\n”);

}

a[i]++;

continue;

}

else

{ if (i==0) return;

a[--i]++;

}

} while (1)

}

main()

{ comb(5,3);

}

【问题】 填字游戏

问题描述:在3×3个方格的方阵中要填入数字1到N(N≥10)内的某9个数字,每个方格填一个整数,似的所有相邻两个方格内的两个整数之和为质数。试求出所有满足这个要求的各种数字填法。

可用试探发找到问题的解,即从第一个方格开始,为当前方格寻找一个合理的整数填入,并在当前位置正确填入后,为下一方格寻找可填入的合理整数。如不能为当前方格找到一个合理的可填证书,就要回退到前一方格,调整前一方格的填入数。当第九个方格也填入合理的整数后,就找到了一个解,将该解输出,并调整第九个的填入的整数,寻找下一个解。

为找到一个满足要求的9个数的填法,从还未填一个数开始,按某种顺序(如从小到大的顺序)每次在当前位置填入一个整数,然后检查当前填入的整数是否能满足要求。在满足要求的情况下,继续用同样的方法为下一方格填入整数。如果最近填入的整数不能满足要求,就改变填入的整数。如对当前方格试尽所有可能的整数,都不能满足要求,就得回退到前一方格,并调整前一方格填入的整数。如此重复执行扩展、检查或调整、检查,直到找到一个满足问题要求的解,将解输出。

回溯法找一个解的算法:

{ int m=0,ok=1;

int n=8;

do { if (ok) 扩展;

else 调整;

ok=检查前m个整数填放的合理性;

} while ((!ok||m!=n)&&(m!=0))

if (m!=0) 输出解;

else 输出无解报告;

}

如果程序要找全部解,则在将找到的解输出后,应继续调整最后位置上填放的整数,试图去找下一个解。


相应的算法如下:

回溯法找全部解的算法:

{ int m=0,ok=1;

int n=8;

do { if (ok)

{ if (m==n)

{ 输出解;

调整;

}

else 扩展;

}

else 调整;

ok=检查前m个整数填放的合理性;

} while (m!=0);

}

为了确保程序能够终止,调整时必须保证曾被放弃过的填数序列不会再次实验,即要求按某种有许模型生成填数序列。给解的候选者设定一个被检验的顺序,按这个顺序逐一形成候选者并检验。从小到大或从大到小,都是可以采用的方法。如扩展时,先在新位置填入整数1,调整时,找当前候选解中下一个还未被使用过的整数。将上述扩展、调整、检验都编写成程序,细节见以下找全部解的程序。

【程序】

# include <stdio.h>

# define N 12

void write(int a[ ])

{ int i,j;

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

{ for (j=0;j<3;j++) printf(“%3d”,a[3*i+j]);

printf(“\n”);

}

scanf(“%*c”);

}

int b[N+1];

int a[10];

int isprime(int m)

{ int i;

int primes[ ]={2,3,5,7,11,17,19,23,29,-1};

if (m==1||m%2=0) return 0;

for (i=0;primes[i]>0;i++)

if (m==primes[i]) return 1;

for (i=3;i*i<=m;)

{ if (m%i==0) return 0;

i+=2;

}

return 1;

}

int checkmatrix[][3]={ {-1},{0,-1},{1,-1},{0,-1},{1,3,-1},

{2,4,-1},{3,-1},{4,6,-1},{5,7,-1}};

int selectnum(int start)

{ int j;

for (j=start;j<=N;j++)

if (b[j]) return j;

return 0;

}

int check(int pos)

{ int i,j;

if (pos<0) return 0;

for (i=0;(j=checkmatrix[pos][i])>=0;i++)

if (!isprime(a[pos]+a[j]) return 0;

return 1;

}

int extend(int pos)

{ a[++pos]=selectnum(1);

b[a][pos]]=0;

return pos;

}

int change(int pos)

{ int j;

while (pos>=0&&(j=selectnum(a[pos]+1))==0)

b[a[pos--]]=1;

if (pos<0) return –1;

b[a[pos]]=1;

a[pos]=j;

b[j]=0;

return pos;

}

void find()

{ int ok=0,pos=0;

a[pos]=1;

b[a[pos]]=0;

do { if (ok)

if (pos==8)

{ write(a);

pos=change(pos);

}

else

pos=extend(pos);

else

pos=change(pos);

ok=check(pos);

} while (pos>=0)

}
void main()

{ int i;

for (i=1;i<=N;i++) b[i]=1;

find();

}

搜索更多相关主题的帖子: 候选 规模 回溯 分享 
2007-04-17 23:57
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
收藏
得分:0 

回溯法及其应用

前言
在计算机奥赛中,有时会遇到这样一类题目,它的问题可以分解,但是又不能得出明确的动态规划或是递归解法,此时可以考虑用回溯法解决此类问题。回溯法的优点在于其程序结构明确,可读性强,易于理解,而且通过对问题的分析可以大大提高运行效率。但是,对于可以得出明显的递推公式迭代求解的问题,还是不要用回溯法,因为它花费的时间比较长。
回溯法的基本思想
对于用回溯法求解的问题,首先要将问题进行适当的转化,得出状态空间树。这棵树的每条完整路径都代表了一种解的可能。通过深度优先搜索这棵树,枚举每种可能的解的情况;从而得出结果。但是,回溯法中通过构造约束函数,可以大大提升程序效率,因为在深度优先搜索的过程中,不断的将每个解(并不一定是完整的,事实上这也就是构造约束函数的意义所在)与约束函数进行对照从而删除一些不可能的解,这样就不必继续把解的剩余部分列出从而节省部分时间。
回溯法中,首先需要明确下面三个概念:
(一)约束函数:约束函数是根据题意定出的。通过描述合法解的一般特征用于去除不合法的解,从而避免继续搜索出这个不合法解的剩余部分。因此,约束函数是对于任何状态空间树上的节点都有效、等价的。
(二)状态空间树:刚刚已经提到,状态空间树是一个对所有解的图形描述。树上的每个子节点的解都只有一个部分与父节点不同。
(三)扩展节点、活结点、死结点:所谓扩展节点,就是当前正在求出它的子节点的节点,在DFS中,只允许有一个扩展节点。活结点就是通过与约束函数的对照,节点本身和其父节点均满足约束函数要求的节点;死结点反之。由此很容易知道死结点是不必求出其子节点的(没有意义)。
深度优先搜索(DFS)和广度优先搜索(FIFO)
在分支界限法中,一般用的是FIFO或最小耗费搜索;其思想是一次性将一个节点的所有子节点求出并将其放入一个待求子节点的队列。通过遍历这个队列(队列在遍历过程中不断增长)完成搜索。而DFS的作法则是将每一条合法路径求出后再转而向上求第二条合法路径。而在回溯法中,一般都用DFS。为什么呢?这是因为可以通过约束函数杀死一些节点从而节省时间,由于DFS是将路径逐一求出的,通过在求路径的过程中杀死节点即可省去求所有子节点所花费的时间。FIFO理论上也是可以做到这样的,但是通过对比不难发现,DFS在以这种方法解决问题时思路要清晰非常多。
因此,回溯法可以被认为是一个有过剪枝的DFS过程。
利用回溯法解题的具体步骤
首先,要通过读题完成下面三个步骤:
(1)描述解的形式,定义一个解空间,它包含问题的所有解。
(2)构造状态空间树。
(3)构造约束函数(用于杀死节点)。

然后就要通过DFS思想完成回溯,完整过程如下:
(1)设置初始化的方案(给变量赋初值,读入已知数据等)。
(2)变换方式去试探,若全部试完则转(7)。
(3)判断此法是否成功(通过约束函数),不成功则转(2)。
(4)试探成功则前进一步再试探。
(5)正确方案还未找到则转(2)。
(6)已找到一种方案则记录并打印。
(7)退回一步(回溯),若未退到头则转(2)。
(8)已退到头则结束或打印无解。
回溯法的数据结构
回溯法的状态空间树,在计算机上的数据结构有两种表示方法。当用k表示树的节点层数,n表示节点总数,m表示解的总数时:
(一)用m个k元组表示m种不同的解。其中,每组中的元素是[1,n]中的一个元素。在Pascal中,可以这样定义变量:
var a:array[1..k,1..m]of integer;(integer可以依n的范围决定)
(二)用m个n元组表示m种不同的解。因为所有的节点都包含在每个解的表示中,每组中的元素只有两种情况,被选用和不被选用。在Pascal中,可以这样定义变量:
var a:array[1..n,1..m]of boolean;
这两种数据结构的解空间最多都含有2n个不同的元组。
应用举例
(一)N皇后问题
国际象棋的N皇后问题一直是回溯算法的经典问题。该题题目要求如下:
在国际象棋棋盘上放置八个皇后,使她们不能相吃。国际象棋中的皇后可以吃掉与她处于同一行、同一列、同一斜角线(与行和列分别成45度角的所有线)上的棋子。因此每一行只能摆放一个皇后。因共有八行。所以每行有且只有一个皇后。
下面是N=8时候的具体分析(N为其它值时解法类似):

首先,皇后的位置有一个一维数组来存放A(I)=J表示第I行皇后放在第J列.下面主要来看看怎么样判断皇后是否安全(不会被吃,这就是构造约束函数的过程)的问题。(1)首先,用一维数组来表示,已经解决了不在同一行的问题。(2)对于列可以引进一个标志数组C[J],若J列上已放了皇后,则C[J]=FALSE。(3)对于左上右下的斜角线I-J为一常量,位于[-7,+7]之间,再此引入标志数组L[-7..7];对于左下右上的对角线,类似的有I+J等于常量,用数组R[2..16]来表示.当在第I行,第J列上放置了皇后,则只需设置:C[J]:=FALSE; L[I-J]:=FLASE; R[I+J]:=FALSE就可以解决皇后的安全问题了.
源程序如下:
Program EightQueens;
var cont,i:integer;
a:array[1..8] of byte;
c:array[1..8] of boolean;
l:array[-7..7] of boolean;
r:array[2..16] of boolean;
q:boolean;
procedure pr;
var i:integer;
begin
for i:=1 to 8 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 8 do
begin
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<8 then try(i+1)
else pr;
erase(i);
end;
end;
end;

begin
for i:=1 to 8 do c[i]:=true;
for i:=-7 to 7 do l[i]:=true;
for i:=2 to 16 do r[i]:true;
cont:=0;
i:=1;
try(i);
readln;
end.

(二)圆盘问题
题目要求:从左向右依次安放 4 根细柱 A,B,C,D. 在 A 上套有 N (N≤20) 个直径相同的圆盘, 从下到上依次用连续的小写字母 a,b,c,...编号, 将这些圆盘经过B, C 单向地移入 D (即不允许从右向左移动). 圆盘可在 B,C 中暂存. 从键盘输入 N, 及前 N 个小写字母的一个排列, 它表示最后在 D 盘上形成的一个从下到上的圆盘序列。请用文本文件 ANS2.TXT 输出形成这一排列的操作过程。该文件的每一行为一个形如 "k M L" 的字母序列, 其中 k 为圆盘编号, M 为k 盘原先的柱号, L 为新柱号。或者直接在屏幕上输出"No", 表示不能生成这种排列。

例: ┃ ┃ ┃ ┃
键盘输入: ┃ ┃ ┃ ┃
3 d ━╋━ ┃ ┃ ┃
acb c ━╋━ ┃ ┃ ┃
则一个正确的输出文件 b ━╋━ ┃ ┃ ┃
可以是: a ━╋━ ┃ ┃ ┃
c A B ━━┻━━━┻━━━┻━━━┻━ 
b A C A B C D
a A D
b C D
c B D

本题是递归回溯方法的一个典型的应用.以D柱的圆盘序列为检验的标准,一旦达到目标便结束,否则考虑下一种移动方案.将D柱上的圆盘序列从下到上依次编号为1,2,...,N,利用字母a,b,c,...与数字1,2,...,N的对应关系,在A柱上得到了数字1,2,...,N的一个排列.这一变换在很大程度上简化了算法。
比如,A柱,D柱的字母序列分别为(a,b,c,d,e)和(e,c,a,b,d),则变换后,A柱,D柱的数字序列分别为(3,4,2,5,1)和(1,2,3,4,5)。
容易看出,在D柱上,盘号从下到上必须是连续递增的。在C柱上,盘号从下到上必须是递减的(不要求连续,因为C柱只能移到D柱)。在A和B柱上,盘号的顺序是随意的。
由此可归纳出以下的算法:
1.记四根柱上顶部(即栈顶)的圆盘编号依次为topa,topb,topc,topd,
则能够从 A,B,C 三根柱子直接将顶部圆盘移入D柱的充分必要条件是:topa=succ(topd)或topb=succ(topd)或topc=succ(topd)。
2.此外还有三种移动方式:A=>B,A=>C,B=>C,能进行这三种移动的条件依次是(1)topa>0
(2)topa>0且topa<topc
(3)topb>0且topb<topc。这三种移动方式要递归地进行,且每一步
都要考虑三种移动方式,在作出认真的论证之前,不要轻易地放弃哪一种,以免造成解的丢失。(DFS过程)
3.当A,B,C三根柱子移空时程序正常结束。当递归回溯全部完成而没有得到目标状态时,以“无解”结束。
结语
回溯法适用于在计算机奥林匹克竞赛中解决难以归纳一般规律解法的问题,其适用范围广,灵活性大,在解一些列举方法的问题时尤其可用。但是,其缺点也是明显的,即时间复杂度较大;因此在采用时还需要谨慎。


倚天照海花无数,流水高山心自知。
2007-04-18 00:00
iwfy
Rank: 1
等 级:新手上路
威 望:2
帖 子:888
专家分:0
注 册:2007-2-23
收藏
得分:0 
不容易消化,为什么我看到很多建议尽量少用递归的

版主把此帖子置顶吧

[此贴子已经被作者于2007-4-18 0:27:25编辑过]


英语不好还想学编程??逆天之路,不由分说!! 数学太差还想学编程??离经叛道,义无返顾!!
2007-04-18 00:19
crackerwang
Rank: 3Rank: 3
等 级:新手上路
威 望:8
帖 子:833
专家分:0
注 册:2007-2-14
收藏
得分:0 
看不太懂顶一下先

2007-04-18 12:13
I喜欢c
Rank: 10Rank: 10Rank: 10
等 级:贵宾
威 望:64
帖 子:1749
专家分:0
注 册:2007-3-2
收藏
得分:0 

好东西.....
顶哈~```
正如1楼所说....
不容易消化~``呵呵


 我是指针,却丢失了目标地址!          我是循环,却缺少了结束条件!      我是函数,却没有人来调用!   
2007-04-18 12:51
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
收藏
得分:0 
以下是引用iwfy在2007-4-18 0:19:32的发言:
不容易消化,为什么我看到很多建议尽量少用递归的

版主把此帖子置顶吧

递归效率不高,要频繁的进粘压栈,保护现场,什么的,一大堆.


倚天照海花无数,流水高山心自知。
2007-04-19 10:22
hahahan
Rank: 1
等 级:新手上路
帖 子:48
专家分:0
注 册:2007-4-10
收藏
得分:0 

好难懂
不过也得顶一个


没事儿你就多到这儿转转
2007-04-19 11:00
sunkaidong
Rank: 4
来 自:南京师范大学
等 级:贵宾
威 望:12
帖 子:4496
专家分:141
注 册:2006-12-28
收藏
得分:0 
不懂这么深奥的东西,不过先顶一下.

学习需要安静。。海盗要重新来过。。
2007-04-19 16:14
快速回复:[分享]回朔,看到蛮多题目可以用这个解,找来个,大家看看.
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.026512 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved