遗传算法求解n皇后问题

目录

1.什么是遗传算法

2.成员变量的定义

3.构造函数

4.计算个体最大的不冲突个数

5.适应度函数

6.返回种群中不存在冲突的个体位置(适应度)

7.计算种群中个体的适应度

8.生成种群

9.选择

10.杂交

11.变异

12.求解


1.什么是遗传算法

我们进行这样的思考,假设有一个长颈鹿的种群,但是这群长颈鹿的脖子不算很长只能吃到底层的树叶,渐渐的,底层的树叶越来越少,这群短脖子的长颈鹿生存压力愈发增大。在长颈鹿种群繁衍的过程中,某些后代长颈鹿发生变异出现了长脖子的性状,它们可以吃到更丰裕的上层树叶,这样能吃到上层树叶的长颈鹿生存压力小,只能吃到下层树叶的长颈鹿生存压力大,随着时间的推移,短脖子的长颈鹿越来越少,长脖子的长颈鹿数量变得更多。所以,长脖子基因慢慢在种群中占据更大比例,这一过程就是自然选择。

遗传算法的主要思想就是通过模拟自然界生物的繁殖和自然选择来达到不断优化,从而得到较优解的过程。

下面我们就以求解n皇后问题来了解遗传算法的过程吧。


2.成员变量的定义

首先针对n皇后问题,我们设置如下变量

	int N;                          // 皇后的数目
	int numOfIndividuals;           //  种群个体数目
	int maxNumberOfNotConflicts;   // 每一个个体最大的不冲突个数
	vector<double> fitness;           // 每一个个体的适应度值列表
	vector<int> targetChessboard;  // 目标棋盘
	vector<vector<int>> population;

最后的population就表示种群了,它其中的个体就表示一个棋盘。


3.构造函数

	geneticAlgorithm(int N) {
		this -> N = N;
		maxNumberOfNotConflicts = calculateMaxNotConflicts(N);
		numOfIndividuals = 4 * N;
	}

在构造函数中,numOfIndividuals赋予的是一个经验值,这样设置后表示一个种群population中包含了4*N个个体。

maxNumberOfNotConflicts表示种群中每个个体最大的不冲突个数


4.计算个体最大的不冲突个数

	// 计算个体最大的不冲突个数
	int calculateMaxNotConflicts(int N) {
		int num = 0;
		/*for (int i = N - 1; i > 0; i--) {
			num += i;
		}*/
		num=N*(N-1)/2;
		return num;
	}

怎么理解个体的最大的不冲突个数呢?

因为我们要求解的是n皇后问题,所以是这样定义的。

已经解决的n皇后的放置不存在冲突的皇后,所以这时候就是最大的不冲突情况。

而个数指的是对于第一个皇后,剩余行不冲突的皇后有n-1个;

对于第二个皇后,剩余行不冲突的皇后有n-2个;

以此类推对于倒数第二个皇后,剩余行不冲突的皇后有1个

所以总的个数为1+2+……+n-1

举例来说,对于4皇后,一个棋盘的放置为:

那么对于第一行的皇后,有二三四行的皇后,所以冲突数为3

对于第二行的皇后,有三四行的皇后,由于在上一步第一行的时候已经计算了第二行的皇后,所以针对第二行,有2个冲突数。

最后第三行皇后有1个冲突数。

总共为1+2+3=6个最大冲突数

最大冲突数就是n皇后放置的情况,所以可以使用最大冲突数来判断某棋盘是否已经为解决的n皇后。

最后为了提升效率,可以用等差数列求和公式。


5.适应度函数

	int getNumOfNotConflicts(vector<int> *chessboard) {
		int numOfNotConflicts = 0;
		int width = this->N;
		for (int i = 0; i < width-1; i++) {
			for (int j = i + 1; j < width; j++) {
				// 当存在皇后不位于对角线的时候 且不在同一列时
				//cout << i << " " << j << " " << N << " " << chessboard->size() << endl;
				if (abs(j - i) != abs((*chessboard)[i] - (*chessboard)[j]) &&  (*chessboard)[i] != (*chessboard)[j]) {
					numOfNotConflicts++;
				}
			}
		}
		return numOfNotConflicts;
	}

