博客
关于我
再识元胞自动机——生命游戏与初等元胞自动机
阅读量:666 次
发布时间:2019-03-15

本文共 8776 字,大约阅读时间需要 29 分钟。

再识元胞自动机——生命游戏与初等元胞自动机

※引入

    在一文中,对元胞自动机的定义和构成作了简单介绍,并且基于元胞自动机的理论框架,在给定元胞※群演化规则前提条件下,使用“C语言+EasyX图形库”编写代码对森林火灾模拟问题进行了求解。

    但是由于之前刚接触元胞自动机,对其中所包含的一些内容理解存在着偏差,所以对一些基本的概念性内容介绍有所偏差。所以,此次主要是对元胞自动机的理论内容进行补充,并且仍旧基于“C语言+EasyX图形库”的路线对生命游戏进行模拟。


【1】再识元胞自动机

元胞自动机的数学定义为:

    设d表示空间维数,k表示元胞的状态,并在一个有限集合S中取值,r表示元胞的邻居半径。Z是整数集,表示一维空间,t表示时间。元胞自动机的动态演化就是在时间上状态组合的变化,可记为:

F:S(t)Z->S(t+1)Z

    用文字可描述为:元胞自动机是定义在一个由离散、有限状态的元胞组成的元胞空间Z上,按照一定的局部规则F,在离散时间维度T上演化的动力学系统S。结合上述数学表示,即:该系统S在(t+1)时刻的状态与该系统在t时刻自身的状态、和t时刻所处在(局部)空间状态Z'(t)有关。

    这里提取四个关键词:时间(T)、元胞空间(Z)、局部规则(F)、局部空间-邻域(Z'),而上面说的系统,在t时刻的运行结果主要受到时间(T)、邻域(Z')、局部规则(F)的影响,有关时间和邻域的概念在此不再进行赘述,下面主要对规则进行阐述。

1.1元胞自动机构成之规则再解读

    规则,可以视为数学函数中的一种映射关系,对于基于元胞自动机的系统,可描述为:在一定限制条件下,从{时间T、邻域Z‘、局部规则F}到系统{S}的映射。

    元胞自动机根据规则进行局部元胞间的相互作用,而引起全局变化,规则支配着元胞自动机的整个动力学行为。即

    根据元胞当前状态及其邻居状态确定下一时刻该元胞状态的动力学函数可表示为:

在这里插入图片描述

从上式可看出,时间变量t,是对系统S状态的描述,但同时由于针对于系统所定义的元胞空间又是由邻域组成的,因此,时间变量t也是对邻域状态的的描述。上一时刻t的系统自身状态、t时刻的邻域状态都是下一时刻t+1时刻系统状态的做决定因素。对这一点的把握至关重要。之前我就是我因为这个条件没有想明白,导致在编写代码进行模拟时,模拟结果出现异常。

1.2元胞自动机特性

    接下来在对元胞自动机的一些特性,结合编程思路做简要介绍。

  1. 同质性:每个元胞的变化服从相同的规律。就是说:在编写代码时,可以使用循环结构,使用相同的映射规则F对每个元素做变换即可。
  2. 整齐性:元胞的分布方式相同,大小形状相同,空间分布规则整齐。此时,如果是规则的几何体系统,那么无论是一维、二维、还是三维,都可以使用循环嵌套的方式,方便地对整个系统结构进行单元划分,然后将每个单元视为“元胞”,针对每个单元进行变换。
  3. 离散性:
    空间:元胞按一定的规律分布在离散的元胞空间上
    ➢时间:演化按等间隔时间分步进行,时间只取等步 长的时刻点。
  4. 时空局域性:元胞在t+1时刻的状态,取决于其周围半径r的邻域中元胞t时刻的状态。这一点是之前强调过的,如果不注意,就会导致时域混乱,致使元胞自动机系统运行出异常的结果。
  5. 多维性:元胞自动机可以定义在一维、二维、三维的元胞空间上。结合之前的整齐性、同质性、离散性等特征,说明确实可以对系统进行单元划分,对每个单元进行变换,最终通过“局部变化引起全局变化”,这一点也正是在“元胞自动机规则介绍”部分所提到的一个重要思想。

【2】生命游戏

2.1生命游戏概述

生命游戏是Conway在20世纪60年代末设计的。

设计思想是:
①元胞分布在二维的正方形格网上;
②元胞具有0、1两个状态,0代表死,1代表生;
③元胞以相邻的上下左右及对角线上的八个元胞为邻居;
④一个元胞下一时刻的生死状态由其自身在该时刻的状态及其8个邻居的状态决定。
这里关于游戏介绍的信息表述很明确,不必再对其作过多解读。

2.2生命演化规则介绍

➢对于一个活的元胞,如果它的邻居中有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

2.3编程模拟思路

