1. 程式人生 > >遺傳演算法(Genetic Algorithm )+C++實現解決TSP問題

遺傳演算法(Genetic Algorithm )+C++實現解決TSP問題

概念

生物進化中的概念 遺傳演算法中的作用
環境 適應函式
適應性 適應函式值
適者生存 適應值大的解被保留的概率大
個體 問題的一個解
染色體 解的編碼
基因 編碼的元素
群體 被選定的一組解
種群 按適應函式選擇的一組解(編碼表示)
交配 以一定的方式由雙親產生後代的過程
變異 編碼的某些分量發生變化的過程

三個生成過程

  • select 自然選擇
  • crossover 交叉(交配)
  • mutation 變異

把每個資料想象成染色體,然後從染色體到人的對映就是object function,也就是這裡的適應函式。

當然可以對映到更深的程度,比如考慮人的身高之類的(可以被量化的東西)

  • 染色體 這樣的比喻,會很容易理解crossover這個步驟。
  • 變異這個也很好理解,避免進入到區域性極小值,被控制住了。
  • 自然選擇,這個根據evolutionary theory,也很容易理解

簡單遺傳演算法框架

在這裡插入圖片描述

一般終止條件是:過了很久最優的適應值都不發生變化

整數編碼問題

因為如果是二進位制編碼的話,會簡單很多,這裡就不講了。
難點其實還是在整數編碼上。

整數編碼(簡單的例項):
(有些問題不是排序的問題,就可以類似於之前的二進位制來實現)
對於父母分別是 0 到 9整數的排序【加上長度均為10】:
要求子代也必須是這樣的排序。

  • 自然選擇:不會產生子代,只是篩選子代,所以不受這個問題影響
  • 交叉(crossover):會產生子代。這裡只考慮排序時候的情況
    • 基於次序的交配法:
      在父代1找到幾個位置,之後,找到這些數字在父代2的位置。並刪除(用空白填充)。之後這些空白按照父代1的中這些數字的順序排好。(非常簡單的方法
    • 基於位置的交配法: 在父代1找到幾個位置,父代2的數值直接替代上去,只會,衝突的位置(不在之前選的位置上衝突的位置),按順序從父代1替代。
    • 部分對映的交配法: 任意選兩個位置,在父代1,2直接這兩個位置之間的序列構建序列對。然後,按照這樣的序列對的對映方式,在父代1或者父代2上做對映交換。就可以得到子代1或子代2。
  • 變異(mutation):會產生子代。
    • 基於位置的變異: 隨機產生兩個變異位,然後將第二個變異位上的基因移動到第一個變異位之前。
    • 基於次序的變異: 隨機的產生兩個變異位,然後交換這兩個變異位上的基因。
    • 打亂變異: 隨機尋去染色體上的一段,然後打亂在該段內的基因次序。逆序交換方式是打亂變異的一個特例。

TSP問題

TSP,是貨郎擔問題,也就是中國郵遞員問題(少數世界級問問題,用中國人命名的問題hhh)。
就是n個點直接連通需要不同的代價,如果想要找到不重複的經歷完所有點,然後在回到初始點的用的代價最小。

用遺傳演算法解決TSP問題

#include <iostream>
#include <cmath>
#include <ctime>
#include <fstream>
#include <cstdlib>
#include <vector>
#include <algorithm>
using namespace std;

// 隨機整數[b, e)
#define RAND(b, e) (rand() % ((e)-(b)) + (b))
// 隨機浮點數 0,1
#define RANDFLOAT() ((double)rand() / double(RAND_MAX))

// 種群數量
int LEN = 10;
int tempLEN = 3 * LEN;
// 交配的概率,編譯的概率
double pc = 0.8, pm = 0.8;
// 種群的所有路徑
int **Paths;
int **tempPaths;
int temp_index = 0;
double **Mat;
double *Value, *tempValue;
int globalValue_index, stay_in_global_times = 0;

double globalValue;
int *globalPath;

// TSP節點數量
int N = 0;

// 適應值函式
double CalValue(int *p) { // read only
	double t = 0;
	for (int i = 1; i < N; ++i) {
		t += Mat[p[i - 1]][p[i]];
	}
	t += Mat[p[N - 1]][0];
	return t;
}

void initialPath(int *Path, int j);
void initialPaths();
void find_min(int first = 1);

// 自然選擇
void select();
// 交配
void crossover(int *p1, int* p2);
// 變異
void mutation(int *Path);
//
void preserve(int *p1, int *p2, int v1, int v2);

int main() {
	srand((unsigned)time(NULL));
	ifstream cin("data.txt");

	cin >> N;
	Mat = new double *[N];
	for (int i = 0; i < N; ++i) {
		Mat[i] = new double[N];
	}

	// read data from data.txt
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < N; ++j) {
			cin >> Mat[i][j];
		}
	}
	// new Path...
	Paths = new int*[LEN];
	for (int i = 0; i < LEN; ++i) {
		Paths[i] = new int[N];
	}
	tempPaths = new int*[tempLEN];
	for (int i = 0; i < tempLEN; ++i) {
		tempPaths[i] = new int[N];
	}
	Value = new double[LEN];
	tempValue = new double[tempLEN];

	globalPath = new int[N];
	// end new Path...
	initialPaths(); // initialize paths

	while (true) {
		temp_index = 0;
		for (int i = 0; i < LEN; i += 2) {
			if (temp_index >= tempLEN) break;
			preserve(Paths[i], Paths[i + 1], Value[i], Value[i + 1]);
			if (temp_index >= tempLEN) break;
			if (RANDFLOAT() < pc) crossover(Paths[i], Paths[i + 1]);
		}

		for (int i = 0; i < LEN; ++i) {
			if (temp_index >= tempLEN) break;
			if (RANDFLOAT() < pm) mutation(Paths[i]);
		}
		select(); 
		if (stay_in_global_times == 1000) { break; }
	}
	cout << globalValue << endl;
	for (int i = 0; i < N; ++i) {
		cout << globalPath[i] << " --> ";
	}

	// delete Path...
	delete[]tempValue;
	delete[]Value;
	for (int i = 0; i < tempLEN; ++i) {
		delete[]tempPaths[i];
	}
	delete[] tempPaths;

	for (int i = 0; i < LEN; ++i) {
		delete[]Paths[i];
	}
	delete[] Paths;

	for (int i = 0; i < N; ++i) {
		delete[] Mat[i];
	}
	delete[]Mat;
	system("pause");
}

