总算是把上学期实验欠下的三道选做尝试了一下,小白摸爬滚打,不过欣慰的是画上了一个句号。
海龟图
【题目描述】
龟图logo语言在个人计算机用户中非常流行,该语言形成了龟图的概念。假设有个机器海龟,通过c++程序控制在房子中移动。在两个方向之一打开画笔,即向上或向下。画笔向下时,海龟跟踪移动的形状并留下移动的路径,画笔向上时,海龟自由移动,不写下任何东西。在这个问题中,要模拟海龟的操作和生成计算机化的草图框。
用20 × 20数组floor,初始化为0。从数组中读取命令。跟踪任何时候海龟的当前位置和画笔的向上或向下状态。假设海龟总是从位置(0,0)开始,画笔向上。程序要处理的海龟命令如下:
Figure . Turtle graphics commands. |
---|
Command | Meaning |
---|---|
1 | Pen up |
2 | Pen down |
3 | Turn right |
4 | Turn left |
5,10 | Move forward 10 spaces (or a number other than 10) |
6 | Print the 20-by-20 array |
9 | End of data (sentinel) |
假设海龟接近平面中心。下列“程序”绘制和打印12*12的正方形并让画笔向上:
2
5 12
3
5 12
3
5 12
3
5 12
1
6
9
画笔向下并移动海龟时,将数组floor的相应元素设置为1。指定命令6(打印)时,只要数组中有1.就显示星号或选择其他符号,而0则显示空白。编写一个程序,实现这里介绍的龟图功能。编写几个龟图程序,画一些有趣的图形。增加其他命令以增加龟图语言的功能。
【结果输出】
【我的代码】
#include<iostream>
using namespace std;
void getCommands(int [][2]);
int turnRight(int);
int turnLeft(int);
void movePen(bool,int [20][20],int,int);
void print(int [20][20]);
int main()
{
int floor[20][20]={0};//floor表示画板大小
int k=0,dir=0;//0为向右移动,1为向下移动,2为向左移动,3为向上移动
bool penDown=false;
int commands[400][2]={0};//假定400为命令输入上限
getCommands(commands);
int instruction=commands[k][0];
while(instruction!=9)
{
switch(instruction)
{
case 1:penDown=false;break;
case 2:penDown=true;break;
case 3:dir=turnRight(dir);break;
case 4:dir=turnLeft(dir);break;
case 5:movePen(penDown,floor,dir,commands[k][1]);break;
case 6:cout << "\nThe drawing is:\n\n";print(floor);break;
default:break;
}
instruction=commands[++k][0];
}
system("pause");
return 0;
}
void getCommands(int commands[][2])
{
int t,i;
cout<<"Enter command(9 to end input): ";
cin>>t;
for(i=0;t!=9&&i<400;i++)
{
commands[i][0]=t;
if(t==5)cin>>commands[i][1];
cout<<"Enter command(9 to end input): ";
cin>>t;
}
commands[i][0]=9;
}
int turnRight(int dir)
{
return ++dir > 3 ? 0 : dir; //原点为0,右转4次回到原点
}
int turnLeft(int dir)
{
return --dir < 0 ? 3 : dir;
}
void movePen(bool penDown,int floor[20][20],int dir,int d)
{
static int x=0,y=0;//注:静态变量在每次调用函数时它的值都保存下来
int i;
switch(dir)
{
case 0://向右
for(i=0;i<d&&(y+i)<20;i++)
{
if(penDown)floor[x][y+i]=1;
else floor[x][y+i]=0;
}
y=y+d-1;
break;
case 1://向下
for(i=0;i<d&&(x+i)<20;i++)
{
if(penDown)floor[x+i][y]=1;
else floor[x+i][y]=0;
}
x=x+d-1;
break;
case 2://向左
for(i=0;i<d&&(y-i)>=0;i++)
{
if(penDown)floor[x][y-i]=1;
else floor[x][y-i]=0;
}
y=y-d+1;
break;
case 3://向上
for(i=0;i<d&&(x-i)>=0;i++)
{
if(penDown)floor[x-i][y]=1;
else floor[x-i][y]=0;
}
x=x-d+1;
break;
}
}
void print(int floor[20][20])
{
for(int i=0;i<20;i++)
{
for (int j=0;j<20;j++)
{
if(floor[i][j]==0) cout<<" ";
else cout<<"*";
}
cout<<endl;
}
}
骑士问题
【问题描述】
(1)
(2)(骑士旅行:强制方法)
骑士旅行,可以用“访问性试探”能产生许多解法并可以有效地执行。随着计算机的运算能力越来越强,可以用更简单的算法解决这个问题,即采用强制算法。
a) 用随机数产生程序让骑士在棋盘上按L形走法任意移动:程序一步一步走完这个棋盘。骑士能走多远?
b) 通常.上述程序不会走太远。现在修改程序,进行1000次走法。用单下标数组跟踪每次走了几步。程序完成1000次旅行后,以表格形式打印这些信息。最佳结果是付么?
c) 通常,上述程序能得到较好地走法,但无法走遍棋盘。现在删除次数限制,让程序一直运行,直到找出走遍棋盘的方法(注意,可能要好几个小时才能在强大的计算机上完成)。以表格形式打印这些信息,看看要多少次才能找出走遍棋盘的方法,花多少时间。
d) 比较强制算法与前面介绍的访问性试探方法。哪个需要更认真地分析问题,哪个算法更难开发,哪个需要更强大的计算机功能,利用访问性试探能否事先保证找出走遍棋盘的方法,利用强制算法能否事先保证找出走遍棋盘的方法,比较两种方法的利与弊。
(3)(骑士族行:闭合线路)
在骑士旅行问题中,骑士经过64格中的每一格一次,且只经过一次。闭合线路就是最后又回到出发的地方。修改骑士旅行程序,测试闭合线路是否走遍了棋盘。
【结果输出】
【我的代码】
算法真是一个奇妙的东西,感谢一开始题目的引导让我首先用访问性试探方法解决了骑士问题,其核心在于下一次移动时移到访问性值最小的格子,并且需要注意的是访问性值是随着每一次可尝试访问而减少的,这真的得很细心很细心,马大哈的我就是在这上面百思不得其解,不过好得攻克了。(后来,我又知道了,它的学名叫“贪心算法”)
//访问性试探方法
#include<iostream>
#include<cstdlib>
#include<ctime>
#include<iomanip>
using namespace std;
void printBoard(int [8][8]);
int main()
{
int board[8][8]={0},
//表示一个8*8棋盘
currentRow,currentColum,
//表示骑士当前位置的行与列
moveNumber=0,
//进行moveNumber类型的移动(moveNumber在0到7之间)
//例:0类型的移动为水平右移两格和垂直上移一格
testRow,testColum,minAccessNumber,minRow,minColum;
int horizontal[8]={2,1,-1,-2,-2,-1,1,2};//左移为负数
int vertical[8]={-1,-2,-2,-1,1,2,2,1};//上移为负数
int access[8][8]={ 2,3,4,4,4,4,3,2,
3,4,6,6,6,6,4,3,
4,6,8,8,8,8,6,4,
4,6,8,8,8,8,6,4,
4,6,8,8,8,8,6,4,
4,6,8,8,8,8,6,4,
3,4,6,6,6,6,4,3,
2,3,4,4,4,4,3,2, };
//棋盘上任一格子上访问性值是该格子能访问的格数
bool over=false;
srand(time(0));
currentRow=rand()%8;
currentColum=rand()%8;//从棋盘上任一一格开始运行64次旅行
board[currentRow][currentColum]=++moveNumber;
while(!over)
{
minAccessNumber=9;
for(int i=0;i<8;i++)
{
testRow=currentRow+vertical[i];
testColum=currentColum+horizontal[i];
if(testRow>7||testRow<0||testColum>7||testColum<0)continue;
//确认进行i类型的移动后不会出棋盘
if(board[testRow][testColum]==0)//确认之前未走到该点
{
if(access[testRow][testColum]<minAccessNumber)
{
minAccessNumber=access[testRow][testColum];
minRow=testRow;
minColum=testColum;
}//为了移到下次移动时访问性值最小的格子
access[testRow][testColum]--;
//这步可知上一步位置不可访问,所以访问性值-1
}
}
if(minAccessNumber==9) over=true;//找不到未走过的点,循环结束
else {
currentRow=minRow;
currentColum=minColum;
board[currentRow][currentColum]=++moveNumber;}
}
cout<<"The tour ended with "<<moveNumber<<" moves."<<endl;
cout<<"This was a full tour!\n"<<endl;
printBoard(board);
system("pause");
return 0;
}
void printBoard(int board[8][8])
{
cout<<" 0 1 2 3 4 5 6 7"<<endl;
for(int i=0;i<8;i++)
{
cout<<i;
for(int j=0;j<8;j++)
cout<<setw(3)<<board[i][j];
cout<<endl;
}
}
接下来,题目引导尝试强制算法,一开始在次数限制的情况下的确无法走遍棋盘,删除次数限制,让程序一直运行,直到找出走遍棋盘的方法(注意,可能要好几个小时才能在强大的计算机上完成)。
之后访问大佬的博客,这种算法在计算人圈中学名是不是就叫“回溯算法”(还是不敢断言),采用穷举的方式,所以运行时间的长就可想而知。但是这其中也包含递归算法(我是那么理解的),所以也不是真正意义上的穷举啦!
//回溯算法
#include<iostream>
#include<cstdlib>
#include<ctime>
#include<iomanip>
using namespace std;
void printSolution(int [8][8]);
bool findSolution(int ,int ,int );
int board[8][8]={0};
int horizontal[8]={2,1,-1,-2,-2,-1,1,2};
int vertical[8]={-1,-2,-2,-1,1,2,2,1};
int main()
{
int currentRow,currentColum,moveNumber=1;
srand(time(0));
currentRow=rand()%8;
currentColum=rand()%8;//从棋盘上任一一格开始运行64次旅行
if(findSolution(currentRow,currentColum,moveNumber))
{
cout<<"The tour ended with 64 moves."<<endl;
cout<<"This was a full tour!\n"<<endl;
printSolution(board);
}
else cout<<"Solution does not exist"<<endl;
system("pause");
return 0;
}
bool findSolution(int currentRow,int currentColum,int moveNumber)
{
board[currentRow][currentColum]=moveNumber;
if(moveNumber==64) return true;
int testRow,testColum;
for(int i=0;i<8;i++)
{
testRow=currentRow+vertical[i];
testColum=currentColum+horizontal[i];
if(testRow>=0&&testRow<8&&testColum>=0&&testColum<8&&board[testRow][testColum]==0)
{
if(findSolution(testRow,testColum,moveNumber+1))return true;
}
}
board[currentRow][currentColum]=0;
//回溯操作,表示这一步往后没有解决方案,归零,从上一步开始另外寻找解决方案
return false;
}
void printSolution(int board[8][8])
{
cout<<" 0 1 2 3 4 5 6 7"<<endl;
for(int i=0;i<8;i++)
{
cout<<i;
for(int j=0;j<8;j++)
cout<<setw(3)<<board[i][j];
cout<<endl;
}
}
运行时间那就看随机数开始的位置了,一切都是随缘,10+min,1+h甚至可能更久(哇!这是我第一次遇到!)
八皇后问题
【问题描述】
(八皇后) 国际象棋中的另一个难题是八皇后问题。
简单地说,空棋盘上能否放八个皇后,使一个皇后不会“攻击”另一个皇后,即不会有两个皇后在同一行、同一列或同—对角线上。
(1) 用访问性试探法解决八皇后问题
(提示:棋盘上的每一格可以指定一个值,表示一个皇后可以“删除”多少个格子,四个角落指定数值22,如下图);在64个格子中放上这些“删除”数之后,问题就变成将下一个皇后放在删除数最少的格子中。
(2) (八皇后:强制算法)
这个练习要用几个强制算法解决八皇后问题。
a) 用随机强制算法解决八皇后问题。
b) 用穷举法,即测试八个皇后在棋盘上的各种组合。
c) 说明穷举法为什么不适用于骑士旅行间题?
d) 比较随机强制算法与穷举法。
(3) 用递归方法解决八皇后问题
【结果输出】
首先通过数学方法知,八皇后问题在不考虑旋转与翻转等价时,共有92种不同的解。
这是回溯算法得到的第一个解(如下)。
当然我们还可以不停止循环,用变量sum做一点改变就能得到92种答案。
剩下72个实在不想继续截下去了,原谅我的懒惰,当它72变变没了吧,嘻嘻。
【我的代码】
经过骑士问题后,我是真的喜欢访问性试探法,因为我是懒癌患者。但是八皇后问题我并没有首当其冲用这个解决出来。可见,方法的好坏还是得看题目而定,嘤嘤嘤。
反而,我采用了回溯算法解决出来,因为它很好设计,理解上也较为容易。我设计了程序:找到第一解,就结束循环,所以其运行结果是唯一的。
//回溯算法
#include<iostream>
#include<iomanip>
using namespace std;
int b[9]={0},
//b[1..8]控制同一列只有一个皇后
d[15]={0},c[17]={0};
//d[-7..7]、c[1..16]控制同一对角线上只能有一个皇后
int a[9];//a[i]=j表示第i个皇后放在第i行第j列(八个皇后一定是不同行,所以一行一行的放)
void search(int);
void print();
int main()
{
search(1);//从第1个皇后开始放置
system("pause");
return 0;
}
void search(int i)
{
for(int j=1;j<=8;j++)
if((!b[j])&&(!c[i+j])&&(!d[i-j+7]))//寻找放置皇后的位置
//+7是由于数组不能为负
{
a[i]=j;//摆放皇后
b[j]=1;//表示占领第j列
c[i+j]=1;//在/斜线上,行列值之和相同
d[i-j+7]=1;//
if(i==8){print();break;}//8个皇后都放置好,输出
else search(i+1);
b[j]=0;//递归返回即为回溯一步,当前皇后退出
c[i+j]=0;
d[i-j+7]=0;
}
}
void print()
{
cout<<" 1 2 3 4 5 6 7 8"<<endl;
for(int i=1;i<=8;i++)
{
cout<<i;
for(int j=1;j<=8;j++)
if(a[i]==j)cout<<setw(3)<<"Q";
else cout<<setw(3)<<"*";
cout<<endl;
}
}
一点小改变(不停止循环直到输出所有结果):
#include<iostream>
#include<iomanip>
using namespace std;
int b[9]={0},
//b[1..8]控制同一列只有一个皇后
d[15]={0},c[17]={0};
//d[-7..7]、c[1..16]控制同一对角线上只能有一个皇后
int sum=0,a[9];//a[i]=j表示第i个皇后放在第i行第j列(八个皇后一定是不同行,所以一行一行的放)
void search(int);
void print();
int main()
{
search(1);//从第1个皇后开始放置
system("pause");
return 0;
}
void search(int i)
{
for(int j=1;j<=8;j++)
if((!b[j])&&(!c[i+j])&&(!d[i-j+7]))//寻找放置皇后的位置
//+7是由于数组不能为负
{
a[i]=j;//摆放皇后
b[j]=1;//表示占领第j列
c[i+j]=1;//在/斜线上,行列值之和相同
d[i-j+7]=1;//在\斜线上,行列值之差相同
if(i==8){print();}//8个皇后都放置好,输出
else search(i+1);
b[j]=0;//递归返回即为回溯一步,当前皇后退出
c[i+j]=0;
d[i-j+7]=0;
}
}
void print()
{
sum++;//方案数+1
cout<<"case "<<sum<<":"<<endl;
cout<<" 1 2 3 4 5 6 7 8"<<endl;//输出一种方案
for(int i=1;i<=8;i++)
{
cout<<i;
for(int j=1;j<=8;j++)
if(a[i]==j)cout<<setw(3)<<"Q";
else cout<<setw(3)<<"*";
cout<<endl;
}
}
我真的试图用贪心算法(访问性试探法)设计代码解决,但还是没运行出来,今天情人节虽然不过,还是希望舒心些,我暂且放一放。
之后是我用很好理解的盲目枚举法(穷举法),发现与我的第二个代码输出一致,我算是放心了。
//盲目枚举法
#include<iostream>
#include<iomanip>
using namespace std;
void print();
int check(int[],int);
int sum=0,a[9];//a[i]=j表示第i个皇后放在第i行第j列
int main()
{
for(a[1]=1;a[1]<=8;a[1]++)
for(a[2]=1;a[2]<=8;a[2]++)
for(a[3]=1;a[3]<=8;a[3]++)
for(a[4]=1;a[4]<=8;a[4]++)
for(a[5]=1;a[5]<=8;a[5]++)
for(a[6]=1;a[6]<=8;a[6]++)
for(a[7]=1;a[7]<=8;a[7]++)
for(a[8]=1;a[8]<=8;a[8]++)
{ if(check(a,8)==0) continue;
else print();}
system("pause");
return 0;
}
void print()
{
sum++;//方案数+1
cout<<"case "<<sum<<":"<<endl;
cout<<" 1 2 3 4 5 6 7 8"<<endl;//输出一种方案
for(int i=1;i<=8;i++)
{
cout<<i;
for(int j=1;j<=8;j++)
if(a[i]==j)cout<<setw(3)<<"Q";
else cout<<setw(3)<<"*";
cout<<endl;
}
}
int check(int a[],int n)
{
for(int i=2;i<=n;i++)
for(int j=1;j<=i-1;j++)
if(a[i]==a[j]||abs(a[i]-a[j])==abs(i-j))return 0;
return 1;
}
遗留问题:N皇后问题的贪心算法???
N皇后问题的随机强制算法???
骑士问题题目中引导的强制算法==回溯算法???
N皇后问题我采用的回溯解法不完全等同于
该类问题的递归解法吧?还有多少解法呢???
wish continue!