算法设计技巧五:回溯算法(backTracing)
回溯算法相当于穷举搜索的巧妙实现,即排除一定条件的穷举情况,从而不必完全穷举。相对于蛮力穷举,回溯算法能显著减轻工作量。在许多情况下,回溯算法的性能不是很理想。但性能是相对的:对于排序算法,的算法相当糟糕,但对于旅行商(或任意NP完全)问题,
算法则是里程碑式的突破。
公路收费点重建问题行为描述:
设给定个点
,他们位于
上,
是
的坐标,并设
= 0,这些点从左到右递增排列。公路收费点重建问题要求根据
对距离
重建并确定
的位置。
公路收费点重建问题图形描述:
设距离集合为:distSet = {1, 2, 2, 2, 3, 3, 3, 4, 5, 5, 5, 6, 7, 8, 10},共15对距离,则N= 6;
1) 初始条件:x_1 = 0, 选取最大距离dmax = 10,放置在x[N]处;
2)选取次最大值8,则x_5 = 8 或者x_2 = 2, 二者互补对称,插入坐标轴判断剩余距离是否满足;此时有(1,5)= 8,(5,6)= 2;
3)选取次最大值7,插入坐标轴判断剩余距离是否满足;此时有x_4 = 7 或者 x_2 = 3;如果x_4=7,则(6,4) = 3, (5,4) = 1如果置x_2=3,则(2,1) = 3且(5, 2) = 5;分别考虑两种情况:
1、x_4 = 7 时的情况,剩余点对最大值为6,此时有x_3 = 6 或者x_2 = 4,当x_3 = 6时,(6,7) = 1, 而(7,8) = 1,所 以不满足;若x_2 = 4,(2, 0) = 4, (2, 5) = 4不满足;
2、x_2 = 3时的情况,(2,1) = 3, (2, 5) = 5,剩余点对最大值为6,x_4 = 6或者x_3 = 4,当x_3 = 4时,(1,3)= 4,(3, 5)= 4 不满足;当x_4 = 6时,为可能情况;
4)通过3)分析可知,可能的位置为x_2 = 3, x_4 = 6,接下来需要确定的只剩下x_5 = 5,带入验证可知此时刚好填满坐标轴且满足距离点集;
整个过程的决策树结构如图所示:
公路收费点重建问题编程实现:
//main.cpp
#include<iostream>
#include<vector>
#include<cmath>
#include<set>
using namespace std;
const int N = 6;
bool place(vector<int> &x, multiset<int> distSet, int N, int left, int right){
int dmax;
bool found = false;
//when all distSet elements have been checked, return
if(distSet.empty()){
return true;
}
//copy of distSet
multiset<int> dSet = distSet;
dmax = *distSet.rbegin();
//place dmax to x[right]
x[right] = dmax;
bool flag = true;
//find the difference in left part
for(int i = 1; i < left; i++){
if(dSet.find(abs(dmax - x[i])) == dSet.end()){
flag = false;
break;
}
//take off the distance from left part(right part complement)
dSet.erase(dSet.find(abs(x[i] - x[right])));
}
//find the difference in right part
for(int i = right + 1; i <= N; i++){
if(dSet.find(abs(dmax - x[i])) == dSet.end()){
flag = false;
break;
}
//take off the distance from right part
dSet.erase(dSet.find(abs(x[i] - x[right])));
}
//iterative recall place method when flag = true
if(flag){
distSet = dSet;
found = place(x, distSet, N, left, right - 1);
//if not, reinsert the distance into distSet [backTracing]
if(!found){
for(int i = 1; i < left; i++){
distSet.insert(abs(x[i] - x[right]));
}
for(int i = right + 1; i <= N; i++){
distSet.insert(abs(x[i] - x[right]));
}
}
}
flag = true;
dSet = distSet;
//if not found suitable place in right, try the left
if(!found){
x[left] = x[N] - dmax;
for(int i = 1; i < left; i++){
if(dSet.find(abs(x[i] - x[left])) == dSet.end()){
flag = false;
break;
}
dSet.erase(dSet.find(abs(x[i] - x[left])));
}
for(int i = right + 1; i <= N; i++){
if(dSet.find(abs(x[i] - x[left])) == dSet.end()){
flag = false;
break;
}
dSet.erase(dSet.find(abs(x[i] - x[left])));
}
//iterative recall place method when flag = true
if(flag){
distSet = dSet;
found = place(x, distSet, N, left + 1, right);
//if not, reinsert the distance into distSet [backTracing]
if(!found){
for(int i = 1; i < left; i++){
distSet.insert(abs(x[N] - dmax - x[i]));
}
for(int i = right + 1; i <= N; i++){
distSet.insert(abs(x[N] - dmax - x[i]));
}
}
}
}
return found;
}
int main(){
vector<int> x(N + 1);
multiset<int> distSet;
int dist[] = {1, 2, 2, 2, 3, 3, 3, 4, 5, 5, 5, 6, 7, 8, 10};
int len = sizeof(dist) / sizeof(dist[0]);
for(int i = 0; i < len; i++){
distSet.insert(dist[i]);
}
//x_1 = 0
x[1] = 0;
//x_n = max, rebegin() reverse iterator
x[N] = *distSet.rbegin();
//take out the max distance
distSet.erase(*distSet.rbegin());
//traceBacking algorithm
bool found = place(x, distSet, N, 2, N - 1);
vector<int>::iterator itr;
for(itr = x.begin() + 1; itr != x.end(); itr++){
cout << *itr << " . ";
}
cout << endl;
cout << " done." << endl;
return 0;
}
practice makes perfect !
实验结果:
0 . 3 . 5 . 6 . 8 . 10 .
done.