// preserve the parents.
void preserve(int *p1, int *p2, int v1, int v2) {
	for (int i = 0; i < N; ++i) {
		tempPaths[temp_index][i] = p1[i];
	}
	tempValue[temp_index] = v1;
	//if (tempValue[temp_index] < 0) cout << "wrong\n";
	temp_index++;
	if (p2 != NULL) {
		for (int i = 0; i < N; ++i) {
			tempPaths[temp_index][i] = p2[i];
		}
		tempValue[temp_index] = v2;
		//if (tempValue[temp_index] < 0) cout << "wrong\n";
		temp_index++;
	}	
}

struct enumate{
	double data;
	int index;
	bool operator < (const enumate& e) const {
		return data < e.data;
	}
};

vector<int> argsort_temp_value() {
	enumate * data = new enumate[temp_index];
	for (int i = 0; i < temp_index; ++i) { 
		data[i].data = tempValue[i];
		data[i].index = i;
	}
	sort(data, data +temp_index);
	vector<int> ans(temp_index);
	for (int i = 0; i < temp_index; ++i) ans[i] = data[i].index;
	delete[]data;
	return ans;
}

void select() {
	vector<int> id = argsort_temp_value();
	for (int i = 0; i < temp_index; ++i) if (tempValue[i] < 0)cout << tempValue[i] << endl;
	for (int i = 0; i < LEN; ++i) {
		Value[i] = tempValue[id[i]];
		for (int j = 0; j < N; ++j) {
			Paths[i][j] = tempPaths[id[i]][j];
		}
	}
	if (Value[0] < globalValue) {
		for (int i = 0; i < N; ++i) globalPath[i] = Paths[0][i];
		stay_in_global_times = 0;
		globalValue = Value[0];
	}
	else if (Value[0] == globalValue) {
		stay_in_global_times++;
	}
	else {
		cout << "Something wrong" << Value[0]<< endl;
	}
}

