数据结构与算法分析-C++描述 第10章 算法设计技巧(回溯算法之公路收费点重建问题)

算法设计技巧五:回溯算法(backTracing)

       回溯算法相当于穷举搜索的巧妙实现,即排除一定条件的穷举情况,从而不必完全穷举。相对于蛮力穷举,回溯算法能显著减轻工作量。在许多情况下,回溯算法的性能不是很理想。但性能是相对的:对于排序算法,O(N^2)的算法相当糟糕,但对于旅行商(或任意NP完全)问题,O(N^5)算法则是里程碑式的突破。

公路收费点重建问题行为描述:

      设给定N个点p_1, p_2, ..., p_N,他们位于x上,x_ip_i的坐标,并设x_1 = 0,这些点从左到右递增排列。公路收费点重建问题要求根据N(N-1)/2对距离|x_i - x_j|(i\neq j)重建并确定x_i的位置。

公路收费点重建问题图形描述:

       设距离集合为: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.

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值