通过前面的信息解读,很显然,生命游戏的基本格局是一个二维的方形阵列,而构成方形阵列的基本单元又满足“同质性、整齐性、离散性”等特点,那么在此:我们就可以用一个二维网格阵列来模拟元胞所在的元胞空间,将整个二维方形空间其做格网划分后,用每一网格单元来存放单个元胞体,并将存活状态的元胞和死亡状态的元胞用不同的颜色在网格上标注出来。这是我们解体的基本思路。下面介绍步骤:


  1. 初始化格网矩阵,作为元胞空间来存放细胞群体;
  2. 初始化占据网格总数一定数目的元宝群体,用于表述状态为1的存活细胞,此时,细胞群体的状态用一个二维数组A保存;
  3. 根据数组A中保存的数据,绘制细胞群体到图形界面上,用绿色标识状态为1的存活细胞;用黑色表示状态为0的死亡细胞;
  4. 根据规则和数组A记录的细胞群状态,获取细胞群的下一时刻状态,并用另一个新的二维数组B保存细胞群体的状态;
  5. 根据新数组B中保存的数据,重新绘制元胞群体到屏幕上;
  6. 将t时刻数组B中的细胞状态记录复制给数组A,再根据规则和数组A中记录的t时刻细胞群的状态,获取细胞群的(t+1)时刻状态,并用二维数组B保存细胞群体状态;
  7. 据新数组B中保存的数据,重新绘制元胞群体到屏幕上。
  8. 循环执行【6-7】,中间可以使其休眠1.5s,以便于观察结果的变化过程。

上述步骤中的每特定操作都被封装成一个函数,见后面代码部分有详细注释。

    这里可以看到:我用了两个数组来记录细胞群在不同时刻的状态值,其中:数组A用于保存前一时刻的状态值,数组B用于保存下一时刻的状态值。原因还是之前所强调的,**一个元胞(t+1)时刻的生死状态由其自身在t时刻的状态及t时刻8个邻居的状态决定**,是为了避免在时间线上,对细胞状态的修正发生时域混乱。


2.4代码实现

还是同前面一样,先贴一张运行结果截图:

生命游戏

/*写在前面:具体编程实现过程中用到了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;}

【3】初等元胞自动机

    上面是二维元胞自动机在生命游戏方面应用的的一个实例及其实现,下面还有一点关于初等元胞自动机的内容想和大家分享。实质上,初等元胞自动机属于一维元胞自动机。

3.1初等元胞自动机简述

    初等元胞自动机是状态集S只有两个元素(k=2),邻居半径r=1的一维元胞自动机。就是说,在一维元胞群构成的线形空间上,每个元胞个体都有2种状态,其中:元胞 i 在(t+1)刻的状态由t时刻元胞(i-1)和元胞(i+1)来决定,那么3个相邻的元胞共存在8中排列组合方式,可用于系统演化结果的预测。

在这里插入图片描述

在这里插入图片描述

8种排列组合方式图解

进一步讲,上述8中排列组合方式可能得到元胞 i 在(t+1)时刻的256种状态。

那么,就可以在给定8个初始排列组合序列的条件下,使用这的256种规则中的一种作为一维元胞自动机系统的演化规则,时系统自动进行演化。这里256种结果状态集中被挑选出来,作为系统演化规则的结果可被称为“初等元胞自动机转换规则”。


【4】结语

    以上就是近期在家上网课所获的对于元胞自动机的认识和理解,至于编程实现的两个小案例,另一个案例可见:,算是课程实验内容之一。元胞自动机作为一种框架模型,被广泛应用在生物学领域(比如癌细胞繁衍的模拟)、生态学领域(比如态变化过程的模拟)、物理学领域、交通科学领域(比如:城市道路交通流和城市交通网络的模拟研究),以及土地利用变化研究、三维建模粒子系统构建等,应用十分广泛。


转载地址:http://mkqmz.baihongyu.com/

你可能感兴趣的文章
mysql 存在update不存在insert
查看>>
Mysql 学习总结(86)—— Mysql 的 JSON 数据类型正确使用姿势
查看>>
Mysql 学习总结(87)—— Mysql 执行计划(Explain)再总结
查看>>
Mysql 学习总结(88)—— Mysql 官方为什么不推荐用雪花 id 和 uuid 做 MySQL 主键
查看>>
Mysql 学习总结(89)—— Mysql 库表容量统计
查看>>
mysql 实现主从复制/主从同步
查看>>
mysql 审核_审核MySQL数据库上的登录
查看>>
mysql 导入 sql 文件时 ERROR 1046 (3D000) no database selected 错误的解决
查看>>
mysql 导入导出大文件
查看>>
MySQL 导出数据
查看>>
mysql 将null转代为0
查看>>
mysql 常用
查看>>
MySQL 常用列类型
查看>>
mysql 常用命令
查看>>
Mysql 常见ALTER TABLE操作
查看>>
MySQL 常见的 9 种优化方法
查看>>
MySQL 常见的开放性问题
查看>>
Mysql 常见错误
查看>>
mysql 常见问题
查看>>
MYSQL 幻读(Phantom Problem)不可重复读
查看>>