// 基於次序的交配方式
void crossover(int * p1, int * p2)
{
	// generate son from p1 and p2
	int crossn = RAND(0, N-1);
	if (crossn == 0) return;
	int * indexs = new int[crossn];
	for (int i = 0; i < crossn; ++i) {
		if (i == 0) indexs[i] = RAND(1, N - crossn + 1);
		else { 
			indexs[i] = RAND(indexs[i - 1] + 1, N - crossn + i + 1);
		}
	}
	// copy from p2
	for (int i = 0; i < N; ++i) {
		tempPaths[temp_index][i] = p2[i];
	}
	int use_count = 0;
	for (int i = 0; i < N; ++i) {
		if (use_count == crossn) break;
		for (int j = 0; j < crossn; ++j) {
			if (tempPaths[temp_index][i] == p1[indexs[j]]) {
				tempPaths[temp_index][i] = p1[indexs[use_count]];
				use_count++;
				break;
			}
		}
	}
	// cal value
	tempValue[temp_index] = CalValue(tempPaths[temp_index]);
	
	temp_index++;
	// copy from p1
	for (int i = 0; i < N; ++i) {
		tempPaths[temp_index][i] = p1[i];
	}
	use_count = 0;
	for (int i = 0; i < N; ++i) {
		if (use_count == crossn) break;
		for (int j = 0; j < crossn; ++j) {
			if (tempPaths[temp_index][i] == p2[indexs[j]]) {
				tempPaths[temp_index][i] = p2[indexs[use_count]];
				use_count++;
				break;
			}
		}
	}
	tempValue[temp_index] = CalValue(tempPaths[temp_index]);
	temp_index++;
	delete[] indexs;
}

// 基於次序的變異
void mutation(int * Path)
{
	int a = RAND(1, N), b, t;
	if (a == N - 1) {
		b = RAND(1, N - 1);
		t = a; 
		a = b; 
		b = t;
	}
	else {
		b = RAND(a + 1, N);
	}
	for (int i = 0; i < N; ++i) {
		if (i == a)
			tempPaths[temp_index][i] = Path[b];
		else if ( i == b )
			tempPaths[temp_index][i] = Path[a];
		else tempPaths[temp_index][i] = Path[i];
	}
	tempValue[temp_index] = CalValue(tempPaths[temp_index]);
	temp_index++;
}

void initialPaths() {
	for (int i = 0; i < LEN; ++i)
		initialPath(Paths[i], i);
	find_min();
}

void find_min(int first)
{
	int temp = 0;
	for (int i = 1; i < LEN; ++i)
		if (Value[temp] > Value[i]) temp = i;
	if (first) {
		globalValue = Value[temp];
		for (int i = 0; i < N; ++i)
			globalPath[i] = Paths[temp][i];
	}
	else if (Value[temp] < globalValue) {
		globalValue = Value[temp];
		for (int i = 0; i < N; ++i)
			globalPath[i] = Paths[temp][i];
	}
}

