算法与数据结构程序设计报告B08030111.docx

上传人:b****5 文档编号:6734811 上传时间:2023-01-09 格式:DOCX 页数:17 大小:280.91KB
下载 相关 举报
算法与数据结构程序设计报告B08030111.docx_第1页
第1页 / 共17页
算法与数据结构程序设计报告B08030111.docx_第2页
第2页 / 共17页
算法与数据结构程序设计报告B08030111.docx_第3页
第3页 / 共17页
算法与数据结构程序设计报告B08030111.docx_第4页
第4页 / 共17页
算法与数据结构程序设计报告B08030111.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

算法与数据结构程序设计报告B08030111.docx

《算法与数据结构程序设计报告B08030111.docx》由会员分享,可在线阅读,更多相关《算法与数据结构程序设计报告B08030111.docx(17页珍藏版)》请在冰豆网上搜索。

算法与数据结构程序设计报告B08030111.docx

算法与数据结构程序设计报告B08030111

回溯法应用

一、课题内容和要求

在人机对弈、决策问题、人工智能、组合数学等等一系列非数值问题的算法设计中,回溯法是经常采用的一种重要而有效的方法。

回溯法是一种选优搜索法。

按选择最优解的条件向前搜索,以达到目的。

但每当搜索到某一步时,发现其达不到预期的效果,就退回一步重新选择。

这种行不通就退回的技术称为回溯法。

回溯法的逻辑思路可表示为一棵数,根节点是初始状态,没搜索到一个节点都有若干个可供选择的后续节点,没有任何能够达到目标的暗示,只有走着瞧,不行了就回溯到上一层节点,回复刚刚使用的参数,再走另一条路径,所以回溯法的本质是穷举与试探,找到从根节点到叶子节点的所有正确结果。

八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。

该问题是十九世纪著名的数学家高斯1850年提出:

在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

二、需求分析

运用面向对象思想,将八皇后问题的解决放在一个类中来解决。

“构造函数模块”用来实现数据的初始化。

“主程序模块”,负责调用其他函数依次在一到八行放置皇后,并按一定规律调整皇后位置,求出八皇后问题的所有解,并输出到文件。

“皇后放置模块”,在解决问题过程中,需要将“皇后”依次放置到第一、二、……、八行。

该模块负责尝试在某一行放置一个“皇后”,放置成功返回一个“成功”值,说明当前皇后放置在某一个位置上可能存在“解”。

否则失败的话,返回“失败”值,说明当前行无论怎样放置皇后,都得不到解,只有调整上面行的皇后的位置。

“输出模块”,负责将某一种皇后放置的方法写到文件中以供查看。

三、概要设计

主类成员变量及函数原型声明:

classQueen

{

private:

intlocation[9];

//location[i](i大于,小于等于)表示在(i,location(i))的位置放置了一个“皇后”

boolLR[16];

//LR[i](i大于,小于等于)从左上角往右下角数第i条斜线上是否有皇后,true为真

intlr_orgin[16];

//lr_origin用以记录左上角往右下角数第i条斜线被哪一行的皇后占用,未被占用为0

boolRL[16];

//RL[i](i大于,小于等于)从右上角往左下角数第i条斜线上是否有皇后,true为真

intrl_orgin[16];

//rl_origin用以记录从右上角往左下角数第i条斜线被哪一行的皇后占用,未被占用为0

boolline[9];

//line[i](i大于,小于等于8)表示在第i列上是否有皇后,true为真

intline_orgin[9];

//line[i](i大于,小于等于8)表示在第i列上被哪一行的皇后占用,未被占用为0

intnum;//用以统计解的个数

public:

Queen();//构造函数实现数据的初始化

boolmytry(inti);//测试第i行皇后的位置,成功返回true,失败返回false;

intSolveProblem();//八皇后问题的解决

voidResult();//输出结果,*8的样式显示,在皇后安放处显示一个"Q"

};

成员变量设计:

数组location[9]用来记录一至八行各个皇后所在列的序号(location[0]不使用)。

LR[16]负责记录从左上角往右下角数,一至十六条斜线是否被占用(即实现皇后间的相互影响)。

由于回溯时需要消除已经放置的部分皇后产生的影响(即改变LR[16]数组的值),所以必须记录某一个值的改变是有哪一行的皇后引起的,否则无法知道哪些影响是该消除的,所以需要设计一个数组lr_origin[16],他的元素与LR[16]一一对应,每当某一条斜线被占用时,先将LR的某一元素的值改变,同时再在lr_origin中相应元素中记录下此改变是由某一行引起的。

数组RL[16]、rl_origin[16]道理相同,只不过是记录从右上角至左下角第i条斜线的使用情况。

