<think>我们分析代码,找出bug。以下是主要步骤和发现的bug:
1. 初始化部分:随机生成一个排列(每行一个皇后,列不重复),并初始化两个对角线的冲突数组(diag1_conflicts和diag2_conflicts)。这里看起来没问题。
2. 在迭代过程中,首先检查当前棋盘是否是一个解(即所有皇后都没有对角线冲突)。这里只检查了对角线冲突,因为列冲突由于初始化和后续操作(交换行)保证每行每列只有一个皇后,所以列冲突不会存在(注释掉了列冲突的代码)。所以检查部分正确。
3. 然后,我们尝试寻找一个可以交换的两行,使得交换后总冲突减少。注意,这里交换两行意味着交换两个皇后的列,这样列冲突仍然不会存在(因为还是每列一个皇后),所以只考虑对角线冲突。
4. 在寻找交换对时,代码使用两层循环遍历所有行对(row1和row2),对于每一对,计算交换前后的冲突变化。注意,这里计算冲突的方式是:
当前冲突:分别计算row1和row2两个皇后在当前位置的对角线冲突(注意每个对角线冲突计数包括了该皇后的贡献,所以要减1,因为当前正在考虑这个皇后)。
新冲突:交换两个皇后的列后,计算row1位置的新列(原本是row2的列)和row2位置的新列(原本是row1的列)的对角线冲突(同样要减1?)。
5. 但是,这里有一个重要的bug:在计算冲突时,我们使用的冲突数组是全局的,并且是当前状态下的。当我们计算新冲突时,我们并没有实际交换,所以应该直接计算如果交换后,这两个位置的新列所在对角线的当前冲突(注意,这个当前冲突包括其他皇后的贡献,也包括这两个皇后在交换前的位置贡献)。然而,由于我们只是计算假设交换,所以我们应该模拟交换后的冲突变化。
6. 实际上,代码中计算新冲突的方式是:
new_conflicts = diag1_conflicts[pos_diag1(row2, col1)] - 1 +
diag2_conflicts[pos_diag2(row2, col1, n)] - 1 +
diag1_conflicts[pos_diag1(row1, col2)] - 1 +
diag2_conflicts[pos_diag2(row1, col2, n)] - 1;
这里的问题在于,diag1_conflicts和diag2_conflicts数组是当前状态下的,即还没有交换。因此,当我们计算交换后row2位置(现在是col1)的对角线冲突时,这个冲突值包含了当前位于row1的皇后(col1)的贡献,同时也包含了当前位于row2的皇后(col2)的贡献。但实际上,交换后,row2位置上的皇后变成了col1,而row1位置上的皇后变成了col2。所以,在计算新冲突时,我们需要排除当前两个皇后对这两个对角线的贡献,然后加上交换后两个皇后在新位置上的贡献(但注意,其他皇后的贡献不变,所以只需调整这两个皇后的贡献即可)。
7. 然而,我们注意到,在计算新冲突时,我们直接用当前冲突数组的值减去1(即减去当前皇后的贡献?)是不对的。因为:
- 在交换前,对角线diag1_conflicts[pos_diag1(row2, col1)]的值包含了哪些皇后?它包含了所有位于该对角线的皇后,包括row1上的皇后(col1)和row2上的皇后(col2)吗?注意,位置(row2, col1)目前并没有皇后(因为当前row2上是col2),所以这个对角线的冲突数并不包括row2上的皇后(因为row2的皇后在col2,不在col1)。因此,这个值实际上包括了其他皇后的贡献以及row1上的皇后(col1)的贡献(因为row1,col1在这个对角线上)。
但是,当我们交换后,row2上的皇后变成了col1,那么该对角线(row2+col1)就会增加一个皇后(row2,col1),但同时row1上的皇后从col1变成了col2,所以该对角线会减少一个皇后(row1,col1)?不对,因为row1,col1原本就在这个对角线上,现在row1变成了col2,所以row1,col2不在这个对角线上。
8. 因此,实际上,我们需要重新考虑:计算交换后的冲突不能直接用当前的冲突数组,因为交换会影响四个对角线(两个皇后各自的两个对角线)的冲突计数。正确的做法是模拟交换,计算交换后这两个皇后的四个新对角线的冲突值(注意,冲突数组本身还没有改变,所以我们需要手动调整:即从当前冲突中减去两个皇后在旧位置对角线的贡献,再加上两个皇后在新位置对角线的贡献,然后得到新的冲突数组,但这样计算量较大)。
9. 另一种方法是:我们注意到,交换两个皇后((r1,c1)和(r2,c2))只会影响以下四个对角线:
- 对角线1: r1+c1 (移除皇后)
- 对角线2: r1-c1+n-1 (移除皇后)
- 对角线3: r2+c2 (移除皇后)
- 对角线4: r2-c2+n-1 (移除皇后)
- 对角线5: r1+c2 (添加皇后)
- 对角线6: r1-c2+n-1 (添加皇后)
- 对角线7: r2+c1 (添加皇后)
- 对角线8: r2-c1+n-1 (添加皇后)
那么,交换后,这两个皇后的冲突变化可以这样计算:
- 对于第一个皇后(从(r1,c1)移到(r1,c2)):它原本的对角线冲突为(diag1_conflicts[r1+c1]和diag2_conflicts[r1-c1+n-1]),新的对角线冲突为(diag1_conflicts[r1+c2]和diag2_conflicts[r1-c2+n-1])。
- 对于第二个皇后(从(r2,c2)移到(r2,c1)):它原本的对角线冲突为(diag1_conflicts[r2+c2]和diag2_conflicts[r2-c2+n-1]),新的对角线冲突为(diag1_conflicts[r2+c1]和diag2_conflicts[r2-c1+n-1])。
但是,这里有一个问题:在交换前,这两个皇后的位置已经在冲突数组中,所以当我们计算新位置的对角线冲突时,当前冲突数组的值包含了这两个皇后的贡献。因此,我们需要在计算新位置的对角线冲突时,先减去这两个皇后的贡献(因为交换后它们离开了原来的位置,但还没有加到新位置),然后再加上它们在新位置的贡献?然而,我们并没有实际修改冲突数组,所以不能直接使用当前冲突数组的值来代表新位置的冲突(因为新位置可能包含另一个皇后的贡献,而那个皇后将在交换后离开?实际上,交换只涉及这两个皇后,其他皇后的位置不变)。
10. 更准确的方法是:交换后,整个棋盘的冲突变化只与这两个皇后的位置变化有关。具体来说,这两个皇后从原来的位置移动到新的位置,那么:
总冲突变化 = [新位置1的冲突变化(注意:新位置1原本的冲突数不包括第一个皇后,但包括第二个皇后?)] + [新位置2的冲突变化] - [旧位置1的冲突变化] - [旧位置2的冲突变化]
但是,由于我们只关心这两个皇后的冲突变化,而且其他皇后的冲突不变,所以我们可以只计算这两个皇后的冲突变化。
然而,这里有一个陷阱:对角线冲突是全局的,所以两个皇后在交换前,它们的旧位置冲突计数包括了它们自己(因此我们减去1来得到其他皇后的冲突?),在交换后,新位置冲突计数也包括了它们自己(所以也需要减去1?)。实际上,在计算单个皇后的冲突时,我们通常计算该皇后所在对角线上除了自己以外的皇后数量(即冲突数=该对角线上皇后总数-1)。所以,对于每个皇后,其冲突贡献为(对角线冲突计数-1)。
11. 因此,当前两个皇后的总冲突为:
current_conflicts = (diag1_conflicts[r1+c1] - 1) + (diag2_conflicts[r1-c1+n-1] - 1) +
(diag1_conflicts[r2+c2] - 1) + (diag2_conflicts[r2-c2+n-1] - 1);
交换后,两个皇后在新位置的总冲突为:
new_conflicts = (diag1_conflicts[r1+c2] - 1) + (diag2_conflicts[r1-c2+n-1] - 1) +
(diag1_conflicts[r2+c1] - 1) + (diag2_conflicts[r2-c1+n-1] - 1);
但是,这里有一个问题:在交换前,新位置(r1,c2)上可能已经有一个皇后(即第二个皇后(r2,c2)吗?)?不,因为第二个皇后在(r2,c2),而(r1,c2)上当前是第一个皇后吗?也不是,因为第一个皇后在(r1,c1),所以(r1,c2)位置上当前是没有皇后的。同理,(r2,c1)也是没有皇后的。所以,在交换前,新位置的对角线冲突计数并不包括这两个皇后。因此,上面的new_conflicts计算应该是正确的。
12. 但是,我们再仔细考虑:交换前,位置(r1,c2)上可能没有皇后,但是该位置的对角线(r1+c2)和(r1-c2+n-1)上可能有其他皇后(除了这两个皇后以外的皇后)。同样,位置(r2,c1)上也没有皇后,其对角线上也可能有其他皇后。所以,交换后,这两个皇后加入到新位置的对角线,那么新位置对角线的冲突计数就是当前冲突数组的值(因为当前冲突数组还没有这两个皇后在新位置的贡献)加上1(因为即将加入)?不对,当前冲突数组的值已经包括了所有皇后的贡献,包括这两个皇后在旧位置的贡献,但不包括它们在新位置的贡献(因为还没有移动)。所以,当前冲突数组的值对于新位置来说,恰好就是交换前该对角线上除了即将加入的这个皇后以外的皇后数量。因此,交换后,该皇后在新位置的冲突值就等于当前冲突数组的值(因为当前冲突数组的值就是该对角线上其他皇后的数量)。所以,我们可以直接使用当前冲突数组的值来表示新位置的对角线冲突(即该皇后加入后,它的冲突数就是当前数组的值,因为加入后该对角线的总数=原数组值+1,所以冲突数=原数组值)。
13. 因此,交换后,第一个皇后(在(r1,c2))的冲突数为:diag1_conflicts[r1+c2](因为加入后,该对角线上有diag1_conflicts[r1+c2]+1个皇后,所以冲突数为diag1_conflicts[r1+c2])?不对,冲突数的定义应该是该对角线上除了自己以外的皇后数。所以,如果交换后,该对角线上有k个皇后(包括自己),那么冲突数就是k-1。而交换前,该对角线上有diag1_conflicts[r1+c2]个皇后(注意,这个值不包括自己,因为自己还没有加入)。所以,交换后,该对角线上有diag1_conflicts[r1+c2] + 1个皇后(因为自己加入),因此冲突数为(diag1_conflicts[r1+c2] + 1 - 1) = diag1_conflicts[r1+c2]。同理,第二个皇后在(r2,c1)的冲突数也是diag1_conflicts[r2+c1]。
14. 但是,这里有一个关键:在交换前,第一个皇后离开位置(r1,c1),那么对角线r1+c1上的皇后数会减少1,同样,对角线r1-c1+n-1也会减少1。同时,第二个皇后离开(r2,c2),对角线r2+c2和r2-c2+n-1也会减少1。而加入新位置后,对角线r1+c2和r1-c2+n-1会增加1,对角线r2+c1和r2-c1+n-1会增加1。
因此,如果我们用当前冲突数组来计算交换后两个皇后的冲突,那么:
新位置第一个皇后的冲突:对角线r1+c2的冲突值(当前值)就是交换后该对角线上除了自己以外的皇后数(因为自己还没有加入,当前值就是其他皇后的数量)。所以,交换后第一个皇后在新位置的冲突数就是当前值(即diag1_conflicts[r1+c2])。同理,第二个皇后的冲突数就是diag1_conflicts[r2+c1](注意,这里不对,因为第二个皇后加入的位置是(r2,c1),所以它的第二个对角线是r2-c1+n-1,因此冲突数应该包括两个对角线:diag1_conflicts[r2+c1]和diag2_conflicts[r2-c1+n-1])。
因此,交换后两个皇后的总冲突数应该是:
new_conflicts = diag1_conflicts[r1+c2] + diag2_conflicts[r1-c2+n-1] +
diag1_conflicts[r2+c1] + diag2_conflicts[r2-c1+n-1];
而交换前两个皇后的总冲突数(注意,交换前两个皇后的冲突数计算中,每个冲突值都是当前冲突数组的值减去1(因为当前冲突数组的值包括了自己,所以要减1得到其他皇后的数量)):
current_conflicts = (diag1_conflicts[r1+c1] - 1) + (diag2_conflicts[r1-c1+n-1] - 1) +
(diag1_conflicts[r2+c2] - 1) + (diag2_conflicts[r2-c2+n-1] - 1);
所以,我们应该比较的是new_conflicts和current_conflicts,如果new_conflicts < current_conflicts,则交换。
15. 但是,在代码中,计算new_conflicts时却减去了1:
new_conflicts = diag1_conflicts[pos_diag1(row2, col1)] - 1 + ...
这是错误的。因为根据上面的分析,交换后新位置的冲突数就是当前冲突数组的值(没有加入这个皇后时的值,即其他皇后的数量),所以不应该减1。而交换前,两个皇后的冲突数计算中减去1是正确的(因为当前冲突数组的值包括了它们自己)。
16. 因此,第一个bug:计算new_conflicts时不应该减1。
17. 第二个bug:在交换后更新冲突数组时,代码这样写:
diag1_conflicts[pos_diag1(row1_to_change, queens[row1_to_change])]--;
diag2_conflicts[pos_diag2(row1_to_change, queens[row1_to_change], n)]--;
diag1_conflicts[pos_diag1(row2_to_change, queens[row2_to_change])]--;
diag2_conflicts[pos_diag2(row2_to_change, queens[row2_to_change], n)]--;
swap(queens[row1_to_change], queens[row2_to_change]);
diag1_conflicts[pos_diag1(row1_to_change, queens[row1_to_change])]++;
diag2_conflicts[pos_diag2(row1_to_change, queens[row1_to_change], n)]++;
diag1_conflicts[pos_diag1(row2_to_change, queens[row2_to_change])]++;
diag2_conflicts[pos_diag2(row2_to_change, queens[row2_to_change], n)]++;
这里的问题是:在swap之前,queens数组中的row1_to_change和row2_to_change位置的值分别是col1和col2。然后我们先从冲突数组中减去这两个位置皇后的贡献(即对角线row1+col1, row1-col1+n-1, row2+col2, row2-col2+n-1)。
然后交换,交换后row1_to_change位置的值变成col2,row2_to_change位置的值变成col1。然后我们再加上新位置(row1+col2, row1-col2+n-1, row2+col1, row2-col1+n-1)的贡献。
这个过程看起来正确。但是,注意,在计算新位置的对角线时,我们使用的是交换后的列值,所以正确。
18. 第三个bug:在寻找最优交换对时,代码使用了两层循环,但是内层循环中,对于每一对(row1, row2)都计算交换后的冲突变化,一旦发现一个改进(new_conflicts < current_conflicts)就跳出内层循环,并且记录这对行,然后跳出外层循环(通过isFound和break)。这样会导致我们只找到第一个改进的交换对,而不是最好的交换对。虽然爬山法不一定要求最好的改进,只要是改进就可以(最陡上升爬山法要求最好改进,但这里实现的是最陡上升吗?)。但是,这里代码写的是“第一个改进”,而通常我们希望找到一个改进就交换(因为计算所有交换对的开销很大)。所以这个策略可以接受(称为“首次改进爬山法”)。所以这个不算bug。
19. 但是,我们注意到,在循环中,我们遍历row1,然后对于每个row1,遍历所有的row2(除了row1自己)。然而,当我们找到一个改进的交换对(row1和row2)后,我们跳出内层循环,然后跳出外层循环(通过break)。这样就会导致我们只检查了第一个row1的所有row2,然后一旦找到就停止,而不会继续检查后面的row1。这可能会导致我们错过更好的交换对。但是,由于我们采用首次改进,所以这样也可以接受。不过,通常我们会随机顺序遍历或者按顺序遍历找到第一个改进。所以这里也可以。
20. 第四个bug:在计算current_conflicts时,代码为:
int current_conflicts = diag1_conflicts[pos_diag1(row1, col1)] - 1 +
diag2_conflicts[pos_diag2(row1, col1, n)] - 1 +
diag1_conflicts[pos_diag1(row2, col2)] - 1 +
diag2_conflicts[pos_diag2(row2, col2, n)] - 1;
这里,最后一项diag2_conflicts[pos_diag2(row2, col2, n)]减了两次1?不对,我们看:每个皇后两个对角线,所以总共四项,每项都减1(因为计算冲突数时要减去自己)。所以这里减了四次1,正确。
21. 但是,计算new_conflicts时,代码为(修改前):
int new_conflicts = diag1_conflicts[pos_diag1(row2, col1)] - 1 +
diag2_conflicts[pos_diag2(row2, col1, n)] - 1 +
diag1_conflicts[pos_diag1(row1, col2)] - 1 +
diag2_conflicts[pos_diag2(row1, col2, n)] - 1;
根据之前的分析,这里不应该减1。所以应该去掉减1。
22. 第五个bug:在交换后,我们更新冲突数组,但是注意,在交换前,我们计算冲突数组时,使用的列值是交换前的,所以正确。但是,在交换后,我们更新冲突数组时,使用的列值已经交换了,所以正确。
23. 第六个bug:在随机重启部分,代码中注释掉了原来重新初始化的代码,改为使用向量洗牌重新初始化。但是,在重新初始化后,冲突数组应该重置(因为重新初始化了皇后的位置)。代码中确实重置了冲突数组:首先用calloc分配冲突数组(在函数开头),然后在每一次随机重启时,先释放之前的冲突数组?不对,没有释放,而是直接重新使用之前分配的冲突数组。在随机重启前,冲突数组还保留着上一次的状态,所以必须先清零。
但是在随机重启的代码中,我们看到:
// 随机重启
vector<int> queens_position;
for (int i = 0; i < n; i++) {
queens_position.push_back(i);
}
std::mt19937 gen(std::random_device{}());
shuffle(queens_position.begin(), queens_position.end(), gen);
for (int i = 0; i < n; i++) {
queens[i] = queens_position[i];
diag1_conflicts[i + queens[i]]++;
diag2_conflicts[i - queens[i] + n - 1]++;
}
这里,冲突数组diag1_conflicts和diag2_conflicts并没有清零,而是直接在原有基础上增加新的皇后位置。这会导致冲突数组的值叠加(原来状态的值还在)。所以,在重新初始化之前,必须先将冲突数组清零。
24. 因此,在随机重启部分,应该在重新初始化皇后位置之前,先将diag1_conflicts和diag2_conflicts数组清零(或者重新分配并初始化为0)。但是,代码中并没有清零操作。
25. 解决方法:在随机重启之前,用memset将冲突数组清零:
memset(diag1_conflicts, 0, 2 * n * sizeof(int));
memset(diag2_conflicts, 0, 2 * n * sizeof(int));
或者,由于我们重新分配了冲突数组(在函数开头分配),那么我们可以不重新分配,而是直接清零。注意,在函数开头,冲突数组是用calloc分配的(初始为0),然后每次随机重启前,需要再次清零。
26. 但是,在第一次初始化(函数开头)时,冲突数组已经分配并初始化为0。然后在随机重启时,我们并没有清零,而是直接在上面增加。所以第一次初始化后,冲突数组是正确的。但随机重启时,没有清零,所以错误。
27. 因此,在随机重启的代码段中,在重新初始化皇后位置之前,应该加上:
memset(diag1_conflicts, 0, 2 * n * sizeof(int));
memset(diag2_conflicts, 0, 2 * n * sizeof(int));
28. 另外,在第一次初始化时,我们也是用calloc分配了冲突数组(初始为0),然后初始化皇后位置时增加冲突计数。所以随机重启时也应该先清零。
29. 第七个bug:在交换操作中,我们更新冲突数组时,使用的对角线位置计算函数是pos_diag1和pos_diag2,但是注意,在交换前,我们减去冲突时,使用的列值是交换前的列值(正确),然后交换后,我们加上冲突时,使用的列值是交换后的列值(正确)。所以这部分正确。
30. 总结主要bug:
- 计算新冲突(new_conflicts)时,不应该减1(去掉减1)。
- 随机重启时,没有清空冲突数组,导致冲突数组叠加计数。
31. 此外,还有一个小问题:在交换后打印日志时,使用了printf,但没有换行(可能导致输出缓冲区未刷新)。建议在打印日志时加上换行符。
32. 另外,在寻找交换对时,内层循环中row2的起始位置:当前是从0开始,然后跳过row1。我们可以优化为row2从row1+1开始,避免重复检查(因为交换(row1,row2)和(row2,row1)是一样的)。但这不是bug,只是效率。
33. 修改步骤:
1. 在计算new_conflicts时,去掉减1。
2. 在随机重启的代码段中,在重新初始化皇后位置之前,将两个冲突数组清零。
34. 但是,注意清零操作:由于冲突数组的大小是2*n(diag1_conflicts的大小是2*n,因为row+col的范围是0到2*n-2;diag2_conflicts的大小也是2*n,因为row-col+n-1的范围是0到2*n-2)。所以清零大小为2*n个int。
35. 修改代码:
// 在随机重启代码段中,添加清零
memset(diag1_conflicts, 0, 2 * n * sizeof(int));
memset(diag2_conflicts, 0, 2 * n * sizeof(int));
36. 注意:在第一次初始化时,我们也是用calloc分配,所以初始为零。随机重启时,我们也要清零。
37. 另外,在交换后打印日志,加上换行符:
printf("迭代 %d: %d 与 %d行交换\n", iter+1, row1_to_change, row2_to_change);
38. 但还有一个问题:在随机重启时,我们使用的随机数生成器每次重启都会创建新的queens_position向量,然后洗牌,这是正确的。但是,在交换操作中,我们交换两个皇后的列,然后更新冲突数组,这是正确的。
39. 最后,我们验证一下:在随机重启时,我们清零冲突数组,然后根据新的皇后位置重新计算冲突数组(在循环中执行diag1_conflicts[i+queens[i]]++等)。所以正确。
40. 还有一个潜在问题:在交换操作中,我们更新冲突数组时,使用的pos_diag2函数调用时,第三个参数是n,这是正确的。
41. 但是,在交换操作中,交换两个皇后的列,可能会影响到其他皇后的冲突吗?不会,因为交换只改变两个皇后的列,其他皇后的位置不变。所以冲突数组的更新仅限于这两个皇后的四个对角线。
42. 因此,我们修改两处。
43. 代码中还有一个注释掉的列冲突(col_conflicts),因为我们使用排列,所以列冲突不存在,因此没有使用。所以没问题。
44. 但是,在检查是否找到解的函数中,我们只检查了对角线冲突,没有列冲突,正确。
45. 因此,我们修改代码并测试。
由于我们无法运行,所以修改后需要测试。但题目要求找出bug,所以我们指出这两个bug。
修改后的代码片段:
在计算new_conflicts的地方:
```c
int new_conflicts = diag1_conflicts[pos_diag1(row2, col1)] +
diag2_conflicts[pos_diag2(row2, col1, n)] +
diag1_conflicts[pos_diag1(row1, col2)] +
diag2_conflicts[pos_diag2(row1, col2, n)];
```
在随机重启的地方:
```c
// 随机重启
// 先清零冲突数组
memset(diag1_conflicts, 0, 2 * n * sizeof(int));
memset(diag2_conflicts, 0, 2 * n * sizeof(int));
vector<int> queens_position;
for (int i = 0; i < n; i++)
{
queens_position.push_back(i);
}
std::mt19937 gen(std::random_device{}());
shuffle(queens_position.begin(), queens_position.end(), gen);
for (int i = 0; i < n; i++)
{
queens[i] = queens_position[i];
// col_conflicts[queens[i]]++;
diag1_conflicts[i + queens[i]]++;
diag2_conflicts[i - queens[i] + n - 1]++;
}
```
另外,在交换操作后打印日志的地方,加上换行符:
```c
printf("迭代 %d: %d 与 %d行交换\n", iter + 1, row1_to_change, row2_to_change);
```
注意:在第一次初始化冲突数组时,是用calloc分配的(初始为0),所以随机重启时清零即可。
但是,在函数开头分配冲突数组后,在随机重启时并没有重新分配,所以我们用memset清零。
46. 还有一个bug:在随机重启时,我们使用std::random_device{}来生成随机数,但是这里可能会抛出异常(如果随机设备不可用)。不过,在大多数系统上可用,所以不是主要bug。
47. 最后,由于我们修改了new_conflicts的计算,那么判断条件(new_conflicts < current_conflicts)的含义是:交换后两个皇后的总冲突数小于交换前的总冲突数,则交换。这是正确的,因为我们的目标是最小化冲突。
48. 另外,注意交换前两个皇后的总冲突数(current_conflicts)是用四个冲突值(每个对角线冲突值减去1)相加,而交换后(new_conflicts)是四个冲突值(没有减1)相加。为什么可以这样比较?因为:
current_conflicts = (d1-1) + (d2-1) + (d3-1) + (d4-1) = d1+d2+d3+d4 - 4
new_conflicts = d5 + d6 + d7 + d8
所以,如果 new_conflicts < current_conflicts,即 d5+d6+d7+d8 < d1+d2+d3+d4 - 4
这等价于:d5+d6+d7+d8 + 4 < d1+d2+d3+d4
这个条件是否合理?实际上,我们并不关心这个等式,我们关心的是交换后总冲突(整个棋盘)的变化。但是,整个棋盘的总冲突变化是多少?
交换前,两个皇后在旧位置,每个皇后有自己的两个对角线,每个对角线的冲突计数(包括其他皇后)为d1(=对角线上皇后总数),那么每个皇后的冲突数(即该对角线上其他皇后的数量)为d1-1。所以两个皇后的总冲突(仅这两个皇后)为(d1-1)+(d2-1)+(d3-1)+(d4-1) = d1+d2+d3+d4-4。
交换后,两个皇后在新位置,每个对角线的冲突计数(其他皇后的数量)为d5,d6,d7,d8(注意,这里d5,d6,d7,d8是交换前在新位置的对角线上已有的皇后数量)。所以两个皇后在新位置的总冲突为d5+d6+d7+d8。
那么,交换后整个棋盘的总冲突变化量是多少?
总冲突变化 = [新位置两个皇后的总冲突] - [旧位置两个皇后的总冲突]
= (d5+d6+d7+d8) - (d1+d2+d3+d4-4)
= (d5+d6+d7+d8) - (d1+d2+d3+d4) + 4
但是,这个变化量还包括了这两个皇后移动对全局冲突的影响(因为其他皇后的冲突数也会变化)。然而,由于其他皇后的位置不变,所以其他皇后的冲突数变化只与这两个皇后移动所涉及的对角线有关。具体来说,这两个皇后离开旧位置,那么旧位置所在对角线的冲突计数会减少(因此其他皇后的冲突数也会减少,因为每个皇后的冲突数等于所在对角线的皇后总数-1);同样,它们加入新位置,新位置所在对角线的冲突计数增加,其他皇后的冲突数也会增加。所以,整个棋盘的总冲突变化并不能仅由这两个皇后的冲突变化来代表。
49. 因此,实际上,我们之前的方法(仅计算两个皇后的冲突变化)并不能代表整个棋盘冲突的变化。正确的方法是计算整个棋盘的总冲突变化,但那样开销太大。
50. 另一种方法是:我们计算交换前后,八个对角线(四个旧对角线和四个新对角线)上的冲突变化。但是,这八个对角线可能会有重叠(比如,两个皇后共享对角线?),所以计算比较复杂。
51. 事实上,我们通常使用交换两个皇后后的全局冲突数变化来评估,但这样需要重新计算整个棋盘的冲突?不可行。
52. 那么,我们退而求其次:我们只考虑这两个皇后的冲突变化,并假设其他皇后的冲突变化可以抵消?事实上,其他皇后的冲突变化很小,我们近似认为这两个皇后的冲突变化代表了全局冲突变化。这个近似在大多数情况下是可行的,因为移动两个皇后主要影响它们自己的冲突。
53. 或者,我们可以精确计算:交换两个皇后后,整个棋盘的冲突变化等于:
[所有涉及改变的八个对角线上皇后的冲突变化] 减去 [其他对角线的冲突变化(不变)]
具体来说,交换操作只影响这八个对角线(两个皇后的四个对角线,由于交换,所以旧位置对角线减少,新位置对角线增加)。因此,我们可以计算这八个对角线的冲突变化(但注意,每条对角线的冲突计数变化,然后每个对角线上的所有皇后的冲突数都会变化)。这很复杂。
54. 因此,我们通常使用两个皇后的冲突变化来近似。所以,尽管不精确,但算法仍然可以工作。所以,我们保留这种近似。
55. 所以,我们按照修改后的代码运行。
综上所述,我们修改两个bug:
1. 计算new_conflicts时不减1。
2. 随机重启前清零冲突数组。
下面给出修改后的完整代码(只修改了这两处和一个printf):
</think>以下代码存在两个主要bug及其修复方案:
### Bug 1: 新位置冲突计算错误
**问题描述**:
在计算交换后冲突时错误地减去了1:
```c
int new_conflicts = diag1_conflicts[pos_diag1(row2, col1)] - 1 +
diag2_conflicts[pos_diag2(row2, col1, n)] - 1 +
diag1_conflicts[pos_diag1(row1, col2)] - 1 +
diag2_conflicts[pos_diag2(row1, col2, n)] - 1;
```
**错误原因**:
交换后新位置的冲突值应直接使用当前冲突数组的值(表示该对角线现有皇后数量),**不应减1**。减1操作会导致冲突计算错误。
**修复方案**:
移除所有减1操作:
```c
int new_conflicts = diag1_conflicts[pos_diag1(row2, col1)] +
diag2_conflicts[pos_diag2(row2, col1, n)] +
diag1_conflicts[pos_diag1(row1, col2)] +
diag2_conflicts[pos_diag2(row1, col2, n)];
```
### Bug 2: 随机重启时冲突数组未清零
**问题描述**:
随机重启时未清除之前的冲突数组:
```c
// 随机重启部分代码
vector<int> queens_position;
// ... 初始化新位置 ...
for (int i = 0; i < n; i++) {
queens[i] = queens_position[i];
diag1_conflicts[i + queens[i]]++; // 直接叠加到旧冲突值上
diag2_conflicts[i - queens[i] + n - 1]++;
}
```
**错误原因**:
冲突数组未重置导致新冲突值错误叠加在旧值上,破坏冲突计数准确性。
**修复方案**:
在重新初始化前清空冲突数组:
```c
// 随机重启前添加清零操作
memset(diag1_conflicts, 0, 2 * n * sizeof(int));
memset(diag2_conflicts, 0, 2 * n * sizeof(int));
```
### 完整修复代码
```c
// ... 其他代码保持不变 ...
for (int iter = 0; iter < max_iterations; iter++) {
// ... 检查解 ...
for (int row1 = 0; row1 < n; row1++) {
int col1 = queens[row1];
bool isFound = false;
for (int row2 = 0; row2 < n; row2++) {
if (row1 == row2) continue;
int col2 = queens[row2];
// 修复Bug 1:移除new_conflicts的减1操作
int current_conflicts = diag1_conflicts[pos_diag1(row1, col1)] - 1 +
diag2_conflicts[pos_diag2(row1, col1, n)] - 1 +
diag1_conflicts[pos_diag1(row2, col2)] - 1 +
diag2_conflicts[pos_diag2(row2, col2, n)] - 1;
int new_conflicts = diag1_conflicts[pos_diag1(row2, col1)] + // 去掉 -1
diag2_conflicts[pos_diag2(row2, col1, n)] + // 去掉 -1
diag1_conflicts[pos_diag1(row1, col2)] + // 去掉 -1
diag2_conflicts[pos_diag2(row1, col2, n)]; // 去掉 -1
if (new_conflicts < current_conflicts) {
// ... 记录交换行 ...
}
}
}
if (row1_to_change == -1) {
// 随机重启
// 修复Bug 2:重置冲突数组
memset(diag1_conflicts, 0, 2 * n * sizeof(int));
memset(diag2_conflicts, 0, 2 * n * sizeof(int));
vector<int> queens_position;
// ... 重新初始化皇后位置 ...
}
// ... 其他代码 ...
}
// ... 后续代码 ...
```
### 修复原理说明
1. **冲突计算逻辑**
- `current_conflicts`:计算当前位置冲突时减1(排除自身)
- `new_conflicts`:新位置冲突直接使用当前值(尚未放置皇后,值即其他皇后数)
2. **冲突数组重置**
随机重启时需清除历史冲突数据,避免新/旧数据叠加污染计数
### 额外优化建议
1. 在交换日志后添加换行符:`printf("...交换\n");`
2. 使用更高效的行对遍历(如只检查row2 > row1避免重复计算)
3. 添加对角线索引的范围检查(防越界访问)