3、N后问题(子集树)
在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。
当且仅当 n = 1 或 n ≥ 4 时问题有解。
思路参考文章
代码参考文章
代码思路:
一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要当前列是否合法,如果合法,则将皇后放置在当前位置,并进行递归t+1,直到所有位置都不合法时,回溯到上一步,使上一步的皇后向右移。每行都摆满皇后时(t>n),则产生了一种解法,即可输出当前的解。
判断合法:
当前将要摆放‘Q’的位置和其他已摆放‘Q’的位置不能在同一列,且不能在同一条45度斜线或135度斜线上。这里判断是否在同一条斜线上可通过当前将要摆放‘Q’的位置和其他已摆放‘Q’的位置横坐标之差和纵坐标之差的绝对值是否相等来判断
a[i]=k
表示第i行的皇后放在a[i]列,这样设计的棋盘所有皇后必定不在同一行。- 所以现在考虑不在一列的条件:由于a数组中记录的是皇后的所在列,假设现在运行到t行,我们让i从1到t-1遍历查找
a[i]==a[t]
,如果有存在一个与a[t]相等,那么当前的摆放与之前处于同一列了。 - 对角线则用
|row-i|!=|a[row]-a[i]|
(行差的绝对值≠列差的绝对值)。include <math.h>后用abs()可以求得正数的绝对值。
# include<bits/stdc++.h>
using namespace std;
int a[100]; //记录i行皇后的列号
int sum;//记录接解的个数
int n;
bool is_conflict(int t)//查找t行的a[t]与之前的列是否冲突
{
for (int i = 1; i < t; i++ )
if ( a[i]==a[t] || abs(t-i)==abs(a[t]-a[i]) )
return false;
return true;
}
void BackTrace(int t)
{
if (t > n) //求出一种解, count+1
{
sum++;
//输出最后最终皇后的位置
printf("第%d种方案:",sum);
for(int i=1; i<=n; i++)
cout<<a[i]<<" ";
cout<<endl;
}
else
{
for (int i = 1; i <= n; i++)//遍历每一列
{
a[t] = i;
if (is_conflict(t))//查找之前的列和当前的a[t]冲突
BackTrace(t + 1);
//查找下一行,如有不符合的上一行右移
}
}
}
int main()
{
printf("请输入皇后的个数n:");
cin>>n;
BackTrace(1);
return 0;
}
/*
输入:
4
输出:
第1种方案:2 4 1 3
第2种方案:3 1 4 2
*/
4、旅行售货员问题(排序树)
问题: 某售货员要到若干城市去推销商品,已知各城市之间的路程(或旅费)。他要选定一条从驻地出发,经过每个城市一次,最后回到驻地的路线,使总的路程(或总旅费)最小
如下图:1,2,3,4 四个城市及其路线费用图,任意两个城市之间不一定都有路可达。
全排列的理解如下:
全排列的代码见文章链接
原代码的参考文章
代码的主要思想和全排列问题相同,只是添加了计算最优权值的问题。
我们用一个二维数组g[100][100]定义这个无向有权图,如果两点之间没有边相连那么他们的权是0。通过x[i]记录全排列的结果,将全排列的相邻两点带入g则可得到这两点间的权。
同时添加了上界函数排除不是最优的解。和最优装载问题一样,如果发现得不到最优的结果,需要回溯到上一个点,同时减去当前的权,并将x的元素再交换回来以恢复之前的状态。
# include<bits/stdc++.h>
using namespace std;
int g[100][100];//无向图
int x[100],bestx[100],cw,bestw;
int n,m;//总点数、总边数
//第i步城市,最优路径,当前权重,最优权重
void Swap(int &a, int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
void BackTrace(int t)
{
if(t==n)
{
//若上一步能到达最后一点,最后一点和起点有边,且邮费更少则更新,要考虑第一次的bestw为0
if(g[x[t - 1]][x[t]] !=0&&g[x[t]][1] != 0&&(cw + g[x[t - 1]][x[t]] + g[x[t]][1] < bestw||bestw == 0))
{
for (int i = 0; i <= n; i++)
bestx[i] = x[i];
bestw = cw + g[x[t - 1]][x[t]] + g[x[t]][1];
}
return;
}
//从t开始找下一个路径
for (int i = t; i <= n; i++)//x[t]先和当前换再与后面的依次交换
{
//如果t-1和t之间有边,且有更小的邮费则往下找
if (g[x[t - 1]][x[i]] != 0 && (cw + g[x[t - 1]][x[i]] < bestw|| bestw == 0))
{
Swap(x[t], x[i]);
cw += g[x[t - 1]][x[t]];
BackTrace(t + 1);
cw -= g[x[t - 1]][x[t]];//恢复原态
Swap(x[t], x[i]);
}
}
}
int main()
{
printf("请输入要经过的城市数n,总边数m:");
cin>>n>>m;
printf("请输入点i到点j的边权重w:\n");
for(int i=1; i<=m; i++)
{
int x,y,w;
cin>>x>>y>>w;
g[y][x]=g[x][y]=w;
}
for(int i=1; i<=n; i++) //初始化城市号
x[i]=i;
//已经确定了起点为城市1,从城市1-2开始
BackTrace(2);
printf("最小费用为:%d\n",bestw);
printf("路径如下:");
for(int i=1; i<=n; i++)
{
cout<<bestx[i]<<' ';
}
return 0;
}
/*
输入
4 6
1 2 30
1 3 6
1 4 4
2 3 5
2 4 10
3 4 20
输出:
最小费用为:25
路径如下:1 3 2 4
*/
5、符号三角形问题(子集树)
补充上图的几个规律:
(1)上图中三角形是居中对齐的,但在二维数组中的存储是左对齐的形式a[i][j]。
(2)当第一行有t个符号时,三角形有t行,且每行的符号数依次递减1个。第j行的列从t-j+1开始。
更多点击参考文章
“+”“+”相遇下面的为“+”、“+”“-”相遇下面的为“-”,“-”“-”相遇下面的为“+”,这个运算规则可以用异或来表示,相同的符号异或得0,不同的符号异或得1,刚好表示出“+”、“-”的两种状态。字符不容易表示所以用1来表示“-”,0表示“+”。
假设当前为a[1][t],要计算他下面所有的三角形,则需要从第j行到t行结束,对该行的每一列求解a[j][t-j+1]=a[j][t-j+1]=a[j-1][t-j+1]^a[j-1][t-j+2];
(当前=正上异或右上)
对于我们的问题可以简化为求解所有的可能三角形,减去“+”≠“-”的三角形。也就是将“-”或“+”的数量>总符号数的一半的情况去掉。由于用1表示“-”,所以记录“-”的数量更加方便。由于我们只知道第一行的符号数n,所以利用等差数列求和公式得到总的符号数为n*(n+1)/2,一半half为n*(n-1)/4。
代码:
# include<bits/stdc++.h>
using namespace std;
int n,num1,sum;//n记录第一行的符号个数,num1记录减号的个数
int a[100][100];//记录符号表
int half;//half表示当前所有符号数的一半
void BackTrace(int t)
{
if((num1>half)||(t*(t-1)/2-num1>half))
return;
if(t>n)//达到末尾,得出一种解法
{
printf("num1=%d,half=%d\n",num1,half);
sum++;//解的数量
for(int i=1;i<=n;i++)//打印符号三角形
{
for(int k=1;k<i;k++)
cout<<" ";
for(int j=1;j<=n;j++)
{
if(a[i][j]==0&&j<=n-i+1)
cout<<"+ ";
else if(a[i][j]==1&&j<=n-i+1)
cout<<"- ";
else
cout<<" ";
}
cout<<endl;
}
cout<<endl;
}
else
{
for(int i=0;i<=1;i++)//遍历第一行的所有可能
{
a[1][t]=i;
num1+=i;
for(int j=2;j<=t;j++)//从第2行到第t行遍历所有可能
{
a[j][t-j+1]=a[j-1][t-j+1]^a[j-1][t-j+2];
num1+=a[j][t-j+1];//是1则加上,是0加上也不影响
}
BackTrace(t+1);//进入下一行
for(int j=2;j<=t;j++)//回溯后num1要还原
num1-=a[j][t-j+1];
num1-=i;
}
}
}
int main()
{
printf("请输入第一行的符号数n:");
cin>>n;
half=(n*(n+1))/4;
BackTrace(1);
cout<<sum;
return 0;
}
/*
输入:
3
输出:
num1=3,half=3
+ + -
+ -
-
num1=3,half=3
+ - +
- -
+
num1=3,half=3
- + +
- +
-
num1=3,half=3
- - -
+ +
+
4
*/