此外,某一列是否被占用,被哪一行的皇后占用,也需要记录下来,所以设计bool数组line[9],和int型l数组ine_origin[9]。

有了这些,就可以记录某一皇后放置的位置,及产生的影响,同时也可以在需要的时候将相应的影响消除,便于重新放置皇后。

成员函数设计:

类的构造函数,实现数据的初始化。

一个函数负责调用其他函数解决八皇后问题。

由于八皇后问题需要不断尝试在某一行放置一个皇后,所以将这一动作也封装到一个函数中,用一个函数实现,第几行放置皇后就将几作为参数传进去。

找到一种解法后需要输出结果,将结果的输出放置到一个函数中实现。

四、详细设计

1、类的设计具体解释见“概要设计”,不再重复

类Queen()的声明:

classQueen

{

private:

intlocation[9];

//location[i](i大于,小于等于)表示在(i,location(i))的位置放置了一个“皇后”

boolLR[16];

//LR[i](i大于,小于等于)从左上角往右下角数第i条斜线上是否有皇后,true为真

intlr_orgin[16];

boolRL[16];

//RL[i](i大于,小于等于)从右上角往左下角数第i条斜线上是否有皇后,true为真

intrl_orgin[16];

boolline[9];

//line[i](i大于,小于等于)表示在第i列上是否有皇后,true为真

intline_orgin[9];

intnum;//统计解法的数量

public:

Queen();

boolmytry(inti);

intSolveProblem();

voidResult();

};

2、类Queen的构造函数主要负责将数据初始化,主要是几个数组,将其中的元素全部清零,用于统计方法数的num也置为零:

Queen:

:

Queen(){//构造函数实现数据的初始化

num=0;

intk;

for(k=0;k<=8;k++)

{

location[k]=0;

line[k]=false;

line_orgin[k]=0;

}

for(k=0;k<=15;k++)

{

LR[k]=false;

lr_orgin[k]=0;

RL[k]=false;

rl_orgin[k]=0;

}

}

3、八皇后问题解决函数。

初始将8个皇后都放在棋盘的外列,即数组location的值全为0。

然后从第一行开始放置皇后,每一行又从第一列开始扫描。

如果在第flag行找到了合适位置,则将其位置存储到location[flag]中,同时将相应的列、斜线置为“被占用”状态。

然后看下一行,即flag++,如果在找到了合适位置,则存入数组location[flag]中,如果没有合适的位置,那就说明上一行皇后的位置不能产生解。

此时,就应该重新调整上一行(即flag--)皇后的位置,调整过后再继续往下放置皇后,(即回溯)。

每当第8行成功放置了一个皇后就找到了一个“解”,输出之。

当全部的可能都试过以后,就求出了八皇后问题的全部解:

intQueen:

:

SolveProblem(){//八皇后问题的解决

intflag;

//flag作为标志位,表示当前第flag行进行尝试,看可不可以放置一个皇后

intk;

flag=1;//从第一行开始尝试

while(flag>0)

{

if(mytry(flag)==true)//如果在第flag行可以找到合适的位置放置“皇后”

{

if(flag==8){

num++;

//当前是第行皇后找到合适的位置,解法数加一,输出结果

Result();

flag--;

//标志位后移一位,即下次继续调整上一行皇后的位置

}else{

flag++;

//不是最后一行,则flag加一,继续找下一行的皇后的位置

}

}

else{

flag--;//找不到就把flag减一,即下次调整上一行皇后的位置

}

}

returnnum;

}

4、在第i行放置一个皇后函数。

上一步可能是在i-1行成功放置了一个皇后,也有可能是在i+1行放置皇后失败,“回溯”到第i行。

所以,如果i+1行放置过皇后,需要将i+1行皇后产生的影响全部消除,然后再尝试在第i放置皇后。

如果当前行皇后的位置已经在第八列,则直接返回“失败”。

否则,继续尝试。

将location[i]的值不断加1(当然控制在小于等于八),检查该新的皇后的位置,该列是否被占用,所在的斜线上是否已经放置过皇后。

如果有冲突,则继续检查下一列,如果无冲突,则将皇后位置固定,将该列、所在两条斜线置为“被占用”状态,记录下被第i行占用,返回“成功”:

boolQueen:

:

