代碼地址:https://github.com/laiy/AI/tree/master/awesome-search
一些前提:
1. 首先要明確這些算法並不是用於解決傳統的搜索問題的(環境是可觀察的,確定的,已知的,問題解是一個行動序列),這些算法適用於哪些關注解狀態而不是路徑代價的問題,我們討論的搜索算法往往和現實世界的一些問題更加的契合。
2. 為了便於測試我們選擇了八皇后和八數碼問題,不考慮它的一些特殊性質來作為一個搜索問題。(因為這兩個問題本身就可以用經典搜索策略來解決,這里只是忽略掉問題的一些性質來模擬一個不可用經典搜索解決的問題)
首先對八皇后問題和八數碼問題做個簡單的介紹:
八皇后問題:
The eight queens puzzle is the problem of placing eight chess queens on an 8×8 chessboard so that no two queens threaten each other. Thus, a solution requires that no two queens share the same row, column, or diagonal.
八數碼問題:
對給的任意一個初始狀態,找到回到目標狀態的步驟。
這兩個問題都是經典的搜索問題,用DFS或BFS都可以找到解,這里我們探討的是爬山法,隨機重啟爬山法和模擬退火算法對這兩個問題的求解性能和能力。
首先,編寫測例生成器,我選擇生成100k個初始狀態的測例,生成思想:
八皇后:生成八個1-8的隨機數作為一個八皇后的初始狀態,數字表示在每一列皇后的位置,例如4 6 8 2 7 1 3 5對應如下狀態
八數碼:終點位置開始隨機移動10k步達到一個有解初始狀態
測例生成器代碼如下:
1 /* 2 * this program is used for generate the majority of 8 digits problems and 8 queens problems 3 */ 4 5 #include <cstdio> 6 #include <cstdlib> 7 8 #define TESRCASE 100000 9 #define STEP 10000 10 #define UP 0 11 #define DOWN 1 12 #define LEFT 2 13 #define RIGHT 3 14 15 inline void swap(int *a, int *b) { 16 *a ^= *b ^= *a ^= *b; 17 } 18 19 void generate_8_digits_problem() { 20 FILE *fp = fopen("testcase_8_digits_problem", "w"); 21 int testcase = TESRCASE, direction, position, steps; 22 while (testcase--) { 23 int state[9] = {1, 2, 3, 4, 5, 6, 7, 8, 0}; 24 steps = STEP; 25 position = 9; 26 while (steps--) { 27 direction = rand() % 4; 28 switch (direction) { 29 case UP: 30 if (position <= 3) 31 break; 32 else { 33 swap(&state[position - 1], &state[position - 4]), position -= 3; 34 break; 35 } 36 case DOWN: 37 if (position >= 7) 38 break; 39 else { 40 swap(&state[position - 1], &state[position + 2]), position += 3; 41 break; 42 } 43 case LEFT: 44 if (position % 3 == 1) 45 break; 46 else { 47 swap(&state[position - 1], &state[position - 2]), position--; 48 break; 49 } 50 case RIGHT: 51 if (position % 3 == 0) 52 break; 53 else { 54 swap(&state[position - 1], &state[position]), position++; 55 break; 56 } 57 } 58 } 59 fprintf(fp, "%d %d %d %d %d %d %d %d %d\n", state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7], state[8]); 60 } 61 fclose(fp); 62 } 63 64 void generate_8_queens_problem() { 65 FILE *fp = fopen("testcase_8_queens_problem", "w"); 66 int testcase = TESRCASE, state[8], position, i; 67 while (testcase--) { 68 for (i = 0; i < 8; i++) 69 position = rand() % 8 + 1, state[i] = position; 70 fprintf(fp, "%d %d %d %d %d %d %d %d\n", state[0], state[1], state[2], state[3], state[4], state[5], state[6], state[7]); 71 } 72 fclose(fp); 73 } 74 75 int main() { 76 generate_8_digits_problem(); 77 generate_8_queens_problem(); 78 return 0; 79 }
好,現在我們就得到了八皇后問題和八數碼問題各100k個初始狀態的測例了,分別保存在testcase_8_digits_problem和testcase_8_queens_problem中,每個測例占用1行。
首先測試爬山法,我們約定用成功的測例的平均時間作為性能指標,用測例的解決率作為能力指標。
爬山法的思想:對每一個狀態,永遠像最理想的狀態前進(貪心)。
對八皇后問題來說,我們用相互碰撞的皇后對作為狀態的權重(weight),那么爬山法的做法就是對每一個后繼狀態計算weight,記錄下最小weight的狀態,前進。
對八數碼問題來說,我們用移動后曼哈頓距離作為狀態權重(weight),最小weight的下一狀態作為前進狀態。
如果周圍的所有狀態都不能達到“爬山”的效果,則算法達到一個山脊(極大值),如果未找到解則認為算法失敗。
爬山法測試代碼如下:
1 # ifndef CLK_TCK 2 # define CLK_TCK CLOCKS_PER_SEC 3 # endif 4 5 #include <cstdio> 6 #include <ctime> 7 #include <cmath> 8 #include <cstdlib> 9 #include <vector> 10 #include <algorithm> 11 12 #define TESRCASE 100000 13 #define UP 0 14 #define DOWN 1 15 #define LEFT 2 16 #define RIGHT 3 17 18 struct State { 19 int direction, diff_manhattan; 20 State(int i, int dis) { 21 this->direction = i; 22 this->diff_manhattan = dis; 23 } 24 bool operator<(const State &s) const { 25 return diff_manhattan > s.diff_manhattan; 26 } 27 }; 28 29 double eight_digits_problem_time; 30 double eight_queens_problem_time; 31 int eight_digits_problem_failed_times; 32 int eight_queens_problem_failed_times; 33 int diff_manhattan_distance; 34 35 inline void swap(int *a, int *b) { 36 *a ^= *b ^= *a ^= *b; 37 } 38 39 inline bool solved(int *state) { 40 for (int i = 0; i < 8; i++) 41 if (state[i] != i + 1) 42 return false; 43 return true; 44 } 45 46 inline int manhattan_distance(int num, int position) { 47 int dest_x = ceil(num / 3), dest_y = (num - 1) % 3 + 1, position_x = ceil(position / 3), position_y = (position - 1) % 3 + 1; 48 return abs(dest_x - position_x) + abs(dest_y - position_y); 49 } 50 51 bool eight_digits_better(int *state, int position, int direction) { 52 switch (direction) { 53 case UP: 54 if (position <= 3) 55 return false; 56 else { 57 diff_manhattan_distance = manhattan_distance(state[position - 4], position - 3) - manhattan_distance(state[position - 4], position); 58 return manhattan_distance(state[position - 4], position - 3) > manhattan_distance(state[position - 4], position); 59 } 60 case DOWN: 61 if (position >= 7) 62 return false; 63 else { 64 diff_manhattan_distance = manhattan_distance(state[position + 2], position + 3) - manhattan_distance(state[position + 2], position); 65 return manhattan_distance(state[position + 2], position + 3) > manhattan_distance(state[position + 2], position); 66 } 67 case LEFT: 68 if (position % 3 == 1) 69 return false; 70 else { 71 diff_manhattan_distance = manhattan_distance(state[position - 2], position - 1) - manhattan_distance(state[position - 2], position); 72 return manhattan_distance(state[position - 2], position - 1) > manhattan_distance(state[position - 2], position); 73 } 74 case RIGHT: 75 if (position % 3 == 0) 76 return false; 77 else { 78 diff_manhattan_distance = manhattan_distance(state[position], position + 1) - manhattan_distance(state[position], position); 79 return manhattan_distance(state[position], position + 1) > manhattan_distance(state[position], position); 80 } 81 } 82 return false; 83 } 84 85 void solve_one_case_of_8_digits_problem(int *state) { 86 clock_t start_time = clock(); 87 int position, i; 88 bool found; 89 for (i = 0; i < 9; i++) 90 if (state[i] == 0) { 91 position = i + 1; 92 break; 93 } 94 while (!solved(state)) { 95 found = false; 96 std::vector<State> v; 97 for (i = 0; i < 4; i++) { 98 if (eight_digits_better(state, position, i)) { 99 found = true; 100 v.push_back(State(i, diff_manhattan_distance)); 101 } 102 if (i == 3 && found) { 103 std::sort(v.begin(), v.end()); 104 switch (v[0].direction) { 105 case UP: 106 swap(&state[position - 1], &state[position - 4]), position -= 3; 107 break; 108 case DOWN: 109 swap(&state[position - 1], &state[position + 2]), position += 3; 110 break; 111 case LEFT: 112 swap(&state[position - 1], &state[position - 2]), position--; 113 break; 114 case RIGHT: 115 swap(&state[position - 1], &state[position]), position++; 116 break; 117 } 118 } 119 } 120 if (!found) { 121 eight_digits_problem_failed_times++; 122 return; 123 } 124 } 125 eight_digits_problem_time += (double)(clock() - start_time) / CLK_TCK; 126 } 127 128 void solve_8_digits_problem() { 129 FILE *fp = fopen("testcase_8_digits_problem", "r"); 130 int original_state[9]; 131 while (fscanf(fp, "%d %d %d %d %d %d %d %d %d", original_state, original_state + 1, original_state + 2, original_state + 3, original_state + 4, original_state + 5, original_state + 6, original_state + 7, original_state + 8) != EOF) 132 solve_one_case_of_8_digits_problem(original_state); 133 fclose(fp); 134 } 135 136 int peers_of_attacking_queens(int *state) { 137 int peers = 0, i, j, k; 138 for (i = 0; i < 7; i++) { 139 for (j = i + 1; j < 8; j++) 140 if (state[j] == state[i]) 141 peers++; 142 for (j = i + 1, k = state[i] + 1; j < 8 && k <= 8; j++, k++) 143 if (state[j] == k) 144 peers++; 145 for (j = i + 1, k = state[i] - 1; j < 8 && k >= 1; j++, k--) 146 if (state[j] == k) 147 peers++; 148 } 149 return peers; 150 } 151 152 void solve_one_case_of_8_queens_problem(int *state) { 153 clock_t start_time = clock(); 154 int h = peers_of_attacking_queens(state), i, j, best_i, best_j, temp, record; 155 while (h != 0) { 156 best_i = -1; 157 for (i = 1; i <= 8; i++) { 158 record = state[i - 1]; 159 for (j = 1; j <= 8; j++) { 160 if (j != record) { 161 state[i - 1] = j; 162 temp = peers_of_attacking_queens(state); 163 if (temp < h) 164 h = temp, best_i = i, best_j = j; 165 } 166 } 167 state[i - 1] = record; 168 } 169 if (best_i == -1) { 170 eight_queens_problem_failed_times++; 171 return; 172 } 173 state[best_i - 1] = best_j; 174 } 175 eight_queens_problem_time += (double)(clock() - start_time) / CLK_TCK; 176 } 177 178 void solve_8_queens_problem() { 179 FILE *fp = fopen("testcase_8_queens_problem", "r"); 180 int original_state[8]; 181 while (fscanf(fp, "%d %d %d %d %d %d %d %d", original_state, original_state + 1, original_state + 2, original_state + 3, original_state + 4, original_state + 5, original_state + 6, original_state + 7) != EOF) 182 solve_one_case_of_8_queens_problem(original_state); 183 fclose(fp); 184 } 185 186 void print_result() { 187 printf("eight digits problem average solved times: %lf\n", eight_digits_problem_time / (double)(TESRCASE - eight_digits_problem_failed_times)); 188 printf("eight digits problem solved rate: %lf\n", 1 - double(eight_digits_problem_failed_times) / TESRCASE); 189 printf("eight queens problem average solved times: %lf\n", eight_queens_problem_time / (double)(TESRCASE - eight_queens_problem_failed_times)); 190 printf("eight queens problem solved rate: %lf\n", 1 - double(eight_queens_problem_failed_times) / TESRCASE); 191 } 192 193 int main() { 194 eight_digits_problem_time = 0; 195 eight_queens_problem_time = 0; 196 eight_digits_problem_failed_times = 0; 197 eight_queens_problem_failed_times = 0; 198 solve_8_digits_problem(); 199 solve_8_queens_problem(); 200 print_result(); 201 return 0; 202 }
測試結果如下:
我們可以看到,用爬山法來解決八數碼問題解決率是非常非常低的,99.9%+的測例用爬山法是無法得到解的。
而對於八皇后問題,爬山法解決率也不是很理想,14.6%的測例是可以找到解的。
優點是平均解決時間短,因為貪心並不需要回溯,找到底就結束了,可以理解為不回溯的啟發式DFS。
然后是隨機重啟爬山法
隨機重啟爬山法的思想是,對於給定的初始狀態如果不能找到解,自己隨機生成一個初始狀態重啟求解直到找到最終的解為止。
這個作為對於八皇后問題來說是合情合理的作為,對於八數碼問題可能有人會認為是不妥當的,因為八數碼問題的目標就是給定初始狀態,找到到達最終狀態的路線,初始狀態是不能隨意改變的。
而八皇后問題不一樣,八皇后問題目標就是找到最終狀態,所有初始狀態隨機改變是可以允許的。
提出這個問題的讀者可以回到本文最開始理解一些我描述的前提。
代碼如下:
1 # ifndef CLK_TCK 2 # define CLK_TCK CLOCKS_PER_SEC 3 # endif 4 5 #include <cstdio> 6 #include <ctime> 7 #include <cmath> 8 #include <cstdlib> 9 #include <vector> 10 #include <algorithm> 11 12 #define TESRCASE 100000 13 #define STEP 100 14 #define UP 0 15 #define DOWN 1 16 #define LEFT 2 17 #define RIGHT 3 18 19 struct State { 20 int direction, diff_manhattan; 21 State(int i, int dis) { 22 this->direction = i; 23 this->diff_manhattan = dis; 24 } 25 bool operator<(const State &s) const { 26 return diff_manhattan > s.diff_manhattan; 27 } 28 }; 29 30 double eight_digits_problem_time; 31 double eight_queens_problem_time; 32 int eight_digits_problem_failed_times; 33 int eight_queens_problem_failed_times; 34 int diff_manhattan_distance; 35 36 inline void swap(int *a, int *b) { 37 *a ^= *b ^= *a ^= *b; 38 } 39 40 inline bool solved(int *state) { 41 for (int i = 0; i < 8; i++) 42 if (state[i] != i + 1) 43 return false; 44 return true; 45 } 46 47 inline int manhattan_distance(int num, int position) { 48 int dest_x = ceil(num / 3), dest_y = (num - 1) % 3 + 1, position_x = ceil(position / 3), position_y = (position - 1) % 3 + 1; 49 return abs(dest_x - position_x) + abs(dest_y - position_y); 50 } 51 52 bool eight_digits_better(int *state, int position, int direction) { 53 switch (direction) { 54 case UP: 55 if (position <= 3) 56 return false; 57 else { 58 diff_manhattan_distance = manhattan_distance(state[position - 4], position - 3) - manhattan_distance(state[position - 4], position); 59 return manhattan_distance(state[position - 4], position - 3) > manhattan_distance(state[position - 4], position); 60 } 61 case DOWN: 62 if (position >= 7) 63 return false; 64 else { 65 diff_manhattan_distance = manhattan_distance(state[position + 2], position + 3) - manhattan_distance(state[position + 2], position); 66 return manhattan_distance(state[position + 2], position + 3) > manhattan_distance(state[position + 2], position); 67 } 68 case LEFT: 69 if (position % 3 == 1) 70 return false; 71 else { 72 diff_manhattan_distance = manhattan_distance(state[position - 2], position - 1) - manhattan_distance(state[position - 2], position); 73 return manhattan_distance(state[position - 2], position - 1) > manhattan_distance(state[position - 2], position); 74 } 75 case RIGHT: 76 if (position % 3 == 0) 77 return false; 78 else { 79 diff_manhattan_distance = manhattan_distance(state[position], position + 1) - manhattan_distance(state[position], position); 80 return manhattan_distance(state[position], position + 1) > manhattan_distance(state[position], position); 81 } 82 } 83 return false; 84 } 85 86 void eight_digits_random_state(int *s) { 87 int state[9] = {1, 2, 3, 4, 5, 6, 7, 8, 0}, steps, position, direction; 88 steps = STEP; 89 position = 9; 90 while (steps--) { 91 direction = rand() % 4; 92 switch (direction) { 93 case UP: 94 if (position <= 3) 95 break; 96 else { 97 swap(&state[position - 1], &state[position - 4]), position -= 3; 98 break; 99 } 100 case DOWN: 101 if (position >= 7) 102 break; 103 else { 104 swap(&state[position - 1], &state[position + 2]), position += 3; 105 break; 106 } 107 case LEFT: 108 if (position % 3 == 1) 109 break; 110 else { 111 swap(&state[position - 1], &state[position - 2]), position--; 112 break; 113 } 114 case RIGHT: 115 if (position % 3 == 0) 116 break; 117 else { 118 swap(&state[position - 1], &state[position]), position++; 119 break; 120 } 121 } 122 } 123 for (int i = 0; i < 9; i++) 124 s[i] = state[i]; 125 } 126 127 void solve_one_case_of_8_digits_problem(int *state) { 128 clock_t start_time = clock(); 129 int position, i; 130 bool found; 131 for (i = 0; i < 9; i++) 132 if (state[i] == 0) { 133 position = i + 1; 134 break; 135 } 136 while (!solved(state)) { 137 found = false; 138 std::vector<State> v; 139 for (i = 0; i < 4; i++) { 140 if (eight_digits_better(state, position, i)) { 141 found = true; 142 v.push_back(State(i, diff_manhattan_distance)); 143 } 144 if (i == 3 && found) { 145 std::sort(v.begin(), v.end()); 146 switch (v[0].direction) { 147 case UP: 148 swap(&state[position - 1], &state[position - 4]), position -= 3; 149 break; 150 case DOWN: 151 swap(&state[position - 1], &state[position + 2]), position += 3; 152 break; 153 case LEFT: 154 swap(&state[position - 1], &state[position - 2]), position--; 155 break; 156 case RIGHT: 157 swap(&state[position - 1], &state[position]), position++; 158 break; 159 } 160 } 161 } 162 if (!found) 163 eight_digits_random_state(state); 164 } 165 eight_digits_problem_time += (double)(clock() - start_time) / CLK_TCK; 166 } 167 168 void solve_8_digits_problem() { 169 FILE *fp = fopen("testcase_8_digits_problem", "r"); 170 int original_state[9]; 171 while (fscanf(fp, "%d %d %d %d %d %d %d %d %d", original_state, original_state + 1, original_state + 2, original_state + 3, original_state + 4, original_state + 5, original_state + 6, original_state + 7, original_state + 8) != EOF) 172 solve_one_case_of_8_digits_problem(original_state); 173 fclose(fp); 174 } 175 176 int peers_of_attacking_queens(int *state) { 177 int peers = 0, i, j, k; 178 for (i = 0; i < 7; i++) { 179 for (j = i + 1; j < 8; j++) 180 if (state[j] == state[i]) 181 peers++; 182 for (j = i + 1, k = state[i] + 1; j < 8 && k <= 8; j++, k++) 183 if (state[j] == k) 184 peers++; 185 for (j = i + 1, k = state[i] - 1; j < 8 && k >= 1; j++, k--) 186 if (state[j] == k) 187 peers++; 188 } 189 return peers; 190 } 191 192 inline void eight_queens_random_state(int *state) { 193 for (int i = 0; i < 8; i++) 194 state[i] = rand() % 8 + 1; 195 } 196 197 void solve_one_case_of_8_queens_problem(int *state) { 198 clock_t start_time = clock(); 199 int h = peers_of_attacking_queens(state), i, j, best_i, best_j, temp, record; 200 while (h != 0) { 201 best_i = -1; 202 for (i = 1; i <= 8; i++) { 203 record = state[i - 1]; 204 for (j = 1; j <= 8; j++) { 205 if (j != record) { 206 state[i - 1] = j; 207 temp = peers_of_attacking_queens(state); 208 if (temp < h) 209 h = temp, best_i = i, best_j = j; 210 } 211 } 212 state[i - 1] = record; 213 } 214 if (best_i == -1) 215 eight_queens_random_state(state), h = peers_of_attacking_queens(state); 216 else 217 state[best_i - 1] = best_j; 218 } 219 eight_queens_problem_time += (double)(clock() - start_time) / CLK_TCK; 220 } 221 222 void solve_8_queens_problem() { 223 FILE *fp = fopen("testcase_8_queens_problem", "r"); 224 int original_state[8]; 225 while (fscanf(fp, "%d %d %d %d %d %d %d %d", original_state, original_state + 1, original_state + 2, original_state + 3, original_state + 4, original_state + 5, original_state + 6, original_state + 7) != EOF) 226 solve_one_case_of_8_queens_problem(original_state); 227 fclose(fp); 228 } 229 230 void print_result() { 231 printf("eight digits problem average solved times: %lf\n", eight_digits_problem_time / (double)(TESRCASE - eight_digits_problem_failed_times)); 232 printf("eight digits problem solved rate: %lf\n", 1 - double(eight_digits_problem_failed_times) / TESRCASE); 233 printf("eight queens problem average solved times: %lf\n", eight_queens_problem_time / (double)(TESRCASE - eight_queens_problem_failed_times)); 234 printf("eight queens problem solved rate: %lf\n", 1 - double(eight_queens_problem_failed_times) / TESRCASE); 235 } 236 237 int main() { 238 eight_digits_problem_time = 0; 239 eight_queens_problem_time = 0; 240 eight_digits_problem_failed_times = 0; 241 eight_queens_problem_failed_times = 0; 242 solve_8_digits_problem(); 243 solve_8_queens_problem(); 244 print_result(); 245 return 0; 246 }
測試結果如下:
我們可以看到隨機重啟爬山法的解決率是接近1的(不能說是1,因為確實存在無論怎么重啟都找不到解的可能性)
但是在每個測例解決的時間消耗上面就大打折扣了,對八數碼問題,是爬山法單個測例解決時間的600倍,對八皇后問題單個測例平均解決時間大概是7倍。
這個時間效率和爬山法的問題解決率有關系,解決率越大時間差距越小。
現在測試最后一個算法:模擬退火算法
模擬退火算法的思想:模擬煉金退火的步驟,溫度隨時間下降,在溫度高的時候,分子是活躍的,有較大可能性隨機運動。
對應到具體問題上就是:對每一個后繼狀態,如果找到的狀態不能使weight更好,也有exp(weight/temperature)的可能性去走這個狀態,然后隨着溫度下降,這個概率越來越小,達到模擬退火的效果。
我選擇溫度下降為比例下降,系數為0.8,每一個溫度最多可以持續300次迭代,終止溫度為0.000000001,八數碼問題起始溫度為10,八皇后問題起始溫度為50。
代碼如下:
1 # ifndef CLK_TCK 2 # define CLK_TCK CLOCKS_PER_SEC 3 # endif 4 5 #include <cstdio> 6 #include <ctime> 7 #include <cmath> 8 #include <cstdlib> 9 #include <vector> 10 #include <algorithm> 11 12 #define TESRCASE 100000 13 #define UP 0 14 #define DOWN 1 15 #define LEFT 2 16 #define RIGHT 3 17 #define EIGHT_DIGITS_ORIGINAL_TEMPERATURE 10 18 #define EIGHT_QUEENS_ORIGINAL_TEMPERATURE 50 19 #define MAX_TRIES_IN_ONE_TEMPERATURE 300 20 #define STOP_TEMPERATURE 0.000000001 21 #define COLD_DOWN_RATE 0.8 22 23 double eight_digits_problem_time; 24 double eight_queens_problem_time; 25 int eight_digits_problem_failed_times; 26 int eight_queens_problem_failed_times; 27 double temperature; 28 29 inline void swap(int *a, int *b) { 30 *a ^= *b ^= *a ^= *b; 31 } 32 33 inline bool solved(int *state) { 34 for (int i = 0; i < 8; i++) 35 if (state[i] != i + 1) 36 return false; 37 return true; 38 } 39 40 inline int manhattan_distance(int num, int position) { 41 int dest_x = ceil(num / 3), dest_y = (num - 1) % 3 + 1, position_x = ceil(position / 3), position_y = (position - 1) % 3 + 1; 42 return abs(dest_x - position_x) + abs(dest_y - position_y); 43 } 44 45 bool eight_digits_better(int *state, int position, int direction) { 46 int dis; 47 switch (direction) { 48 case UP: 49 if (position <= 3) 50 return false; 51 else { 52 dis = manhattan_distance(state[position - 4], position - 3) - manhattan_distance(state[position - 4], position); 53 return (dis > 0) ? true : ((double)(rand() % 1000) / 1000) < exp(dis / temperature); 54 } 55 case DOWN: 56 if (position >= 7) 57 return false; 58 else { 59 dis = manhattan_distance(state[position + 2], position + 3) - manhattan_distance(state[position + 2], position); 60 return (dis > 0) ? true : ((double)(rand() % 1000) / 1000) < exp(dis / temperature); 61 } 62 case LEFT: 63 if (position % 3 == 1) 64 return false; 65 else { 66 dis = manhattan_distance(state[position - 2], position - 1) - manhattan_distance(state[position - 2], position); 67 return (dis > 0) ? true : ((double)(rand() % 1000) / 1000) < exp(dis / temperature); 68 } 69 case RIGHT: 70 if (position % 3 == 0) 71 return false; 72 else { 73 dis = manhattan_distance(state[position], position + 1) - manhattan_distance(state[position], position); 74 return (dis > 0) ? true : ((double)(rand() % 1000) / 1000) < exp(dis / temperature); 75 } 76 } 77 return false; 78 } 79 80 void solve_one_case_of_8_digits_problem(int *state) { 81 clock_t start_time = clock(); 82 int position, i, tries_count; 83 bool found; 84 for (i = 0; i < 9; i++) 85 if (state[i] == 0) { 86 position = i + 1; 87 break; 88 } 89 temperature = EIGHT_DIGITS_ORIGINAL_TEMPERATURE; 90 tries_count = MAX_TRIES_IN_ONE_TEMPERATURE; 91 while (!solved(state)) { 92 found = false; 93 for (i = 0; i < 4; i++) 94 if (eight_digits_better(state, position, i)) { 95 found = true; 96 switch (i) { 97 case UP: 98 swap(&state[position - 1], &state[position - 4]), position -= 3; 99 break; 100 case DOWN: 101 swap(&state[position - 1], &state[position + 2]), position += 3; 102 break; 103 case LEFT: 104 swap(&state[position - 1], &state[position - 2]), position--; 105 break; 106 case RIGHT: 107 swap(&state[position - 1], &state[position]), position++; 108 break; 109 } 110 break; 111 } 112 if (--tries_count == 0) 113 tries_count = MAX_TRIES_IN_ONE_TEMPERATURE, temperature *= COLD_DOWN_RATE; 114 if (!found || temperature < STOP_TEMPERATURE) { 115 eight_digits_problem_failed_times++; 116 return; 117 } 118 } 119 eight_digits_problem_time += (double)(clock() - start_time) / CLK_TCK; 120 } 121 122 void solve_8_digits_problem() { 123 FILE *fp = fopen("testcase_8_digits_problem", "r"); 124 int original_state[9]; 125 while (fscanf(fp, "%d %d %d %d %d %d %d %d %d", original_state, original_state + 1, original_state + 2, original_state + 3, original_state + 4, original_state + 5, original_state + 6, original_state + 7, original_state + 8) != EOF) 126 solve_one_case_of_8_digits_problem(original_state); 127 fclose(fp); 128 } 129 130 int peers_of_attacking_queens(int *state) { 131 int peers = 0, i, j, k; 132 for (i = 0; i < 7; i++) { 133 for (j = i + 1; j < 8; j++) 134 if (state[j] == state[i]) 135 peers++; 136 for (j = i + 1, k = state[i] + 1; j < 8 && k <= 8; j++, k++) 137 if (state[j] == k) 138 peers++; 139 for (j = i + 1, k = state[i] - 1; j < 8 && k >= 1; j++, k--) 140 if (state[j] == k) 141 peers++; 142 } 143 return peers; 144 } 145 146 void solve_one_case_of_8_queens_problem(int *state) { 147 clock_t start_time = clock(); 148 int h = peers_of_attacking_queens(state), i, j, best_i, best_j, temp, record, tries_count, temp_h; 149 temperature = EIGHT_QUEENS_ORIGINAL_TEMPERATURE; 150 tries_count = MAX_TRIES_IN_ONE_TEMPERATURE; 151 while (h != 0) { 152 temp_h = 28; 153 for (i = 1; i <= 8; i++) { 154 record = state[i - 1]; 155 for (j = 1; j <= 8; j++) { 156 if (j != record) { 157 state[i - 1] = j; 158 temp = peers_of_attacking_queens(state); 159 if (temp < temp_h) 160 temp_h = temp, best_i = i, best_j = j; 161 } 162 } 163 state[i - 1] = record; 164 } 165 if (--tries_count == 0) 166 tries_count = MAX_TRIES_IN_ONE_TEMPERATURE, temperature *= COLD_DOWN_RATE; 167 if (!(temperature < STOP_TEMPERATURE) && (temp_h < h || ((double)(rand() % 1000) / 1000) < exp(temp_h / temperature))) 168 state[best_i - 1] = best_j, h = temp_h; 169 else { 170 eight_queens_problem_failed_times++; 171 return; 172 } 173 } 174 eight_queens_problem_time += (double)(clock() - start_time) / CLK_TCK; 175 } 176 177 void solve_8_queens_problem() { 178 FILE *fp = fopen("testcase_8_queens_problem", "r"); 179 int original_state[8]; 180 while (fscanf(fp, "%d %d %d %d %d %d %d %d", original_state, original_state + 1, original_state + 2, original_state + 3, original_state + 4, original_state + 5, original_state + 6, original_state + 7) != EOF) 181 solve_one_case_of_8_queens_problem(original_state); 182 fclose(fp); 183 } 184 185 void print_result() { 186 printf("eight digits problem average solved times: %lf\n", eight_digits_problem_time / (double)(TESRCASE - eight_digits_problem_failed_times)); 187 printf("eight digits problem solved rate: %lf\n", 1 - double(eight_digits_problem_failed_times) / TESRCASE); 188 printf("eight queens problem average solved times: %lf\n", eight_queens_problem_time / (double)(TESRCASE - eight_queens_problem_failed_times)); 189 printf("eight queens problem solved rate: %lf\n", 1 - double(eight_queens_problem_failed_times) / TESRCASE); 190 } 191 192 int main() { 193 eight_digits_problem_time = 0; 194 eight_queens_problem_time = 0; 195 eight_digits_problem_failed_times = 0; 196 eight_queens_problem_failed_times = 0; 197 solve_8_digits_problem(); 198 solve_8_queens_problem(); 199 print_result(); 200 return 0; 201 }
這個測試時間比較久,題主跑了6個小時才跑出來,測試結果如下:
可以看到,模擬退火算法的性能是比較優秀的,和爬山法的解決效率只相差0-1個數量級。
而且解決率相比爬山法也大幅度提升,對於八數碼問題問題解決率提升了10倍左右,對於八皇后問題解決率也提升了2倍多。
結論:
1. 解決NP問題用模擬退火算法在性能上和解決率上都有出色的表現。
2. 隨機重啟爬山法來達到很高的解決率,但是時間效率非常低下。
3. 爬山法有最高性能,但是問題解決率不堪入目。