在之前爬山法和模拟退火法中,我们是求冲突的皇后数量,最后优化直到冲突数为0.

而这里我们是计算不冲突的皇后数量。


6.返回种群中不存在冲突的个体位置(适应度)

	int positionOfNotConflict(vector<vector<int>>& population) {
		for (int i = 0; i < numOfIndividuals; i++) {
			if (getNumOfNotConflicts(&(population[i])) == this->maxNumberOfNotConflicts) {
				return i;
			}
		}
		return -1;
	}

最后我们优化的终止条件就可以调用该函数,如果不为-1,那么就说明找到了最优解。


7.计算种群中个体的适应度

	void calculateFitness(vector<vector<int>>& population, vector<double>& fitness) {
		fitness.clear();
		int sumOfNotConflicts = 0;
		vector<int> notConflict;
		for (int i = 0; i < numOfIndividuals; i++) {
			int n = getNumOfNotConflicts(&(population[i]));
			sumOfNotConflicts += n;
		//	cout << "CalculateFitness: " << i << " " << n << endl;
			notConflict.push_back(n);
		}
		for (int i = 0; i < numOfIndividuals; i++) {
			fitness.push_back(1.0 * notConflict[i]/sumOfNotConflicts);
			//cout << " " << 1.0 * notConflict[i] / sumOfNotConflicts;
		}
		
	}

getNumOfNotConflicts函数可以计算个体的适应度,然后fitness存放每个个体适应度的比列。


8.生成种群

	vector<vector<int>>* createPopulation(vector<vector<int>>& population, int numberOfIndividual) {
		population.clear();
		vector<int> chessboard ;
		for (int i = 0; i < numberOfIndividual; i++) {
			for (int j = 0; j < N; j++) {
				chessboard.push_back(j);
			}
			for (int row1 = 0; row1 < N; row1++) {
				int row2 = random(N);
				// 随机交换行,打散棋盘,但保证皇后都在不同列
				swap(chessboard[row1], chessboard[row2]);
			}
			
			population.push_back(chessboard);
			chessboard.clear();
		}
		return &population;
	}

初始化棋盘的过程为添加对角线,之后随机交换行。最后将棋盘作为个体添加到种群中。


9.选择

	vector<vector<int>>* select(const vector<vector<int>>& population,const vector<double>& fitness, vector<vector<int>>& parents) {
		float m = 0;
		parents.clear();
		float p1 = (rand() % 100) * 1.0 / 100;
		float p2 = (rand() % 100 )* 1.0 / 100;
		for (int i = 0; i < numOfIndividuals; i++) {
			m += fitness[i];
			if (p1 <= m) {
				// 加入第一个个体
				// 产生的随机数p1 在 m ~m+fitness[i] 间则认为选择了i
			//	cout << "select1 " << p1 << " " << m << " " << "i " << i << endl;
				parents.push_back(population[i]);
				break;
			}
		}
		m = 0;
		for (int i = 0; i < numOfIndividuals; i++) {
			m += fitness[i];
			// 加入第二个个体
			if (p2 <= m) {
			//	cout << "select2 " << p2 << " " << m << " " << "i " << i << endl;
				parents.push_back(population[i]);
				break;
			}
		}
		return &parents;
	}

这里采用的是轮盘赌算法。

简单来说就是一种抽奖。

本质上我们实现的办法是将轮盘一维化为数轴的形式。

由于之前我们对于适应度列表fitness是按照比例存储的,所以我们每次加上一个个体的适应度值,就会得到一个区间,最终这个区间变为0-1。接下来用一个随机数随机0-1的任意小数,在每次加上个体的适应度值时,就进行一次判断,用来决定是否作为父亲。另外用另一个随机数,决定是否最为母亲。


10.杂交

	vector<int>* crossover(const vector<int>& chessboard1, const vector<int>& chessboard2, vector<int> &son) {
		//srand((unsigned)time(NULL));
		
		int pos1 = 0, pos2 = 0;
		son.clear();
		while (pos1 >= pos2) {
			pos1 = rand() % N;
			pos2 = rand() % N;
		}
	
		for (int i = 0; i < this->N; i++) {
			if (i < pos1 || i > pos2) {
				son.push_back(chessboard1[i]);
			}
			else {
				son.push_back(chessboard2[i]);
			}
		}
		//cout << "crossover: " << pos1 << " " << pos2 << endl;
		
		return &son;
	}