mytry(inti){//测试第i行皇后的位置,成功返回true,失败返回false;

intk;

if((i+1)<=8)

{

location[i+1]=0;

}

for(k=0;k<=8;k++)

{

if(line_orgin[k]==i||line_orgin[k]==i+1)

//i行及以后i+1行皇后放置产生的影响全部消除

{

line_orgin[k]=0;

line[k]=false;

}

}

for(k=0;k<=15;k++)

{

if(rl_orgin[k]==i||rl_orgin[k]==i+1)

//i行及以后i+1行皇后放置在RL斜线上产生的影响全部消除

{

rl_orgin[k]=0;

RL[k]=false;

}

}

for(k=0;k<=15;k++)

{

if(lr_orgin[k]==i||lr_orgin[k]==i+1)

//i行及以后i+1行皇后放置在LR斜线上产生的影响全部消除

{

lr_orgin[k]=0;

LR[k]=false;

}

}

if(location[i]==8)

//如果当前行皇后的位置在最后一列,则放弃往后尝试,直接返回失败

{

returnfalse;

}

for(++location[i];location[i]<=8;location[i]++)

{

if((line[location[i]]==0)&&(LR[i+location[i]-1]==false)&&(RL[i-location[i]+8]==false))

//在i行,location[i]列可以放置皇后

{

line[location[i]]=1;

//location[i]列标记为已占用

line_orgin[location[i]]=i;

//location[i]列被i行的皇后占用

LR[i+location[i]-1]=true;

//从左上往右下角第i+location[i]-1条斜线被标记为占用

lr_orgin[i+location[i]-1]=i;

//从左上往右下角第i+location[i]-1条斜线被标记被第i行占用

RL[i-location[i]+8]=true;

//从右上往左下角第i-location[i]+8条斜线被标记为占用

rl_orgin[i-location[i]+8]=i;

//从右上往左下角第i-location[i]+8条斜线被标记被第i行占用

returntrue;

}

}

returnfalse;

}

5、结果输出函数,结果保存在location数组中,为了便于查看,将结果写入到文件“result.txt”中,当然,可以进行适当的格式控制,在8*8的格子中,若未放置皇后显示“_”,反之,显示“Q”:

voidQueen:

:

