人工智能课程设计报告学号:***********姓名:***班级:191091指导老师:***2011年10月14目录1.N皇后问题 (1)需求分析,设计 (1)设计表示 (1)运行结果 (2)用户手册即测试数据 (2)结论 (5)主要算法代码 (5)2罗马尼亚问题 (9)需求分析,设计 (9)设计表示,详细设计 (9)用户手册 (11)运行结果 (11)主要算法代码 (12)3.实习心得 (21)1 N 皇后问题1.问题描述、需求分析在N*N 的棋盘上分布N 个皇后,其中N 个皇后不能在同一行同一列,也不能出现在同一对角线上,此时N 个皇后不会相互攻击。
程序需能手动输入皇后个数,并分别采用回溯法、爬山法、遗传法得出皇后的分布情况,输出皇后的位置即棋盘。
2.设计思想2.1 形式化N 个皇后的位置可用一个N 维数组表示,如921543……,意思是第一个皇后在第一列的第9行。
2.2 程序模块CreatIndividual( )函数用于产生一组表示皇后不在同一行也不再同一列的的一位数组,即产生一组互不相等的0~N 之间的整数,便于快速求解。
IsLegal( )函数用于判断新放置的皇后是否合法,在回溯法中用到。
AttackQueenNum( )用于计算整个棋盘的攻击皇后个数,相当于一个评价函数,在爬山法和遗传法中用到;Find( )回溯法求解函数ClimbHill( )爬山法求解函数; GA( )遗传算法求解函数;(1)函数调用关系图如下:(2)函数接口规格说明:下图中的箭头指向表示为被指向函数所用2.3 详细设计a: CreatIndividual(int *A,int QueenNum):以当时时间为种子循环产生随机数,为了使得产生的随机数都不想等,设计集合S[N]并初始化为0,表示还没有产生一个皇后,当产生的皇后不在S[N]中即S[N]!=1时将S[n]置为1,接着产生下一个皇后,如此循环便产生一组互不相等的值。
b: IsLegal(int *A,int t)此函数用于判断第t列的皇后是否合法,即有没有皇后在同一行、同一列,同一对角线上,并返回true或者false。
c: AttackQueenNum(int *A,int QueenNum)循环调用IsLegal()函数对攻击数进行累加并返回其值,此函数作为棋盘的评价函数。
d: Find(int *A,int k,int QueenNum,long beginTime)回溯法求解,因为回溯法的每一层都是for循环,所以不能单纯的用break语句进行控制,因为即使当前层循环停止了,程序还会继续执行上一层的循环,所以将起始时间作为参数进行传递,以便求出算法执行时间,然后用exit(0)语句终止程序,所以要将回溯法放在其它两个算法的后面执行。
e: ClimbHill(int *A,int QueenNum):由于在产生一个棋盘个体的时候所有的皇后就不在同一行同一列,所以在爬山法中只对棋盘的列进行交换,这样即使再怎么交换所有的皇后还是不在同一行同一列,但在交换的时候需要调用AttackQueenNum( )函数进行棋盘的评价,如果交换前的攻击数大于交换后的攻击数则进行交换,否则与下一列进行交换比较,如此循环直到找出解。
f: GA( )3.用户手册运行程序,输入皇后个数N,各种算法得到的皇后分布情况、耗时自动显示;4.测试数据及测试结果分别测试4,20,30,50皇后,测试结果如下:程序运行结果:4皇后运行结果20皇后运行结果如下30皇后运行结果如下:50皇后运行结果如下由于50皇后的棋盘稍大,这里只给出运行时间结论:根据输入皇后个数递增的运行结果可以看出爬山法的速度是相当快的,在皇后个数比较少时回溯法的速度要快于遗传算法,而当皇后个数比较多时,回溯法的深度搜索明显慢于遗传算法。
主要算法程序代码:1:bool IsLegal(int *A,int t) //判断第t列的皇后位置是否合法{int i;for (i=0;i<t;i++) //首先判断前面几列同一行有没有皇后{if (A[i]==A[t])return false;}if (t>0 && abs(A[t-1]-A[t])==1)//然后再判断是否与相邻前一列皇后发生冲突return false;return true;}2:void CreatIndividual(int *A,int QueenNum){int i,x;//在产生随机数时使用int *s=new int[QueenNum];//集合,用于产生一组皇后位置for (i=0;i<QueenNum;i++)s[i]=0;srand((unsigned)time(NULL));//种子A[0]=rand()%QueenNum;//第一列的皇后可以没有限制的产生,所以先产生s[A[0]]=1;for (i=1;i<QueenNum;i++){do{x=rand()%QueenNum;} while (s[x]==1);//s[x]==1表示此位置已有皇后,则重新产生新的位置A[i]=x;s[A[i]]=1;}}3:void Find(int *A,int k,int QueenNum,long beginTime){int i,j;if (k==QueenNum ){for (i=0;i<QueenNum;i++){printf(" ");for (j=0;j<QueenNum;j++){if (A[j]==i) printf("# ");else printf("O ");}printf("\n");}long endTime = clock();//获得结束时间(endTime-beginTime)<10000,单位为毫秒printf("回溯法 %d 皇后耗时: %d ms\n",QueenNum,endTime-beginTime);exit(0);}else{for (i=0;i<QueenNum;i++){A[k]=i; //对A[k]从0开始进行赋值,下面再判断所赋的值是否合法,合法的话进行下一列皇后位置的赋值if (IsLegal(A,k))Find(A,k+1,QueenNum,beginTime);//当循环结束时仍然找不到则返回一层,A[k]的层数加1}}}4:int AttackQueenNum(int *A,int QueenNum){int i,CountAttack=0;if (abs(A[0]-A[1])==1) //判断第一列CountAttack++;for (i=1;i<QueenNum-1;i++) //首先判断前面几列同一行有没有皇后{if (abs(A[i]-A[i-1])==1) //与前一列是否冲突CountAttack++;if (abs(A[i]-A[i+1])==1) //与后一列是否冲突CountAttack++;}if (abs(A[QueenNum-2]-A[QueenNum-1])==1)//判断最后一列CountAttack++;return CountAttack;}5:void ClimbHill(int *A,int QueenNum){int i,j,temp,Mark,Count,CountAttack; //Mark用于标记交换的位置int MinCountAttack;//在选取移动方案时使用int *SaveTry=new int[QueenNum];//存储临时方案,用于比较CreatIndividual(A,QueenNum);CountAttack=AttackQueenNum(A,QueenNum);MinCountAttack=CountAttack;for (i=0;i<QueenNum;i++)SaveTry[i]=A[i];while (CountAttack!=0){for (i=0;i<QueenNum && MinCountAttack!=0;i++){Mark=-1;MinCountAttack=AttackQueenNum(SaveTry,QueenNum);//在每一列与其他列交换之前MinCountAttack都等于当前的Try的攻击数for (j=0;j<QueenNum && MinCountAttack!=0;j++){if (i!=j) //只与其他列进行交换{temp=SaveTry[j];SaveTry[j]=SaveTry[i];SaveTry[i]=temp;Count=AttackQueenNum(SaveTry,QueenNum);if (Count==0){MinCountAttack=Count;//即为0Mark=j; //记录交换列的位置break; //如果攻击数位0直接跳出循环}if (Count<MinCountAttack){MinCountAttack=Count;Mark=j; //记录交换列的位置}temp=SaveTry[j]; //再将刚刚交换的位置复原,用于与下一列进行比较,交换两次即达到交换的效果SaveTry[j]=SaveTry[i];SaveTry[i]=temp;}}if (MinCountAttack==0) break;if (Mark!=-1){temp=SaveTry[Mark]; //再将刚刚交换的位置复原,用于与下一列进行比较,交换两次即达到交换的效果SaveTry[Mark]=SaveTry[i];SaveTry[i]=temp;}}CountAttack=AttackQueenNum(SaveTry,QueenNum);}for (i=0;i<QueenNum;i++)A[i]=SaveTry[i]; //存储存储最终结果}2 罗马尼亚问题1.需求分析:从文件中读取图和启发函数,分别用Dijkstra、深度优先、广度优先、贪婪算法、A*算法得到从起始点Arad到目标点Bucharest的一条路径,即为罗马尼亚问题的一个解,在求解的过程中记录扩展节点的个数(用于比较几种算法的优劣),记录每种算法得到的解,即输出每种解得到的条路径。
2.设计:2.1 设计思想对于图的存储采用邻接矩阵进行存储,因为此图节点与边比较多(若比较少则采用邻接表结构,此时效率比较高),采用堆栈和队列等进行路径的存储,并且在某条路径走到最大深度都没有发现目标节点时具有返回上一节点的能力(好处:在某条路上找不到时可以进入相邻的一条路径,并不是单纯的返回:索索失败),为了不重复访问同一个节点(此时路径会出现环,导致程序循环执行)利用集合的思想,即将访问过的节点状态置为1没有访问过的置为0,以此来避免路径出现环。