用两个随机数,表示一个区间。

然后在下面的循环中(0-N)来决定孩子个体每一行皇后放置的位置(模拟孩子遗传父亲或者母亲的DNA)。

如果i在区间外侧,那么选择父亲的DNA;如果在内侧,选择母亲的DNA。

最后返回的son,也是一个棋盘。


11.变异

	vector<int>* mutate(vector<int>& chessboard) {
		//srand((unsigned)time(NULL));
		int row1 = rand() % N;
		int col1 = rand() % N;
		chessboard[row1] = col1;
		//cout << "mutate :" << row1 << " " << col1 << endl;
		return &chessboard;
	}

这里变异的本质是随机改变某一行皇后放置的位置。


12.求解

求解的过程为:

初始化种群

计算个体适应度

选择父母

杂交生成孩子(也有一定概率不杂交,直接遗传父亲或者母亲,因为可能父母中某一方基因已经足够好了)

一定概率变异

判断孩子适应度是否由于父母,如果由于父母将孩子个体放在新的种群中;否则,增加突变率,继续循环杂交

直到新种群个体数量等于原来种群个体数量,停止杂交循环

新种群替换就种群

直到新得到的种群中存在个体有最大冲突值个数,则终止种群的繁衍。

代码:

	vector<int>* solve(vector<int>& chessboard) {
		/*
		 设置种群参数
		 pMutate:  变异发生的概率
		 pCrossover: 交叉发生的概率
		*/
		double pMutate = 0.2;
		double pCrossover = 0.9;
		
		vector<int> individual;
		population = *createPopulation(population,numOfIndividuals);  // 初始化种群
		int numOfGeneration = 0;   // 进化的代数
		srand((unsigned)time(NULL));
		// 开始进化
		do {
			// 计算种群中每一个个体的适应度值,结果保存在fitness
			calculateFitness(population, fitness); 
			vector<vector<int>> newPopulation;
			do {
				// 随机挑选两个个体作为父母
				vector<vector<int>> parents;
				parents = *select(population, fitness, parents);
				//只产生更优的儿子
				while (1) {
					// 防止不交叉,son为空
					vector<int> son = parents[0];
					// 随机播种,产生随机数
					// 一定概率发生交叉,产生儿子,儿子适应度更高
					float pc = rand() % 100 * 1.0 / 100;
					if (pc < pCrossover) {
						son = *(crossover((parents).at(0), (parents).at(1), son));
					}
					// 一定概率发生变异,改变儿子
					float pm = rand() % 100 * 1.0 / 100;
					if (pm < pMutate) {
						son = *mutate(son);
					}
					// 若儿子优于或等于父母,则添加到种群中
					if (getNumOfNotConflicts(&son) >= getNumOfNotConflicts(&parents[0]) && getNumOfNotConflicts(&son) >= getNumOfNotConflicts(&parents[1])) {
						//cout << getNumOfNotConflicts(&son) << " " << getNumOfNotConflicts(&parents[0]) << " " << getNumOfNotConflicts(&parents[0]) << endl;
						// 将儿子加入新的种群中
						/*if (getNumOfNotConflicts(&son) == 27) {
							printChessboard(son);
							system("pause");
						}*/
						newPopulation.push_back(son);
						son.clear();
						parents.clear();
						break;
					}
					else {
						if(pMutate <= 0.98)
							pMutate += 0.02;
					}
				}
			} while(newPopulation.size()!= numOfIndividuals); // 直到儿子种群数目重新等于种群规模

			// 新种群替代旧种群
			population.clear();
			for (int i = 0; i < numOfIndividuals; i++) {
				population.push_back(newPopulation[i]);
			}
			numOfGeneration++;
		} while (positionOfNotConflict(population) == -1);
		// 得到种群中满足适应度的个体
		chessboard = population[positionOfNotConflict(population)];	
		cout << "总共进化的代数为: " << numOfGeneration << endl;
		return &chessboard;
	}

  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值