Result(){

//输出结果,8*8的样式显示,在皇后安放处显示一个"Q"

intj,k;

ofstreamfout("result.txt",ios:

:

app);

fout<<"解法"<

"<

for(j=1;j<=8;j++)

{

for(k=1;k

{fout<<"_";}

fout<<"Q";

for(k=location[j]+1;k<=8;k++)

{

fout<<"_";

}

fout<

}

fout<

}

6、main函数,先生成Queen对象,调用其中的SolveProblem方法,最后显示总的解法数目,结束程序:

intmain()

{

Queenq;

cout<<"共有"<

return0;

}

五、测试数据及其结果分析

程序运行结果如下:

控制台显示解法总数,提示结果保存在“result.txt”中:

result.txt保存在当前文件夹下:

result.txt中显示皇后的放置方法:

六、调试过程中的问题

为了解决八皇后问题,第一反应是利用八重循环,一次在八行,八列上放置皇后,从中选择正确解,但是如果对全部位置检查,需要检查8^8次,耗时太长。

接着就考虑使用回溯法,提高效率。

回溯法有两个关键问题要考虑:

一是如何实现皇后之间的互相影响,即某一列放置皇后后,其他皇后可以“知道”避开这一列;二是回溯以后,调整上一行皇后的位置,那么下一行“皇后”的位置必须“清零”,即从第一列开始重新变化,且消除原来放置时产生的影响。

常用方法是利用一个8*8的二维数组,共64个元素,初始值为0,然后放置皇后,每放置一个皇后以后,就将相应的行、列、斜线全部置为1,这样下次放置时,只要看相应位置上的值是否为1,若为1,则表示有冲突,考虑下一个位置。

回溯是,将相应的行、列、斜线上的1改为0即可。

这样做,比较简单,但是由于回溯的次数很多,如果采用此算法,每次需要修改很多值得大小,效率不高。

为了使程序更加简单、快捷。

可以考虑利用数个一维数组解决问题。

一个一维数组L[9]表示八列,初始值为false,某一列被占用后,就将相应列的值改为true。

同样的道理,再建立两个一维数组,LR[16]表示从左上角往右下角数的斜线,RL[16]从右上角往左下角数的斜线,初始值同样置为false,当有皇后放置在该斜线上时,将相应的值改为ture。

如果要在第i行,第j列放置一个皇后,只需保证L[j]、LR[i+j-1]、RL[i-j+8]的值都为false,如果不能保证,则表示有冲突,寻找下一个位置。

按照这个思路,我们编写程序,运行起来的不到结果。

单步调试发现,问题发生在回溯时的影响消除上。

对于影响的消除,我们一开始的设想是这样的:

从第i+1行回溯到第i行时,在调整皇后位置之前,先检查location[i+1],将L[location[i+1]]的值修改成false。

但是只有这样是不够的,程序跑不出结果。

斜线上的影响也必须消除掉,但是斜线上的几个值都被修改了,无法判断哪个值是第i+1行的皇后放置时修改的。

为了解决这一问题,设计一个辅助数组lr_origin[16],rl_origin[16],用以记录某一条斜线的值被哪一行的皇后修改。

初始时值都置为0,当数组LR、RL的元素的值被第i行修改时,将lr_origin、rl_origin的值置为i。

回溯到i行时,遍历lr_origin、rl_origin两个数组,如果某一个值等于i+1,就将相应的LR、RL数组元素的值变为false,同时lr_origin、rl_origin的这两个元素值也清零。

代码修改如下:

for(k=0;k<=15;k++)

{

if(rl_orgin[k]==i||rl_orgin[k]==i+1)

{

rl_orgin[k]=0;

RL[k]=false;

}

}

for(k=0;k<=15;k++)

{

if(lr_orgin[k]==i||lr_orgin[k]==i+1)

{

lr_orgin[k]=0;

LR[k]=false;

}

}

另一个关键问题是主循环及流程控制问题,八皇后设计到同样方法(在某一行放置皇后)的反复调用,因此,必须巧妙控制整个流程,适当的时候输出解,适当的时候回溯,最后将所有解全部求出来,又能在适当的时候跳出循环。

考虑到皇后是一行一行的放置的,从一到八,每次对其中的一行进行处理。

而第八行成功放置皇后即代表找到一个合适的解,输出之。

如果某一行找不到合适位置,需要调试上一行皇后的位置,继续寻找,所以可以设计一个标志位(即程序中的flag),初始值为1,即从第一行开始放置皇后,如果成功,flag++,往下一行寻找,如果失败,flag——,调整上一行。

每次皇后找到合适位置,判断当前flag是否为8,如果是的话,则找到一个结果,输出之,并令flag--继续寻找。

最后,当第一行(flag=1)八个位置全部考虑过以后,皇后无法往后调整,flag--,此时flag变为0,则结束程序。

编写代码如下:

flag=1;

while(flag>0)

{

if(mytry(flag)==true)

{

if(flag==8){

num++;

Result();

flag--;

}else{

flag++;

}

}

else{

flag--;

}

}

八皇后改进:

八皇后问题做出来以后,将其改进成N皇后问题。

N皇后问题与八皇后问题算法思想上类似,区别在于数组规模上,八皇后问题中数组大小是固定的,而在N皇后问题中是不定的,所以需要使用指针,在程序开始时动态的申请内存空间。

回溯、判断、修改值时也许要根据N的大小稍作变换。

由于N皇后问题结果可能很多(N稍大一点),所以没有将结果写到文件中,而是直接将location数组输出在屏幕上。

修改后的几段核心代码如下(全部代码另外提交):

初始时输入N并动态申请内存:

cout<<"输入皇后数N:

"<

cin>>n;

location=newint[n+1];

line=newbool[n+1];

line_orgin=newint[n+1];

LR=newbool[2*n];

lr_orgin=newint[2*n];

RL=newbool[2*n];

rl_orgin=newint[2*n];

算法主循环(在8皇后基础上只需将8变成n):

while(flag>0)

{

if(mytry(flag)==true)

{

if(flag==n){

num++;

Result();

flag--;

}else{

flag++;

}

}

else{

flag--;

}

}

N皇后程序运行效果截图:

初始时输入皇后数:

输入13后运行结果如下:

必须知道的是,单靠回溯法,N皇后问题在N大于15以后,结果就很难出来(需要等很久)。

因为潜在的方法数是N^N,每当N加1,需要测试的可能性会像几何级数一样迅速增加。

除了上面已经实现的n皇后的改进外,还有几点可以改进:

1、如果flag++以后,那么是不需要消除影响的,消除影响只是在flag—以后,即回溯时需要。

2、所需一维数组数量可以进一步减少,LR与lr_origin,RL与rl_origin都只需要保留后者即可,后者为0即表示未被占用,否则表示被占用。

七、程序设计总结

这次程序设计,做回溯法,一开始没有什么思路,所以事先去网上找了相关的算法思想介绍。

详细了解算法思想后,开始独立编写程序。

将一段文字描述转化成程序,中间还是有一些困难的。

主要问题出在许多细节问题没有搞清楚,就开始编程,结果导致编出来的代码运行不出结果。

后来开始单步调试,一步步跟踪,边调试边检查,最后终于发现了程序设计的大漏洞,加以弥补之后,运行,稍加修改,程序终于成功了。

由于最后程序编写好之后还剩余一段时间,就利用这段时间,在原有八皇后基础上,改写出了可以解决N皇后问题的程序。

但是由于N皇后问题自身的复杂性,以及程序本身算法限制。

导致当N大于15以后,结果就很难跑出来,这点以后有待提高。

收获:

1、对回溯算法有了更加深入的了解。

学会了用回溯法解决实际问题。

2、学会分析算法的复杂度,并改进程序,提高效率。

3、进一步掌握程序单步调试、分析、修改的方法。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1