回溯算法四:子集树、排列树以及组合优化
一、子集树
1、子集树:若一个组合问题的解释给定集合的子集,则解向量<x1, x2,...,xn>可以表示为分量取值为{0,1}的比特串,解空间可以组成一颗完全二叉树,称这棵搜索树为一棵子集树;
2、由于解向量的每个分量均取0或1,因此可以省略解集合处理过程;
3、子集问题示例,可以参考:回溯算法三:经典问题实现(m-着色、n-皇后、Hamilton回路、子集和)
二、排列树
1、把解向量是n个元素排列的组合问题的搜索树,称为排列树,将这类组合问题成为排列树问题;
2、由于排列树问题的特殊性,可以将n-叉完全二叉树简化,以提高效率;
3、简化步骤:
EXPLORE(P, K)
0 n = length(x)
1 if IS-COMPLATE(X) // 判断解向量是否完全
2 then flag = true // 若为完全解,则置解标志,输出解信息,并返回
3 PRINT-SOLUTION(X)
4 return // 需要分析,是否需要输出所有的完整解
5 if k > n // k为当前解向量长度,n为解向量的最大长度
6 then return // 若k>n,表示当前分支遍历完全且无解,直接返回
7
8 for i=(k,...,n) // 对当前第k个分量,逐一检测各种可能的取值
10 do x[k] <=> x[i] // 遍历x[k]的有效取值(非完全遍历),且交换后,维持x的有效性
11 if IS-PARTIAL(x, k) // 确定是否为部分解
12 then EXPLORE(P, k+1) // 继续递归下一步探索过程
13 x[k] <=> x[i] // 还原x,继续进行for中的下一个排列
4、n-皇后问题示例如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// x[i]表示每行棋子的列位置,k表示最新添加的棋子索引,即当前处理的第k行的棋子位置问题(k从0开始)
int isPartial(int n, int *x, int k)
{
int diff;
for (int i = 0; i < k; i++) {
// 判断新增的位置与历史位置是否满足规则:不同行、不同列、不同斜线
// 遍历判断新增棋子与之前棋子的位置差异
diff = x[i] - x[k];
// 列位置相同——同列;列号之差等于行号之差——对角线;遍历处理[0,k-1]行,不可能同行
if (diff == 0 || (diff == i - k) || (diff == k - i)) {
return 0;
}
}
return 1;
}
void swap(int *x, int k, int i)
{
int temp = x[k];
x[k] = x[i];
x[i] = temp;
}
// 递归过程,x与k同时变化,其他均为定值
void nQueensPlus(int n, int *x, int k)
{
int i;
// 完全解判断:k为当前解长度,n为完整解的最大长度
if (k >= n) {
for (int i = 0; i < n; i++) {
printf("%d ", x[i]);
}
printf("\n");
return;
}
// 递归遍历,回溯过程
for (int i = k; i < n; i++) {
// 针对x[k]的遍历值,取值范围为x[k,,,n],有效减小搜索空间
swap(x, k, i);
// 判断部分解逻辑复杂,建议抽取函数
if (isPartial(n, x, k)) {
nQueensPlus(n, x, k + 1);
}
// 恢复x,进行下一轮
swap(x, i, k);
}
}
int main(void)
{
// n为棋盘规模,x为解向量空间,k为当前解的个数[0, n-1]
int n = 4;
int k = 0;
// 初始化解向量
int *x = (int*)malloc(n * sizeof(int));
for (int i = 0; i < n; i++) {
x[i] = i;
}
// n为棋盘规模,locations为棋盘位置集合(列),x为解向量空间,k为当前解的个数[0, n-1]
nQueensPlus(n, x, k);
while(1);
return 0;
}
三、组合优化
1、简要说明
(1)组合优化问题需要在解空间中找到最优者:在回溯过程中,需要跟踪目前为止最优合法解的目标值most,与当前搜索到的合法解current的目标值进行比较,确认是否需要更新most。
(2)可以利用当前最优值most,对不可能成为最优解的子树进行剪枝(isPartial步骤)。
2、旅行商问题
(1)问题描述:商人从n个城市中的一个出发,希望走遍每个城市且每个城市只经过一次,然后回到出发的城市。如果有这样的路径,要求找到里程最短的路径。(最短的Hamilton回路)
输入:带权无向图G=<V, E>,权函数w:E->R,起始点s。
输出:如果存在Hamilton回路,输出权值最小的路径,否则输出无解信息。
(2)分析过程:
(3)算法实现:
(4)测试结果: