一、需求分析
1.问题描述:
一个农夫带着一只狼、一只羊和一棵白菜,身处河的南岸。他要把这些东西全部运到北岸。他面前只有一条小船,船只能容下他和一件物品,另外只有农夫才能撑船。如果农夫在场,则狼不能吃羊,羊不能吃白菜,否则狼会吃羊,羊会吃白菜,所以农夫不能留下羊和白菜自己离开,也不能留下狼和羊自己离开,而狼不吃白菜。
2.基本要求:
(1)利用图的存储结构
(2)图的搜索算法
(3)求出农夫将所有的东西运过河的所有方案
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<stdio.h> #define M 200 int a=0;//遍寻下一条路径 int b=0;//方案中的值 int c[4],d[4]; int Fa[M*M];//保存方案 int N;//有效的顶点数 typedef struct { int Vertices[M];//存放顶点(比N少) int edge[M][M];//存放边的邻接矩阵 int count; }AdjMGraph; typedef struct stack { int top; int node[M*M]; }Path;//路径栈 int Judge(int a,int b) { int i; int j=0; //int max; c[0]=a/8; d[0]=b/8; a=a%8; b=b%8; c[1]=a/4; d[1]=b/4; c[2]=(a%4)/2; d[2]=(b%4)/2; c[3]=a%2; d[3]=b%2; //printf("%d %d %d %d %d %d\n",c[0],c[1],c[2],d[0],d[1],d[2]);N if(c[0]==d[0])return 0; //else if (c[0]>d[0]){max=c[0];c[0]=d[0];d[0]=max;} for(i=1;i<4;i++) { if(c[i]!=d[i])j++; } if(j<=1)return 1; return 0; } void Initiate(AdjMGraph *G,int n)//邻接矩阵的建立 { int i,j; G->count=n; for(i=0;i<n;i++) for(j=0;j<=i;j++)//初始化 { if(Judge(G->Vertices[i],G->Vertices[j])) { G->edge[i][j]=1; G->edge[j][i]=1; } else { G->edge[i][j]=0; G->edge[j][i]=0; } } } int Push(Path *path,int m) //入栈 { path->node[path->top]=m; path->top++; return 0; } int Top(Path *path ,int *m) //读出栈顶值 { *m=path->node[path->top-1]; return *m; } int GetTop(Path *path,int *m) //出栈 { --path->top; *m=path->node[path->top]; return 0; } int printfpath(Path *path) //复制出出栈中所有值 { int i; i=0; if(path->top==8&&path->node[path->top-1]==9) { while(i<path->top) { Fa[b]=path->node[i]; //printf("%d ",Fa[b]); i++; b++; } } return 0; } void Printf(AdjMGraph *G)//辅助:邻接矩阵输出 { int i,j; for(i=0;i<G->count;i++) { for(j=0;j<G->count;j++) { printf("%d ",G->edge[i][j]); } printf("\n"); } } int DFS(AdjMGraph *G,Path *path,int x,int t)// { int i; int k,p,m; Push(path,x);//遍历过的点入栈 //printfpath(path); //printf("\n"); p=0; k=0; //路径未遍历完 for(i=t;i<N;i++) //搜相关联的顶点 if(G->edge[i][x]>0) { k=1; G->edge[i][x]=0; G->edge[x][i]=0; //此边已访问,删除此边 DFS(G,path,i,0);//找到的是与x关联的i,寻找下一条关联的边 break; } if(k==0) //遍历完当前顶点关联的边 { //if(GetTop(path,&m)==9)jishu++; //if(jishu==2)return 0; printfpath(path); // printf("\n"); while(path->top!=0&&p==0&&a<N-1)//a为点数减1 { GetTop(path,&m); //printf("\n\n%d ",m);//取出 Top(path,&x); // printf("\n\n%d ",x);//读出 for(i=0;i<N;i++) if(G->edge[i][x]>0) { p=1; k=1; //一条路径未遍历完 if(x==0) //从新开始遍历 { G->edge[x][m]=1; G->edge[m][x]=1; GetTop(path,&x); } G->edge[x][m]=1;G->edge[m][x]=1; a=++m; //如果可能的话,从当前节点的下一条关联边开始搜寻 // printf("%d***\n\n",a); GetTop(path,&x); //Printf(G);/// DFS(G,path,x,a);//x被入栈 break; } //printf("%d***\n\n",a); G->edge[x][m]=1;G->edge[m][x]=1;//恢复在上一层中被删除的边 } } return 0; } void Ch(int i) { switch(i) { case 0:printf("人");break; case 1:printf("狼");break; case 2:printf("羊");break; case 3:printf("菜");break; } } int main() { int i,j,k; int dx1,dx2,dx3,dx4; j=0; k=0; AdjMGraph graph; Path path; int a[20];//记不合理的状态,做检验用 //for(;k>0;k--)printf("%d ",graph.Vertices[k-1]); //for(;j>0;j--)printf("\n%d ",a[j-1]); /*踢出状态1001 1000 0111 0110 1100 0011*/ /*for(i=0;i<10;i++)//踢出不合法的状态 { if(i<3)graph.Vertices[i]=i; else if(i>2&&i<5)graph.Vertices[i]=i+1; else if(i>4&&i<7)graph.Vertices[i]=i+5; else graph.Vertices[i]=i+6; }*/ //Printf(&graph); path.top=0; printf("\t\t#####################################\n"); printf("\t\t##### 东西(a,b,c,d)的状态说明 ####\n"); printf("\t\t##### 0代表人"); printf("\t\t1代表狼 ####\n"); printf("\t\t##### 2代表羊"); printf("\t\t3代表菜 ####\n"); printf("\t\t##### a,b,c,d取0表示在河的南岸 ####\n"); printf("\t\t##### a,b,c,d取1表示在河的北岸 ####\n"); printf("\t\t#####################################\n"); printf("\t\t请输入无人时不能在同一侧的东西代号:\n\t\t"); scanf("%d %d",&dx1,&dx2); printf("\t\t"); scanf("%d %d",&dx3,&dx4); while(dx1!=1 || dx2!=2 || dx3!=2 || dx4!=3) { printf("\n\t\t错误!请输入无人时不能在同一侧的东西代号:\n\t\t"); scanf("%d %d",&dx1,&dx2); printf("\t\t"); scanf("%d %d",&dx3,&dx4); } for(i=0;i<16;i=i+2) { Judge(i,i+1);//i为C[],i+1为d[]; if(c[0]!=c[dx1] && c[dx1]==c[dx2])a[j++]=i; else if(c[0]!=c[dx3] && c[dx3]==c[dx4])a[j++]=i; else graph.Vertices[k++]=i; if(d[0]!=d[dx1] && d[dx1]==d[dx2])a[j++]=i+1; else if(d[0]!=d[dx3] && d[dx3]==d[dx4])a[j++]=i+1; else graph.Vertices[k++]=i+1; } //printf("%d\n\n",k); N=k; Initiate(&graph,k); DFS(&graph,&path,0,0); for(i=0;i<b;i=i+2) { Judge(graph.Vertices[Fa[i]],graph.Vertices[Fa[i+1]]); if(graph.Vertices[Fa[i]]==0)printf("\t\tCase %d 南岸(状态) 北岸(状态)\n",(i/8+1)); printf("\t\t("); for(j=0;j<4;j++) { if(j==3)printf("%d",c[j]); else printf("%d,",c[j]); } printf(") "); for(j=0;j<4;j++) if(c[j]==0)Ch(j); printf("\t\t\t"); for(j=0;j<4;j++) if(c[j]==1)Ch(j); printf("\n"); printf("\t\t("); for(j=0;j<4;j++) { if(j==3)printf("%d",d[j]); else printf("%d,",d[j]); } printf(") "); for(j=0;j<4;j++) if(d[j]==0)Ch(j); printf("\t\t\t"); for(j=0;j<4;j++) if(d[j]==1)Ch(j); printf("\n"); } //int Fa[N*N];int c[4],d[4]; //int Judge(,int b) //printfpath(&path); return 0; }
6.农夫过河问题
一、需求分析
1.问题描述:
一个农夫带着一只狼、一只羊和一棵白菜,身处河的南岸。他要把这些东西全部运到北岸。他面前只有一条小船,船只能容下他和一件物品,另外只有农夫才能撑船。如果农夫在场,则狼不能吃羊,羊不能吃白菜,否则狼会吃羊,羊会吃白菜,所以农夫不能留下羊和白菜自己离开,也不能留下狼和羊自己离开,而狼不吃白菜。
2.基本要求:
(1)利用图的存储结构
(2)图的搜索算法
(3)求出农夫将所有的东西运过河的所有方案
二、设计
1. 设计思想
(1)存储结构
以邻接矩阵存储合理状态,用一个一维数组保存所有的方案;
(2)主要算法基本思想
人,狼,羊和白菜共有2的四次方16种状态(河岸的状态可由人的状态确定),去掉不允许出现的6种状态,10个状态对应矩阵的10个结点值,然后根据状态的连续改变初始化矩阵,接着就用递归的深度优先法搜索所有的路径,即求出过河的方案。
|
2. 设计表示
(1)函数调用关系图
main→Judge→Initiate→DFS→Push→StackPop→Top→GetTop→ printfpath→Ch
(2)函数接口规格说明
int Judge(int a,int b) //将16种状态通过a,b输入,去掉不允许的6种
int GetTop(Path *path,int *m) //出栈
int Push(Path *path,int m) //入栈
int Top(Path *path ,int *m) //读出栈顶值
void Initiate(AdjMGraph *G,int n)//邻接矩阵顶点数为n的邻接矩阵G的建立
int DFS(AdjMGraph *G,Path *path,int x,int t) 图G中搜索的起始点为X,从t点开始搜索与x关联的顶点,搜索过的点入栈path。
int printfpath(Path *path) //复制出出栈path中所有值,用FA【】保存
void Printf(AdjMGraph *G)//辅助:邻接矩阵输出,用于观察搜索的过程。
void Ch(int i) //将状态转换为汉字的方案输出
3. 详细设计(主要函数)
【1】int Judge(int a,int b)
将十进制的16种状态分别转换为二进制的数存在一维数组中
判断:
人不在时,羊狼,羊菜不能再一起
从而初始化了矩阵的结点值
【2】Initiate(&graph,k);
通过Judge函数判断可以连续变化的状态
在Judge中人的状态必须一次一遍,而其他东西的状态,最多变一个。根据其返回值将矩阵初始化。
【3】DFS(AdjMGraph *G,Path *path,int x,int t)
递归的深度优先搜索法
int a=0;//遍寻下一条路径
int DFS(AdjMGraph *G,Path *path,int x,int t)
{
int i;
int k,p,m;
Push(path,x);//遍历过的点入栈
p=0;
k=0; //路径未遍历完
for(i=t;i<N;i++) //搜相关联的顶点
if(G->edge[i][x]>0)
{
k=1;
G->edge[i][x]=0;
G->edge[x][i]=0; //此边已访问,删除此边
DFS(G,path,i,0);//找到的是与x关联的i,寻找下一条关联的边
break;
}
if(k==0) //遍历完当前顶点关联的边
{
printfpath(path);
while(path->top!=0&&p==0&&a<N-1)//a为点数减1
{
GetTop(path,&m);
Top(path,&x); for(i=0;i<N;i++)
if(G->edge[i][x]>0)
{
p=1;
k=1; //一条路径未遍历完
if(x==0) //从新开始遍历
{
G->edge[x][m]=1;
G->edge[m][x]=1;
GetTop(path,&x);
}
G->edge[x][m]=1;G->edge[m][x]=1;
a=++m;//如果可能的话,从当前节点的下一条关联边开始搜寻
GetTop(path,&x);
DFS(G,path,x,a);//x被入栈
break;
}
G->edge[x][m]=1;G->edge[m][x]=1;//恢复在上一层中被删除的边
}
}
return 0;
}
三、调试分析
1.调试过程中遇到的主要问题是如何解决的;
该题有两大难点,一是:矩阵的合理逻辑构建
二是:两条方案的搜出
刚开始由于平常做ACM题图论的影响,没有正确把握题意,曲解了题意,在老师的提示下正确的理解了题意。
由于搜图函数已在理解错误下正确写出,所以分析了如何逻辑构图之后,问题很容易就得到解决。在老师的指导下,程序越来越完善。
2.对设计和编码的回顾讨论和分析;
在遇到每个问题的时候,一定要认真冷静的分析题意,以免通过过往经验而误读题意,最后导致与正确结果南辕北辙的后果。
弄懂题意后,要先从程序的整体入手,分析写程序的难点所在,然后联系所学找到可能解决的途径,选出最佳的方法攻破难点。
最后还等考虑程序的安全性,尤其是健壮性的问题,使程序更加的完善,如有可能最好能解决题目之外类似的问题。
3.时间和空间复杂度的分析;
【1】:图的初始化算法 时间O(n*n),空间O(1)
【2】:图的搜索算法 时间O(n*n), 空间O(1)
4.改进设想;
程序能实现预期功能,可有改进空间
【1】:图更简洁初始化方法
【2】:更能解决一般性的问题
5.经验和体会等。
多参加有益的比赛,科研,动手编程,才能熟练灵活的掌握C语言基础知识,才能更好的理解掌握数据结构的精髓。从而避免基础语法错误,让代码变得更简洁高效。如此才能准确高效的解决问题。
四、用户手册(即使用说明)
仅需按照提示的输入即可。若出错,则重新来过。
五、运行结果
运行环境:codeblocks
测试用表:1 2 3 4
0 1 2 3
1 2 2 3
运行后的界面
测试数据:1 2 3 4
**输入不符合要求或不满足题意时,无法输出正确结果
****在输入完后,核对输入是否符合题意,如若不然提示出错,重新输入
(已改正)测试数据:1 2 3 4
正确测试数据:1 2 2 3
六、源程序清单
所有函数均在一个 农夫过河.c 文件中