虽然说全排列好像很简单,但真的当用程序来生成时还一时不知道怎么办,但是我觉得穷举法是很多算法的基础,比如回溯法,排序等,我会以旅行商问题来说明全排列,只需一次就会了。
1.开始
回溯法的本质也是搜索,只是加上了约束条件,使搜索的次数减少许多,我们把约束条件和目标函数的边界称为剪枝策略。
对于旅行商这个问题,说的是一个商人想从一个城市出发,不重复地走遍所有城市然后回到起点,每个城市间有不同的旅行费用,求花最少的钱走完?
典型的一个图的存储结构,如下例子:
我采用简单的二维矩阵存储结构,接下来:
我会给出穷举的算法,然后加上约束条件,最后进行对比。
穷举法
穷举次序列的本质是全排列,采用递归生成,打印全过程:
仔细的看代码就好,注释很详细,全排列的精髓就在那了:
#include <iostream>
using namespace std;
int *x;//城市编号数组
int *bestx;//路线
int w = 0;//过渡变量
int bestw = INT32_MAX;//最优的费用
void Tsp(int **a, int n, int s);
int main() {
int **a;//a[i][j]表示从第i个城市到第j个的费用
int n;//城市个数
cin >> n;
x = new int[n];
for (int i = 0; i < n; i++) {
x[i] = i;//表示第i个城市编号,0到n-1
}
bestx = new int[n];
a = new int*[n];
for (int i = 0; i < n; i++) {
a[i] = new int[n];
}
//输入费用矩阵表示图
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> a[i][j];
}
}
Tsp(a, n, 1);//传1表示只搜从0开始的全排,传0表示所有全排
cout << "最优的是:" << bestw << endl;
for (int i = 0; i < n; i++) {
delete a[i];
}
delete[]a;
delete[]x;
delete[]bestx;
return 0;
}
void Tsp(int **a, int n, int s) {
if (s == n) {
w = 0;//清零
cout << "bestx: ";
for (int i = 0; i < n; i++) {
bestx[i] = x[i];
cout << bestx[i] << " ";
if (i < n - 1) {
w += a[x[i]][x[i + 1]];
}
else {
w += a[x[i]][x[0]];//回到起点的费用
}
}
cout << "w:" << w << endl;
if (bestw > w) {
bestw = w;//更新最优值
}
}
else {
for (int i = s; i < n; i++) {
//为什么要交换呢?因为要将x[s]作为起点进行往下搜索
int t = x[i]; x[i] = x[s]; x[s] = t;
Tsp(a, n, s + 1);
t = x[i]; x[i] = x[s]; x[s] = t;
}
}
}
运行结果如下:
5
0 3 3 2 6
3 0 7 3 2
3 7 0 2 5
2 3 2 0 3
6 2 5 3 0
bestx: 0 1 2 3 4 w:21
bestx: 0 1 2 4 3 w:20
bestx: 0 1 3 2 4 w:19
bestx: 0 1 3 4 2 w:17
bestx: 0 1 4 3 2 w:13
bestx: 0 1 4 2 3 w:14
bestx: 0 2 1 3 4 w:22
bestx: 0 2 1 4 3 w:17
bestx: 0 2 3 1 4 w:16
bestx: 0 2 3 4 1 w:13
bestx: 0 2 4 3 1 w:17
bestx: 0 2 4 1 3 w:15
bestx: 0 3 2 1 4 w:19
bestx: 0 3 2 4 1 w:14
bestx: 0 3 1 2 4 w:23
bestx: 0 3 1 4 2 w:15
bestx: 0 3 4 1 2 w:17
bestx: 0 3 4 2 1 w:20
bestx: 0 4 2 3 1 w:19
bestx: 0 4 2 1 3 w:23
bestx: 0 4 3 2 1 w:21
bestx: 0 4 3 1 2 w:22
bestx: 0 4 1 3 2 w:16
bestx: 0 4 1 2 3 w:19
最优的是:13
请按任意键继续. . .
回溯法
现在,我们加上约束条件:
#include "stdafx.h"
#include <iostream>
using namespace std;
int *x;//城市编号数组
int *bestx;//最好的路线
int bestw = 0;//最优的费用
int cc = 0;//过渡变量cost
void Tsp(int **a, int n, int s);
int main() {
int **a;//a[i][j]表示从第i个城市到第j个的费用
int n;//城市个数
cin >> n;
x = new int[n];
for (int i = 0; i < n; i++) {
x[i] = i;//表示第i个城市编号,0到n-1
}
bestx = new int[n];
a = new int*[n];
for (int i = 0; i < n; i++) {
a[i] = new int[n];
}
//输入费用矩阵表示图
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
cin >> a[i][j];
}
}
Tsp(a, n, 1);
cout << "最优的是:" << bestw << endl;
for (int i = 0; i < n; i++) {
delete a[i];
}
delete[]a;
delete[]x;
delete[]bestx;
return 0;
}
void Tsp(int **a, int n, int s) {
if (s == n) {
//找到一条路线,下面判断条件的含义
//a[x[n - 1]][x[0]] != 0:0表示城市不可达
//cc + a[x[n - 1]][x[0]] < bestw:加上返回起点的费用是否大于最优的费用
if ((a[x[n - 1]][x[0]] != 0 && (cc + a[x[n - 1]][x[0]] < bestw)) || bestw == 0) {
cout << "bestx: ";
for (int i = 0; i < n; i++) {
bestx[i] = x[i];
cout << bestx[i] << " ";
}
bestw = cc + a[x[n - 1]][x[0]];
cout << "bestw:" << bestw << endl;
}
}
else {
for (int i = s; i < n; i++) {
//这里就是递归遍历的地方,加了约束条件,和上面一样
//递归的基础是:前面交换了,回溯时得交换回来,加了得减回来
if ((a[x[i - 1]][x[i]] != 0 && cc + a[x[i - 1]][i] < bestw) || bestw == 0) {
int t = x[i]; x[i] = x[s]; x[s] = t;
cc += a[x[s - 1]][x[s]];
Tsp(a, n, s + 1);
cc -= a[x[s - 1]][x[s]];
t = x[i]; x[i] = x[s]; x[s] = t;
}
}
}
}
再来看运行结果:
总结
可以看出约束条件还是很有用的。
全排列的大体框架都差不多,操作都在s==n里。