一、实验目的
1.加深学生对分治法算法设计方法的基本思想、基本步骤、基本方法的理解与掌握;
2.提高学生利用课堂所学知识解决实际问题的能力;
3.提高学生综合应用所学知识解决实际问题的能力。
二、实验任务
1、 编写一个生命游戏:
规则如下:(或者网上找到更详细的规则)
一个人可以有8个邻居;
一个人若只有一个邻居,在下一代会孤独的死去;
若有2或3个邻居,在下一代依然活着;
若有4个或以上邻居,在下一代会因拥挤而死;
死去的人若有3个邻居,在下一代会复活;
所有的死去或复活都在下一代变化时同时发生。
2、 带锁的门:
在走廊上有n个带锁的门,从1到n依次编号。最初所有的门都是关着的。我们从门前经过n次,每次都从1号门开始。在第i次经过时(i = 1,2,…, n)我们改变i的整数倍号锁的状态;如果门是关的,就打开它;如果门是打开的,就关上它。在最后一次经过后,哪些门是打开的,哪些门是关上的?有多少打开的门?
3、三壶谜题:
有一个充满水的8品脱的水壶和两个空水壶(容积分别是5品脱和3品脱)。通过将水壶完全倒满水和将水壶的水完全倒空这两种方式,在其中的一个水壶中得到4品脱的水。
4、串匹配问题
给定一段文本,在该文本中查找并定位任意给定字符串。
要求:
(1)实现BF算法;(必做)
(2) 实现BF算法的改进算法:KMP算法和BM算法;(选做)
5、交替放置的碟子
我们有数量为2n的一排碟子,n黑n白交替放置:黑,白,黑,白…
现在要把黑碟子都放在右边,白碟子都放在左边,但只允许通过互换相邻碟子的位置来实现。为该谜题写个算法,并确定该算法需要执行的换位次数。
三、实验设备及编程开发工具
笔记本,Windows10操作系统,Dev-C++
四、实验过程设计(算法设计过程)
1、编写一个生命游戏
(1)问题分析:
解法生命游戏的规则可简化为以下,并使用CASE比对即可使用程式实作:
邻居个数为0、1、4、5、6、7、8时,则该细胞下次状态为死亡。
邻居个数为2时,则该细胞下次状态为复活。
邻居个数为3时,则该细胞下次状态为稳定。
代码是不断生成细胞存活的状态图,首先细胞默认都是死的,活细胞需要你自己一个一个生成,核心代码在neighbors(int,int)函数里面。
(2)算法实现
//需要的头文件以及宏定义,最多8*8个人,存活状态为1,死亡状态为0
#include<stdio.h>
#define MAXROW 8
#define MAXCOL 8
#define ALIVE 1
#define DEAD 0
//函数声明
int map[MAXROW][MAXCOL], newmap[MAXROW][MAXCOL];
void init();//初始化游戏
int neighbors(int, int);//根据邻居数量判定生命状态
void outputMap();//输出游戏当代的状态
void copyMap();//复制当代的状态
//主函数
int main()
{
int row, col;
char ans;
init();
while (1)
{
outputMap();
for (row = 0; row < MAXROW; row++)
{
for (col = 0; col < MAXCOL; col++)
{
switch (neighbors(row, col))
{
case 0:
case 1:
case 4:
case 5:
case 6:
case 7:
case 8:
newmap[row][col] = DEAD;
break;
case 2:
newmap[row][col] = map[row][col];
break;
case 3:
newmap[row][col] = ALIVE;
break;
}
}
}
copyMap();
printf("\nContinue next Generation ? ");
getchar();
ans = toupper(getchar());
if (ans != 'Y')
break;
}
return 0;
}
//生命游戏初始化,由用户输入存活人的坐标x,y,按-1,-1结束
void init()
{
int row, col;
for (row = 0; row < MAXROW; row++)
for (col = 0; col < MAXCOL; col++)
map[row][col] = DEAD;
puts("Game of life Program");
puts("Enter x, y where x, y is living cell");
printf("0 <= x <= %d, 0 <= y <= %d\n", MAXROW - 1, MAXCOL - 1);
puts("Terminate with x, y = -1, -1");
while (1)
{
scanf("%d%d", &row, &col);
if (0 <= row && row < MAXROW && 0 <= col && col < MAXCOL)
map[row][col] = ALIVE;
else if (row == - 1 || col == - 1)
break;
else
printf("(x, y) exceeds map ranage!");
}
}
//游戏规则制定,根据邻居数量判定人的生命状态,row,col为人的坐标
int neighbors(int row, int col)
{
int count = 0, c, r;
for (r = row - 1; r <= row + 1; r++)
for (c = col - 1; c <= col + 1; c++)
{
if (r < 0 || r >= MAXROW || c < 0 || c >= MAXCOL)
continue;
if (map[r][c] == ALIVE)
count++;
}
if (map[row][col] == ALIVE)
count--;
return count;
}
//打印游戏状态,死亡记为“_”,生存记为“*”
void outputMap()
{
int row, col;
printf("\n\n%20cGame of life cell status\n");
for (row = 0; row < MAXROW; row++)
{
printf("\n%20c", ' ');
for (col = 0; col < MAXCOL; col++)
if (map[row][col] == ALIVE)
putchar('*');
else
putchar('-');
}
}
void copyMap()
{
int row, col;
for (row = 0; row < MAXROW; row++)
for (col = 0; col < MAXCOL; col++)
map[row][col] = newmap[row][col];
}
2、带锁的门
(1)问题分析:
举例说明:假设n = 5,0表示门是关着的,1表示门是开着的,则模拟上述过程如下:
状态数组下标:1 2 3 4 5
状态数组的值:0 0 0 0 0 ―――>初始状态
状态数组的值:1 1 1 1 1 ―――>第一次
状态数组的值:1 0 1 0 1 ―――>第二次
状态数组的值:1 0 0 0 1 ―――>第三次
状态数组的值:1 0 0 1 1 ―――>第四次
状态数组的值:1 0 0 1 0 ―――>第五次
(2)算法实现
//需要的头文件以及宏定义,N为门数量的最大值
#include <stdio.h>
#define N 1000
//主函数
int main()
{
int L[N];
int i,j,k;
int n;
printf("请输入小于1000的整数代表门的总数:");
while(1)
{
scanf("%d",&n);
if(n<0||n>1000)//验证输入是否在有效范围内
printf("输入错误,请重新输入");
else break;
}
for(i=0;i<n;i++)
L[i]=0;
for(j=1;j<=n;j++)
for(k=1;k<=n;k++)
if(k%j==0)
L[k-1]=(L[k-1]+1)%2;
for(i=0;i<n;i++)
{
if(L[i]==1)
printf("第%d号门开着\n",i+1);
}
printf("\n");
return 0;
}
3、 三壶谜题
(1) 问题分析:
可以把每次三个水壶中水的量组成一个状态,比如初始状态为008,对应第一个水壶0品脱水,第二个水壶0品脱水,第三个水壶8品脱水。对题目的状态空间图进行广度优先遍历。当表示状态的数字中出现4时,即求出答案。
1、为了打印出倒水的过程,需要声明一个前置状态保存当前状态由哪个状态转换而来,然后就可以回溯到初始状态,打印出倒水过程。相当于树中的父结点。
2、可以声明一个map表,保存已有的状态,对已有的状态,就不再向下继续遍历,这样可以节省求解时间。
3、因为是广度优先遍历,所以第一次解得的答案所需的倒水的次数最少,解为最优解。
(2) 算法实现
#include <iostream>
#include <vector>
#include <map>
#define MaxFirst 3
#define MaxSecond 5
#define MaxThird 8
using namespace std;
class State
{
public:
int second;
int num[3];
State* preState;
static map<int,int> mapping;
public:
State(int first,int second,int third)
{
num[0]=first;
num[1]=second;
num[2]=third;
}
void init()
{
mapping[0]=MaxFirst;
mapping[1]=MaxSecond;
mapping[2]=MaxThird;
}
bool canPour(int from,int to)//判断是否可以从from水壶中倒水到to水壶中
{
if(num[from]==0)
{
return false;
}
if(num[to]==mapping[to])
{
return false;
}
else
{
return true;
}
}
void pour(int from,int to)//倒水过程
{
if(num[from]+num[to]>mapping[to])
{
num[from]=num[from]-(mapping[to]-num[to]);
num[to]=mapping[to];
}
else
{
num[to]=num[to]+num[from];
num[from]=0;
}
}
};
map<int,int> State::mapping;
int main()
{
map<int,int> states;
State *start=new State(0,0,8);
start->init();
State *state=start;
State *endState=new State(8,8,8);//只有获得解endState才会改变,赋值全为8为了方便判断是否获得最终解
vector<State> action;//保存所有状态对象
action.push_back(*start);//把初始状态先加入队列中
int n=0;
do{
for(int i=0;i<3;i++)//双层循环为从i水壶中倒水入j水壶中
{
for(int j=0;j<3;j++)
{
if(i!=j)
{
if(state->canPour(i,j))
{
state->pour(i,j);
if(states[state->num[0]*100+state->num[1]*10+state->num[2]]==0)//如果该状态不在hash表中,即为第一次出现该状态
{
states[state->num[0]*100+state->num[1]*10+state->num[2]]++;
(state->preState)=new State(action[n]);
action.push_back(*state);
if(state->num[0]==4||state->num[1]==4||state->num[2]==4)//获得解
{
endState=state;
i=4;
break;
}
}
}
}
*state=action[n];
}
}
n++;
}while(endState->num[0]==8&&endState->num[1]==8&& n<action.size());
cout<<endState->num[0]<<" "<<endState->num[1]<<" "<<endState->num[2]<<endl;
state=endState;
do
{
state=state->preState;
cout<<state->num[0]<<" "<<state->num[1]<<" "<<state->num[2]<<endl;
}while(state->num[2]!=8);
return 0;
}
4、 串匹配问题
(1) 问题分析
从主串s的pos位置出发,与子串t第一位进行匹配,若相等,接着匹配后一位字符,若不相等,则返回到s前一次匹配位置的后一位,接着与t的起始位进行匹配,直到与t全部匹配成功,则返回在s中开始完全匹配的下标。
简单说这个算法的思想就是匹配失败,就重新从上一次匹配位置的下一位开始匹配。
(2) 算法实现
#include <stdio.h> //头文件
#include <string.h> //字符串头文件
//BF算法 s是原字符串,t是匹配字符串
int BF(char s[],char t[],int pos)
{
int m,n;
int i=pos,j=0; //从0位置开始匹配
m=strlen(s);
n=strlen(t); //用m,n表示s,t长度
while (i<=m&&j<=n) //m,n是串长
{
if (s[i]==t[j])
{
i++;
j++; //逐个匹配,成功s++ t++
}
else
{
i=i-j+1; //不成功,s返回到此次循环匹配的初始位置
j=0; //不成功,t返回到0位置
}
}
if(j>n)
return (i-n); //返回匹配成功的原串中第一个字符的位置
else
return 0;
}
int main(){
char s[]="abcdeabcabcde"; //初始定义s串
int pos;
pos=BF(s,"abcd",0); //从0位置匹配abcd
printf("pos=%d\n",pos);
pos=BF(s,"abcde",pos); //从上次匹配成功的位置匹配abcde
printf("pos=%d\n",pos);
return 0;
}
5、 交替放置的碟子
(1) 问题分析
输入盘子总数2n,起始状态黑白盘子交替出现,为黑白黑白黑白…定义黑为2,白为1,则初始状态利用数组存储为[2,1,2,1,2,1…],对数组内的数据进行排序将所有的1放置所有的2之前,输出为[2,2,2,1,1,1…]即可实现。
(2) 算法实现
#include<stdio.h>
int main(){
int bubble_num,i ;
void sortDisplay(int bubble_set[],int length);
printf("请输入盘子总数:");
scanf("%d",&bubble_num);
printf("\n");
int bubble_set[bubble_num];
int length = sizeof(bubble_set)/sizeof(bubble_set[0]);
for(i=0;i<=(length-2)/2;i++){
bubble_set[2*i]=2;
bubble_set[2*i+1]=1;
}
printf("盘子的初始顺序为:\n");
sortDisplay(bubble_set,length);
printf("\n");
for(i=0;i<length-1;i++){
int j;
for(j=0;j<length-1-i;j++){
if(bubble_set[j+1]<bubble_set[j]){
int t=bubble_set[j];
bubble_set[j]=bubble_set[j+1];
bubble_set[j+1]=t;
}
}
}
printf("盘子排序后的顺序为:\n");
sortDisplay(bubble_set,length);
return 0;
}
void sortDisplay(int bubble_set[],int length){
int i;
for(i=0;i<length;i++){
// printf("%d ",bubble_set[i]);
if(bubble_set[i]==1){
printf("白 ");
}else{
printf("黑 ");;
}
}
printf("\n");
}
五、实验结果及算法复杂度分析
1、编写一个生命游戏
2、带锁的门
3、三壶谜题
4、串匹配
复杂度最坏情况O(M*N)(M,N分别为2个字符串的长度)。
5、交替放置的碟子