void initialPath(int *Path, int j) {
	for (int i = 0; i < N; ++i) {
		Path[i] = i;
	}
	// Path[i]表示路上第i個點的標記為Path[i]
	// Path[0] = 0
	int tx, t;
	for (int i = 1; i < N - 1; ++i) {
		tx = RAND(i, N);
		if (tx != i) { // swap
			t = Path[i];
			Path[i] = Path[tx];
			Path[tx] = t;
		}
	
            
           

相關推薦

遺傳演算法Genetic Algorithm +C++實現解決TSP問題

概念 生物進化中的概念 遺傳演算法中的作用 環境 適應函式 適應性 適應函式值 適者生存 適應值大的解被保留

【尋優演算法遺傳演算法Genetic Algorithm 引數尋優的python實現

【尋優演算法】遺傳演算法(Genetic Algorithm) 引數尋優的python實現 一、遺傳演算法簡介 1、遺傳演算法由來 2、遺傳演算法名詞概念 3、遺傳演算法中對染色體的操作 3.1、選擇 3.2

簡單的遺傳演算法Genetic algorithms-吃豆人

遺傳演算法簡介: 一直都在收聽卓老闆聊科技這個節目,最近播出了一起人工智慧的節目,主要講的是由霍蘭提出的遺傳演算法,在目中詳細闡述了一個有趣的小實驗:吃豆人。 首先簡單介紹下遺傳演算法: 1:為了解決某個具體的問題,先隨機生成若干個解決問題的實體,每個實體

分水嶺演算法Watershed algorithm與OpenCV實現

前言 分水嶺演算法主要用於影象的分割!          這個演算法需要輸入一個灰度圖,在接下來的洪水漫堤過程中,相鄰的積水盆地之間的分水嶺便慢慢構建起來。一般情況下,這會引起過分割,尤其是具有噪聲的影象。          影象必須要預處理,以消除噪聲;分割結果必須要基於

Python實現遺傳演算法二進位制編碼求函式最優值

目標函式 maxf(x1,x2)=21.5+x1sin(4πx1)+x2sin(20πx2) max f({x_1},{x_2}) = 21.5 + {x_1}\sin (4\pi {x_1}) + {x_2}\sin (20\pi {x_2})

1015 Reversible Primes 20 分C++實現 (已AC)

題目 題目連結:https://pintia.cn/problem-sets/994805342720868352/problems/994805495863296000 思路: 這道題的思路很簡單, 檢查輸入數是否是質數, 如果是的話, 把它按進位制轉換, 再翻轉, 轉換回

1014 Waiting in Line 30 分C++實現(已AC)

題目 題目連結:https://pintia.cn/problem-sets/994805342720868352/problems/994805498207911936 思路: 建立一個視窗列表,每個列表維護一個M長度的佇列, 以及佇列末尾的人服務結束的時間, 逐個插入客戶

1012 The Best Rank 25 分c++實現(已AC)

題目: 連結:https://pintia.cn/problem-sets/994805342720868352/problems/994805502658068480 思路: 像是自己見一個數據庫, 用以增和查, 我的思路是按照4個科目(M, C, E, A)建四個表, 每

1010 Radix 25 分C++實現-終於AC了

題目 題目連結:https://pintia.cn/problem-sets/994805342720868352/problems/994805507225665536 是一道目前為止比較有意思的題,也是碰到的為數不多需要考慮上溢位的題目 知識點:整型上溢位, 二分查詢 思

1004 Counting Leaves 30 分c++實現

題目 https://pintia.cn/problem-sets/994805342720868352/problems/994805521431773184 解題思路與程式碼實現 思路1: 構建一個結構體,包含三個元素,id,是否有孩子, 層級 構建一個數組維護樹結構

堆排序 heap sort C# 實現

main 函式中呼叫:            var data = new int[] {5,7,6,4,1,9,3};             heapSort(data);

【深度學習筆記】優化演算法 Optimization Algorithm

本文依舊是吳恩達《深度學習工程師》課程的筆記整理與拓展。 一、優化演算法的目的與挑戰     優化演算法主要是用來加快神經網路的訓練速度,使得目標函式快速收斂。     優化問題面臨的挑戰有病態解、鞍點、梯度爆炸與梯度消失……具體可見參考文獻【1】241頁到249頁。

1152 Google Recruitment 20 分(c++實現 已AC)

思路 題目的大意很簡單, 在L長度的數字字串中找到第一個k長度的素數. 如果找不到哦啊返回404, 找到了返回這個k長度的字串. 一個簡單暴力的思路就是遍歷L長度, 檢查從這個字元起往後k長度的字串表示的數字是否是素數. 測試點: // 樣例測試點 20 5 23654987

1043 Is It a Binary Search Tree 25 分C++實現(已AC)

題目 題目連結 :https://pintia.cn/problem-sets/994805342720868352/problems/994805440976633856 思路: 建樹的思路是不可行的, 對於普通二叉樹一定需要中序遍歷和另一個遍歷來複原,對於二叉搜尋樹來說,

1026 Table Tennis 30 分C++實現(已AC)

題目: 題目連結:https://pintia.cn/problem-sets/994805342720868352/problems 思路: 感覺我的思路比較清奇, 導致程式碼寫的比較長… 我的思路是: 建立一個客戶列表,時間按照秒儲存,按照到達時間排序; 桌子放入一個列表

排序演算法sorting algorithm之 插入排序insertion sort

https://en.wikipedia.org/wiki/Insertion_sort   loop1: 4,6,1,3,7 -> 4,6,1,3,7 loop2: 4,6,1,3,7 -> 4,1,6,3,7         &nb

線性判別--感知機演算法perceptron algorithm

  感知器演算法是一種線性判別演算法,它適用於二分類模型。在這個模型中,輸入向量x\mathbf{x}x首先使用一個固定的非線性變換得到一個特徵向量ϕ(x)\phi(\mathbf{x})ϕ(x),接著用這個特徵向量構造一個線性模型: (1)y(x)=f(wTϕ

馬拉車演算法Manacher Algorithm--用於計算最長迴文子串

馬拉車演算法的目標是找到一串字串中的最長迴文子串,優點是時間複雜度為O(n) 現以尋找 “cgbaabgk” 中的最長子迴文串( “gbaabg”)為例進行說明 演算法過程(總共3步): 1.改造字串結構: 字元座標 0 1

合併排序Merge SortC 實現簡單效能測試

#include <time.h> #include <stdlib.h> #include <stdio.h> #define sential RAND_MAX  /* 定義哨兵*/ #define SIZE 1000000/

迪傑斯特拉演算法Dijkstra algorithm

       emmmm....寫語氣詞被同桌吐槽啊....嫌我emmm太長。桑心QAQ        好把同桌趕跑了~        這次來講迪傑斯特拉,這個東西嘛...和我們上一次看的弗洛伊德差不多啦,對沒錯不是那個寫性學三論的傢伙,所以不用期待我的文章裡會出現什麼奇