1. 程式人生 > >C語言編寫遺傳演算法解決TSP旅行商問題

C語言編寫遺傳演算法解決TSP旅行商問題

最近在上計算智慧的課,老師剛剛教了遺傳演算法,佈置了用遺傳演算法解決TSP的問題的作業,於是經過幾小時的奮戰,終於編寫完成。

首先先對TSP問題進行分析。TSP問題,也就是旅行商問題,題目的大題內容是 一位旅行商,要遍遊N座城市(城市數量記為NUM_CITY), 已知每兩座城市之間的位置,要求每座城市必須去且只去過一次,求遍遊該N座城市的最短路程。

利用遺傳演算法解決該問題,步驟如下:

1.初始化城市之間的距離

2.生成並初始化初始種群,種群內每個個體儲存著一條完整的路徑(每個城市標號出現且只出現一次)

3.計算目前種群每個個體的適應值(要求求最短路程,路徑一定是個正數,故而得到每個個體中儲存的路徑的總路程後,取倒數,得到的數越大,適應性越高,總路程越短)

4.找出目前最優個體,讓其直接複製至下一代(即不變異)

5.對其他個體根據發生交叉互換的概率Pc,得到參與交叉互換的個體集合

6.使參與交叉互換的個體隨機發生交叉互換(每個個體只參與一次)交叉互換的片段起始點和終點均隨機產生

7.對除最優個體外的每個個體,根據突變概率Pm發生突變,隨機產生兩個位置點,使這兩個位置點之間的片段進行置倒(即2345變成5432)

8.迴圈執行步驟34567,直到演變代數>GENERATIONS為止(暫定為5000代)

程式碼如下所示,(註釋部分用的printf用於測試查錯,可忽視)

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#define RAND(X) (rand()%(X))
#define NUM_CITY	(30)
#define GENERATIONS	(5000)
#define MAX_SIZE	(50)
#define LOWEST_ALIVE	(1.0/MAX_SIZE)
typedef struct _Group{
	int city[NUM_CITY];
	int adapt;
	float pAlive;
}Group;

int cityDistance[NUM_CITY][NUM_CITY];
Group g_Group[MAX_SIZE];
float	Pm = 0.1;
float	Pc = 0.8;
int bestOne;
void getFitness(Group &group) // 得到適應值
{
	int distance = 0;
	int temp;
	int x1, x2, y1, y2;
	int tdistance;
	for (int i = 1; i < NUM_CITY; i++)
	{
		distance += cityDistance[group.city[i - 1]][group.city[i]];
	}
	group.adapt = distance;
	group.pAlive = 1 / (float)distance;
}
void Swap(int &a, int &b) // 交換ab值
{
	int c;
	c = a;
	a = b;
	b = c;
}
void Init() // 初始化
{
	srand((unsigned int)time(0));
	for (int i = 0; i < NUM_CITY; i++) // 初始化城市距離
	{
		for (int j = i + 1; j < NUM_CITY; j++)
		{
			cityDistance[i][j] = RAND(100) + 1;
			cityDistance[j][i] = cityDistance[i][j];
		}
	}
	printf("城市距離如下:\n");
	for (int i = 0; i < NUM_CITY; i++)
	{
		for (int j = 0; j < NUM_CITY; j++)
		{
			if (j < i + 1)
				printf("    ");
			else
				printf("%3d ", cityDistance[i][j]);
		}
		printf("\n");
	}
	for (int i = 0; i < MAX_SIZE; i++) // 初始化樣本數列
	{
		for (int j = 0; j < NUM_CITY; j++)
		{
			g_Group[i].city[j] = j;
		}
	}
	int r;
	for (int i = 0; i < MAX_SIZE; i++) // 打亂順序
	{
		for (int j = 0; j < NUM_CITY; j++)
		{
			r = RAND(NUM_CITY);
			Swap(g_Group[i].city[j], g_Group[i].city[r]);
		}
	}
	printf("產生初始種群如下:\n");
	for (int i = 0; i < MAX_SIZE; i++)
	{
		printf("第%d個個體:\n", i + 1);
		for (int j = 0; j < NUM_CITY; j++)
		{
			printf("%3d ", g_Group[i].city[j]);
		}
		printf("\n");
	}

}
void GetAliveP() // 存活率
{
	float totalAlive = 0;
	//		選擇最優部分
	totalAlive = 0;
	for (int i = 0; i < MAX_SIZE; i++) // 計算個體適應值
	{
		getFitness(g_Group[i]);
		totalAlive += g_Group->pAlive;
	}
	for (int i = 0; i < MAX_SIZE; i++) // 矯正個體存活率 讓總和為1
	{
		g_Group[i].pAlive /= totalAlive;
	}
	bestOne = 0;
	for (int i = 0; i < MAX_SIZE; i++)
	{
		if (g_Group[i].pAlive > g_Group[bestOne].pAlive)
			bestOne = i;
	}
	printf("目前最佳個體為:%d, 其距離為%d,其軌跡如下:\n", bestOne+1, g_Group[bestOne].adapt);
	for (int i = 0; i < NUM_CITY; i++)
		printf("%d ", g_Group[bestOne].city[i]);
	printf("\n");
}
int isOnIt(int num, int Array[NUM_CITY], int ignorePos, int pos1, int pos2) // num是否在Array[]的pos1到pos2之間 其中跳過ignorePos(該數字的原位置)
{
	for (int i = pos1; i <= pos2; i++)
	{
		if (Array[i] == num)
			return i;
	}
	return -1;
}

