本文共 8776 字,大约阅读时间需要 29 分钟。
在一文中,对元胞自动机的定义和构成作了简单介绍,并且基于元胞自动机的理论框架,在给定元胞※群演化规则前提条件下,使用“C语言+EasyX图形库”编写代码对森林火灾模拟问题进行了求解。
但是由于之前刚接触元胞自动机,对其中所包含的一些内容理解存在着偏差,所以对一些基本的概念性内容介绍有所偏差。所以,此次主要是对元胞自动机的理论内容进行补充,并且仍旧基于“C语言+EasyX图形库”的路线对生命游戏进行模拟。元胞自动机的数学定义为:
设d表示空间维数,k表示元胞的状态,并在一个有限集合S中取值,r表示元胞的邻居半径。Z是整数集,表示一维空间,t表示时间。元胞自动机的动态演化就是在时间上状态组合的变化,可记为:用文字可描述为:元胞自动机是定义在一个由离散、有限状态的元胞组成的元胞空间Z上,按照一定的局部规则F,在离散时间维度T上演化的动力学系统S。结合上述数学表示,即:该系统S在(t+1)时刻的状态与该系统在t时刻自身的状态、和t时刻所处在(局部)空间状态Z'(t)有关。
这里提取四个关键词:时间(T)、元胞空间(Z)、局部规则(F)、局部空间-邻域(Z'),而上面说的系统,在t时刻的运行结果主要受到时间(T)、邻域(Z')、局部规则(F)的影响,有关时间和邻域的概念在此不再进行赘述,下面主要对规则进行阐述。
规则,可以视为数学函数中的一种映射关系,对于基于元胞自动机的系统,可描述为:在一定限制条件下,从{时间T、邻域Z‘、局部规则F}到系统{S}的映射。
元胞自动机根据规则进行局部元胞间的相互作用,而引起全局变化,规则支配着元胞自动机的整个动力学行为。即
根据元胞当前状态及其邻居状态确定下一时刻该元胞状态的动力学函数可表示为:接下来在对元胞自动机的一些特性,结合编程思路做简要介绍。
生命游戏是Conway在20世纪60年代末设计的。
设计思想是: ①元胞分布在二维的正方形格网上; ②元胞具有0、1两个状态,0代表死,1代表生; ③元胞以相邻的上下左右及对角线上的八个元胞为邻居; ④一个元胞下一时刻的生死状态由其自身在该时刻的状态及其8个邻居的状态决定。 这里关于游戏介绍的信息表述很明确,不必再对其作过多解读。➢对于一个活的元胞,如果它的邻居中有2个或3个元胞是活的,那么该元胞继续生存
➢对于一个活的元胞,如果它的邻居中有4个或4个以上的元胞是活的,该元胞死亡(过度拥挤、资源匮乏) ➢对于一个活的元胞,如果它的邻居中有1个或没有活的元胞,该元胞死亡(人烟稀少、过度孤单) ➢对于一个死的元胞,如果它的邻居中有3个活的元胞,那么该元胞将成为活的元胞(繁殖) 对上述信息进行简单的整理,可设:t时刻元胞s的状态为state(要么取0-死亡,要么取1-存活),t时刻八邻域内(除了该元胞自己)存在的活体元胞数目为sum,那么该元胞在(t+1)时刻的状态new_state可用下面的伪代码描述为:if state==1 //t时刻-存活状态 { if sum==2||sum==3 //保持原状态-1 new_state=1 else //过度拥挤或者孤独致死-0 new_state=0 }//end if-1 else if state=0 //t时刻-死亡状态 { if sum==3 //繁殖-1 new_state=1 else //不满足繁殖条件-0 new_state=0 }//end if-0
通过前面的信息解读,很显然,生命游戏的基本格局是一个二维的方形阵列,而构成方形阵列的基本单元又满足“同质性、整齐性、离散性”等特点,那么在此:我们就可以用一个二维网格阵列来模拟元胞所在的元胞空间,将整个二维方形空间其做格网划分后,用每一网格单元来存放单个元胞体,并将存活状态的元胞和死亡状态的元胞用不同的颜色在网格上标注出来。这是我们解体的基本思路。下面介绍步骤:
上述步骤中的每特定操作都被封装成一个函数,见后面代码部分有详细注释。
这里可以看到:我用了两个数组来记录细胞群在不同时刻的状态值,其中:数组A用于保存前一时刻的状态值,数组B用于保存下一时刻的状态值。原因还是之前所强调的,**一个元胞(t+1)时刻的生死状态由其自身在t时刻的状态及t时刻8个邻居的状态决定**,是为了避免在时间线上,对细胞状态的修正发生时域混乱。还是同前面一样,先贴一张运行结果截图:
/*写在前面:具体编程实现过程中用到了EasyX图形库,该库文件可以在网上自行下载并配置EasyX 是针对 C++ 的一个轻量级图形库,可以帮助 C 语言初学者快速上手图形和游戏编程。提供了绘图函数、鼠标与键盘事件响应函数、以及资源文件读取函数等。同时针对绘图部分也提供了批量绘图函数,解决了窗体刷新过程中出现的窗口闪烁的问题,这在下面的代码部分都有体现*/#include#include #include #include #include #define N 100//变量关系:ROW=COL=cellNum=WiDTH/cellSize=HEIGHT/cellSize#define HEIGHT 600//窗体高度#define WIDTH 600//窗体宽度#define cellSize 8//单元边长#define cellNum 75//单元行/列数目#define ROW 75 //元胞矩阵行数#define COL 75 //元胞矩阵列数#define initialVal 1500//初始元胞个数 //定义结构体:元胞位置struct Point{ int x; int y;}mypoint;void InitGrid();//初始化网格void getInitialcellPos(int life_Matrix[cellNum][cellNum]);//获取元胞初始位置void drawCell(int life_Matrix[cellNum][cellNum]);//绘制元胞void getNext_CellQuene(int life_Matrix[cellNum][cellNum]);//获取下一时刻的元胞位置void copyTOlife_Matrix(int life_next_Matrix[cellNum][cellNum], int life_Matrix[cellNum][cellNum]);//把life_next_Matrix复制给矩阵life_Matrixint getCell_SUM(int row, int col);//计算8邻域元胞状态为1的总数void getRandomCellPos(int cell_Pos[cellNum][cellNum]);//生成随机元胞位置void getRandXY(int &x, int &y);//产生随机坐标位置/*元胞自动机-生命游戏: 根据任意一个元胞的八邻域的其他元胞状态来确定该元胞在下一刻的状态: 演化规则:设邻域状态为1的元胞总数为count, ①对于一个活的元胞,如果它的邻居中有2个或3个元胞是活的,那么该元胞继续生存 ②对于一个活的元胞,如果它的邻居中有4个或4个以上的元胞是活的,该元胞死亡(过度拥挤、资源匮乏) ③对于一个活的元胞,如果它的邻居中有1个或没有活的元胞,该元胞死亡(人烟稀少、过度孤单) ④对于一个死的元胞,如果它的邻居中有3个活的元胞,那么该元胞将成为活的元胞(繁殖)*/int life_Matrix[cellNum][cellNum];//t时刻生命矩阵:60*60【0-死亡;1-存活】int life_next_Matrix[cellNum][cellNum];//t+1时刻生命矩阵:60*60【0-死亡;1-存活】int flag = 0;//标志位int main(int args,char* argv){ initgraph(WIDTH,HEIGHT, SHOWCONSOLE);//初始化设备 HWND hwnd = GetHWnd();//获取当前窗体句柄 SetWindowText(hwnd,_T("生命游戏")); SetWindowPos(hwnd, NULL, 600, 200, HEIGHT, WIDTH, 0); //1、初始化网格 InitGrid(); srand((int)time(NULL));//随机数生成种子:确保每次生成的随机数都不同 //2、获取元胞初始位置 getInitialcellPos(life_Matrix); //3、绘制元胞 drawCell(life_Matrix); //批量循环绘图 BeginBatchDraw();//开启批量绘图模式 while (true)//迭代次数:置为死循环 { //4、获取下一时刻元胞群的状态 getNext_CellQuene(life_Matrix); //5、重新初始化格网 InitGrid(); //6、重新绘制元胞 drawCell(life_next_Matrix); FlushBatchDraw(); // 显示绘制 //7、停顿1.5s Sleep(100); //8、用于实现刷新设备 cleardevice(); } EndBatchDraw();//结束批量绘制 system("pause"); closegraph();//关闭设备 return 0;}/*1、初始化网格*/void InitGrid(){ setlinecolor(RGB(0,255,0));//蓝色线条 for (int i = 0; i < cellNum;i++) { line(0, i*cellSize, 600, i*cellSize); line(i*cellSize, 0, i*cellSize, 600); }}/*2、//获取初始cell_Num元胞的初始位置*/void getInitialcellPos(int life_Matrix[cellNum][cellNum]){ /* //方法1:遍历矩阵,逐个产生随机数,以一定概率产生初始元胞 float randVal=0; for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { randVal = rand() % (N + 1) / (float)(N + 1); //随机数大于0.7时,将当前位置的元胞状态置为1 if (randVal>=0.90) { //初始化生命矩阵 life_Matrix[i][j] = 1;//[i,j]表示ROW*COL规格的网格处对应的一个元胞位置 } else { life_Matrix[i][j] = 0; } } }*/ //方法二:通过产生随机坐标,将其状态置为1 int x, y; for (int i = 0; i < initialVal; i++) { getRandXY(x, y);//获取随机位置 while (life_Matrix[x][y] == 1)//如果新产生的元胞位置已经被标记,就重新生成 { getRandXY(x, y); } life_Matrix[x][y] = 1;//将产生的随机位置上的元胞进行标记 }}/*3、绘制元胞*/void drawCell(int life_Matrix[cellNum][cellNum]){ int count = 0; int i, j; for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { if (life_Matrix[i][j] == 1) { //设置填充颜色为绿色 setfillcolor(RGB(0, 0, 255));//蓝色填充色 //绘制被标记为1的矩形元胞 fillrectangle(i*cellSize, j*cellSize, i*cellSize + cellSize, j*cellSize + cellSize); count++;//记录新一轮的元胞值 } else if (life_Matrix[i][j]==0) { //设置填充色为白色 setfillcolor(RGB(255,255,255)); //绘制被标记为1的矩形元胞 fillrectangle(i*cellSize, j*cellSize, i*cellSize + cellSize, j*cellSize + cellSize); count++;//记录新一轮的元胞值 } } } printf("%d\n", count);}/*4、获取下一时刻的元胞位置*/void getNext_CellQuene(int life_Matrix[cellNum][cellNum]){ if (flag==1)//标志位为1时执行 { //获取上一时刻的生命矩阵值:把life_next_Matrix复制给矩阵life_Matrix copyTOlife_Matrix(life_next_Matrix, life_Matrix); } //从第二次开始重置life_next_Matrix矩阵 int i, j, sum = 0,temp; //遍历60*60大小的元胞体矩阵所包含的3600个元胞体 for (i = 1; i < (ROW)-1; i++) { for (j = 1; j < (COL)-1; j++) { temp = life_Matrix[i][j];//记录当前元胞状态 sum = getCell_SUM(i, j);//获取当前元胞胞体8邻域元胞总数目 //判断当前轮胞体状态 if (temp==1)//上一时刻为活体 { if (sum==2||sum==3) { life_next_Matrix[i][j] = 1; } else life_next_Matrix[i][j] = 0; }//end if-1 if (temp==0)//上一时刻死亡 { if (sum==3) { life_next_Matrix[i][j] = 1; } else life_next_Matrix[i][j] = 0; }//end if-0 }//end for内部循环 }//end for外部循环 flag = 1;//置标志位为1}//把life_next_Matrix复制给矩阵life_Matrixvoid copyTOlife_Matrix(int life_next_Matrix[cellNum][cellNum], int life_Matrix[cellNum][cellNum]){ int i, j; for (i = 0; i < cellNum;i++) { for (j = 0; j < cellNum;j++) { life_Matrix[i][j] = life_next_Matrix[i][j]; } }}//计算8邻域元胞状态为1的总数int getCell_SUM(int row, int col){ int sum = 0; //统计周围元胞数目 for (int i = row - 1; i <= row + 1; i++) { for (int j = col - 1; j <= col + 1; j++) { if (i==row&&j==col) { continue;//如果为中心单元,就直接进行下一次循环 } if (life_Matrix[i][j] == 1) { sum++; } } } printf("%d\t", sum); return sum;}//产生随机坐标位置void getRandXY(int &x,int &y){ x = rand() % cellNum; y = rand() % cellNum;}
上面是二维元胞自动机在生命游戏方面应用的的一个实例及其实现,下面还有一点关于初等元胞自动机的内容想和大家分享。实质上,初等元胞自动机属于一维元胞自动机。
初等元胞自动机是状态集S只有两个元素(k=2),邻居半径r=1的一维元胞自动机。就是说,在一维元胞群构成的线形空间上,每个元胞个体都有2种状态,其中:元胞 i 在(t+1)刻的状态由t时刻元胞(i-1)和元胞(i+1)来决定,那么3个相邻的元胞共存在8中排列组合方式,可用于系统演化结果的预测。
进一步讲,上述8中排列组合方式可能得到元胞 i 在(t+1)时刻的256种状态。
那么,就可以在给定8个初始排列组合序列的条件下,使用这的256种规则中的一种作为一维元胞自动机系统的演化规则,时系统自动进行演化。这里256种结果状态集中被挑选出来,作为系统演化规则的结果可被称为“初等元胞自动机转换规则”。以上就是近期在家上网课所获的对于元胞自动机的认识和理解,至于编程实现的两个小案例,另一个案例可见:,算是课程实验内容之一。元胞自动机作为一种框架模型,被广泛应用在生物学领域(比如癌细胞繁衍的模拟)、生态学领域(比如态变化过程的模拟)、物理学领域、交通科学领域(比如:城市道路交通流和城市交通网络的模拟研究),以及土地利用变化研究、三维建模粒子系统构建等,应用十分广泛。
转载地址:http://mkqmz.baihongyu.com/