局部搜索算法(加入随机因素):主要思想是要想得到全局最优解,总沿着局部最优的方向寻找,但会存在局部最优陷阱,即到达一个极值点(非最值点),在其邻域内无法找到更优解,所以需要多次尝试。
a) 变量:
i. N:皇后个数;
ii. 皇后位置数组Pos[N]:下标为行号,对应值为列号;
iii. 冲突表CollTable[2N][2]:分别记录正负对角线上的冲突数,对于正负对角线标号如下图所示,其中正对角线如图中蓝线所示,采取逆时针方向标号;负对角线如图中绿线所示,采取顺时针方向标号。
b) 邻域的定义:
随机选择两个皇后进行列交换得到的新序列。
c) 算法描述(只介绍N>3情况下,此时一定有解):
i. 初始化迭代次数IterCnt=0,初始化马尔科夫链长为N*N,随机生成皇后位置Pos[],每个皇后位于不同行不同列,记录冲突表CollTable,计算其冲突数collcnt;
ii. IterCnt++,判断冲突数是否为0,如果是算法结束,输出结果;否则,转iii;
iii. 判断迭代次数IterCnt是否等于链长N*N,如果等于,说明陷入局部极小,转i;否则,转iv;
iv. 随机选择两个皇后进行列号交换,更新冲突表CollTable,计算其冲突数是否减小,是则交换;否则恢复其冲突表CollTable,不进行交换;转ii。
d) 程序框图:
e) 效率表
皇后个数N |
10 |
100 |
1000 |
10000 |
1 |
不足0.001s |
约0.001s |
0.033s |
0.576s |
2 |
不足0.001s |
约0.006s |
0.027s |
0.543s |
3 |
不足0.001s |
约0.008s |
0.043s |
0.564s |
4 |
约0.001s |
约0.01s |
0.031s |
0.643s |
5 |
不足0.001s |
约0.003s |
0.028s |
0.634s |
6 |
不足0.001s |
约0.001s |
0.029s |
0.486s |
7 |
约0.001s |
约0.001s |
0.064s |
0.462s |
8 |
约0.001s |
约0.005s |
0.061s |
0.569s |
9 |
不足0.001s |
约0.006s |
0.019s |
0.631s |
10 |
不足0.001s |
约0.007s |
0.067s |
0.593s |
平均 |
不足0.001s |
约0.0048s |
0.0402s |
0.5701s |
#include <cstdio> #include <cstdlib> #include <iostream> #include <cstring> #include <iterator> #include <vector> #include <ctime> #define MAXN 30000 using namespace std; int N; int Pos[MAXN]; int CollTable[MAXN*2][2];//冲突表,分别记录正负对角线上的冲突数 void OutputSolution() { cout << "{"; for (int i = 0; i<N - 1; i++) cout << Pos[i] << ", "; cout << Pos[N-1] << "}" << endl; } void InitialPos() { for (int i = 0; i<N; i++) Pos[i] = i; for (int i = 0; i<N; i++) { int pos = rand() % N; swap(Pos[pos], Pos[N - pos - 1]); } memset(CollTable,0,sizeof(CollTable)); } int CalcCollision() { int CollCnt=0; for(int i=0;i<N;i++) { CollTable[Pos[i]+i][0]++; CollTable[Pos[i]-i+N-1][1]++; } for(int i=0;i<2*N-1;i++) { for(int j=0;j<2;j++) { if(CollTable[i][j]>1) CollCnt+=CollTable[i][j]-1; } } return CollCnt; } int NewCollision(int pos1,int pos2,int oc) { int currcollcnt=oc; //删去原有冲突 if(--CollTable[Pos[pos1]+pos1][0]>0) currcollcnt--; if(--CollTable[Pos[pos1]-pos1+N-1][1]>0) currcollcnt--; if(--CollTable[Pos[pos2]+pos2][0]>0) currcollcnt--; if(--CollTable[Pos[pos2]-pos2+N-1][1]>0) currcollcnt--; //计算现有冲突 if(++CollTable[Pos[pos1]+pos2][0]>1) currcollcnt++; if(++CollTable[Pos[pos1]-pos2+N-1][1]>1) currcollcnt++; if(++CollTable[Pos[pos2]+pos1][0]>1) currcollcnt++; if(++CollTable[Pos[pos2]-pos1+N-1][1]>1) currcollcnt++; return currcollcnt; } void RecoverCollision(int pos1,int pos2) { //恢复原有冲突 ++CollTable[Pos[pos1]+pos1][0]; ++CollTable[Pos[pos1]-pos1+N-1][1]; ++CollTable[Pos[pos2]+pos2][0]; ++CollTable[Pos[pos2]-pos2+N-1][1]; //删去现有冲突 --CollTable[Pos[pos1]+pos2][0]; --CollTable[Pos[pos1]-pos2+N-1][1]; --CollTable[Pos[pos2]+pos1][0]; --CollTable[Pos[pos2]-pos1+N-1][1]; } int SwapQueen(int OrigCollCnt) { int pos1 = rand() % N; int pos2 = rand() % N; while (pos1 == pos2) pos2 = rand() % N; int CurrCollCnt = NewCollision(pos1,pos2,OrigCollCnt); if (CurrCollCnt < OrigCollCnt) swap(Pos[pos1], Pos[pos2]); else { CurrCollCnt=OrigCollCnt; RecoverCollision(pos1,pos2); } return CurrCollCnt; } int Queen() { int Markov = N*N; clock_t start=clock(); while (1) { int IterCnt = 0; InitialPos(); int collcnt = CalcCollision(); if (collcnt == 0) { OutputSolution(); return 1; } while (IterCnt<Markov) { collcnt = SwapQueen(collcnt); if (collcnt == 0) { clock_t finish=clock(); OutputSolution(); double totaltime=(double)(finish - start)/CLOCKS_PER_SEC; cout<<"Speed:"<<totaltime<<"s"<<endl; return 1; } IterCnt++; } } return 0; } int main() { srand(time(NULL)); while (cin >> N) { memset(Pos, -1, sizeof(Pos)); if (N <= 0) cout << "N is not legal!" << endl; else if(N >= MAXN) cout << "For my PC's random range, I limit the N less than 30000" << endl; else if (N == 2 || N == 3) cout << "Cannot get a solution" << endl; else Queen(); } return 0; }