void Swap(int sel1,int sel2,int pos1, int pos2) // 交叉互換
{
	int temp;
	int maxSize = pos2 - pos1 + 1;
	//printf("開始初步交叉互換\n");
	//printf("%d %d\n", pos1, pos2);
	for (int i = pos1; i <= pos2; i++)
	{
		temp = g_Group[sel1].city[i];
		g_Group[sel1].city[i] = g_Group[sel2].city[i];
		g_Group[sel2].city[i] = temp;
	}
	//for (int j = 0; j < NUM_CITY; j++)
	//	printf("%4d", g_Group[sel1].city[j]);
	//printf("\n");
	//for (int j = 0; j < NUM_CITY; j++)
	//	printf("%4d", g_Group[sel2].city[j]);
	//printf("\n");
	int pos;
	//printf("開始矯正重複值\n");
	int times = 0;
	for (int i = 0; i < NUM_CITY; i++) // 矯正重複值
	{
		times = 0;
		if (i >= pos1 && i <= pos2)
		{
			i = pos2;
			continue;
		}
		do 
		{
			times++;

			pos = isOnIt(g_Group[sel1].city[i], g_Group[sel1].city, i, pos1, pos2);
			if (pos != -1)
			{/*
				for (int j = 0; j < NUM_CITY; j++)
					printf("%4d", g_Group[sel1].city[j]);
				printf("\n");
				for (int j = 0; j < NUM_CITY; j++)
					printf("%4d", g_Group[sel2].city[j]);
				printf("\n");
				printf("%d %d %d %d %d\n",pos1,pos2,pos, g_Group[sel1].city[i], g_Group[sel2].city[pos]);*/
				g_Group[sel1].city[i] = g_Group[sel2].city[pos];
				//printf("pos:%d,pos1:%d,pos2:%d\n", pos, pos1, pos2);
			}
		} while (pos != -1);
		do
		{
			pos = isOnIt(g_Group[sel2].city[i], g_Group[sel2].city, i, pos1, pos2);
			if (pos != -1)
			{
				g_Group[sel2].city[i] = g_Group[sel1].city[pos];
				//printf("pos:%d,pos1:%d,pos2:%d\n", pos, pos1, pos2);
			}
		} while (pos != -1);
	}
//	printf("交叉互換過程完畢\n");
}
void Mutation(int sel, int pos1,int pos2)//個體突變
{
	int maxSize = pos2 - pos1 + 1;
	for (int i = 0; i < maxSize / 2; i++)
	{
		Swap(g_Group[sel].city[pos1+i], g_Group[sel].city[pos2-i]);
	}
}
void Genetic() // 產生下一代種群
{
	int maxNum = 0, minNum = 0;	
	for (int i = 0; i < MAX_SIZE; i++)
	{
		if (g_Group[i].pAlive > g_Group[maxNum].pAlive)
			maxNum = i;
		if (g_Group[i].pAlive < g_Group[maxNum].pAlive)
			minNum = i;
	}
	g_Group[minNum] = g_Group[maxNum]; // 使最大直接替換最小 
	//printf("開始交叉\n");
	// 交叉配對
	int selNum;
	int maxTimes = 0, nowTimes = 0;
	int canSelected[MAX_SIZE]; // 可以用於交叉的個體
	bool isCanSelected[MAX_SIZE];
	for (int i = 0; i < MAX_SIZE; i++)
	{
		if (i == maxNum) // 不讓最優秀的個體參與配對
			continue;
		else if ((RAND(100)) / 100.0 < Pc) // 根據概率判斷是否參與配對
		{
			canSelected[maxTimes++] = i;
		}
	}
	for (int i = 0; i < maxTimes; i++)
	{
		selNum = RAND(maxTimes);

		Swap(canSelected[i], canSelected[selNum]);
	}
	int pos1, pos2;
	for (int i = 0; i < maxTimes; i+=2)
	{
		selNum = i + 1;
		if (selNum >= maxTimes)
			break;
		pos1 = RAND(NUM_CITY); // 選定交叉位置
		pos2 = RAND(NUM_CITY - pos1) + pos1;
		if (pos1 == pos2)
		{
			if (pos1 > 0)
				pos1--;
			else
				pos2++;
		}/*
		printf("%d與%d開始交叉互換\n", canSelected[i], canSelected[selNum]);*/
		Swap(canSelected[i], canSelected[selNum], pos1, pos2);
		/*
				printf("第%d個體與第%d個體進行交叉配對,得到新的兩個個體如下:\n", canSelected[i] + 1, canSelected[selNum] + 1);
				printf("第%d個體:\n", canSelected[i] + 1);
				for (int j = 0; j < NUM_CITY; j++)
				printf("%4d", g_Group[canSelected[i]].city[j]);
				printf("\n第%d個體:\n", canSelected[selNum] + 1);
				for (int j = 0; j < NUM_CITY; j++)
				printf("%4d", g_Group[canSelected[selNum]].city[j]);
				printf("\nselNum:%d, maxTimes:%d\n",selNum,maxTimes);*/
	}
/*
	printf("開始突變\n");*/
	// 突變
	for (int i = 0; i < MAX_SIZE; i++)
	{
		if (i == maxNum || i == minNum)
		{
			continue;
		}
		if (RAND(100) / 100.0 < Pm) // 符合突變概率
		{
			pos1 = RAND(NUM_CITY); // 選擇位置
			pos2 = RAND(NUM_CITY - pos1) + pos1;
			if (pos1 == pos2)
			{
				if (pos1 > 0)
					pos1--;
				else
					pos2++;
			}
			/*printf("第%d個體突變前:\n", i + 1);
			for (int j = 0; j < NUM_CITY; j++)
				printf("%4d", g_Group[i].city[j]);
			printf("\n");*/
			Mutation(i, pos1, pos2);
		//	printf("第%d個體突變:\n", i + 1);/*
		//	for (int j = 0; j < NUM_CITY; j++)
		//		printf("%4d", g_Group[i].city[j]);
		//	printf("\n");
		}
	}
}
void Train()
{
	int nowGenerations = 0;
	float totalAlive = 0;
	do 
	{
		printf("第%d代種群\n", nowGenerations + 1);
		GetAliveP();// 計算存活率
		Genetic();// 根據遺傳規律得到下一代
		nowGenerations++;
	} while (nowGenerations < GENERATIONS);
	printf("經過%d次繁衍,得到的優秀個體為:\n", nowGenerations);
	printf("其距離為%d,其軌跡如下:\n", g_Group[bestOne].adapt);
	for (int i = 0; i < NUM_CITY; i++)
		printf("%d ", g_Group[bestOne].city[i]);
	printf("\n");
	printf("其他個體如下:\n");
	for (int i = 0; i < MAX_SIZE; i++)
	{
		printf("其距離為%d,其軌跡如下:\n", g_Group[i].adapt);
		for (int j = 0; j < NUM_CITY; j++)
		{
			printf("%d ", g_Group[i].city[j]);
		}
		printf("\n");
	}
}
int main()
{
	Init();
	Train();
	return 0;
}