《数据结构与算法分析:C语言描述》复习——第十章“算法设计技巧”——收费站重建问题...

2014.07.07 18:19

简介:

  给定一条数轴上的n个互不重合的点,你可以计算出C(n,2)=n(n-1)/2个距离。如果我给你这些距离值,你能反推出这n个点的坐标吗?

描述:

  首先,考虑到你可以平移这n个点,并且可以左右反转它们得到对称的两种情况,我们不妨假设最靠左的点p0的坐标正好是0

  

  接下来我们通过深度优先搜索回溯的手段逐个求出每个点的坐标。

  起初我们有n(n-1)/2个距离,搜索要如何下手呢?从最长的那个下手。

  比如n=3时,有距离之{3,7,10}。这其中,10能够告诉我们最靠右的点p2坐标一定10,因为两端的两点离得最远。

  由此我们一开始就能够确定两个端点的坐标p0(人为规定为0)和pn-1,接下来中间的n-2个点需要通过搜索来确定。

  

  假设你现在已经确定了i个点的坐标,剩余的距离集合是D(想想当你确定了i个点之后,手里剩下还有多少个距离)。初始的距离集合D拥有n(n-1)/2个距离。

  如果目前D中最大的距离是d=max(D),那么第i+1个点的坐标可能是d或者pn-1-d。

  

  这样产生了两种可能性,对于每一种可能性,你都要第i+1个点与前i个点的距离是否能在D余中找到。

  如果D余中找不到对应的距离值,说明这次搜索已经失败,需要回溯到上层。

  而如果这i次检验全部成功的话,需要从D中去掉i个距离值。

  

  这个搜索与回溯的过程说起来容易,但如果你真的用简单的数组作为数据结构,会发现操作很不方便而且效率低下。

  我们有如下三点需求:

    1. 找出D中最长的边,我需要有序的数据

    2. 查找某个距离值d是否在D中,我需要快速查找(至少不是线性查找)

    3. 回溯时我需要把去掉过的距离值重新放回D中,我需要快速插入(向数组中间插入元素,效率无法忍受)

  基于以上三点需求,平衡树成了最佳选择。于是STL的<map>成为解决这个问题的上佳工具。(begin()与rbegin()有何用处?)

  就算你执意用简单的数组解决这个问题,觉得那样可以偷懒,实际写起来还是既繁琐又低效。(因为往数组中间插入或删除东西实在是很不和谐的操作)

  说到时间复杂度,这种搜索+回溯的解法自然是指数级的。

  也许用八皇后问题来作为回溯的例子更容易些,不过既然作者选了这个我们就学这个吧。

实现:

  1 // Turnpike reconstruction problem.
  2 //    Description:
  3 //        Given n * (n - 1) / 2 distances, find out if you can determine the 
  4 //            relative coordinates of n points.
  5 #include <algorithm>
  6 #include <cmath>
  7 #include <iostream>
  8 #include <map>
  9 #include <vector>
 10 using namespace std;
 11 
 12 int myabs(int x)
 13 {
 14     return x > 0 ? x : -x;
 15 }
 16 
 17 bool turnpikeReconstruction(int idx, vector<int> &x, map<int, int> &dist_set, 
 18     const int far)
 19 {
 20     if (idx == 0) {
 21         return true;
 22     }
 23     
 24     int cur_n;
 25     int cur_far;
 26     int i;
 27     map<int, int>::iterator it;
 28     
 29     cur_n = (int)x.size();
 30     cur_far = dist_set.rbegin()->first;
 31     for (i = 0; i < cur_n; ++i) {
 32         it = dist_set.find(myabs(cur_far - x[i]));
 33         if (it == dist_set.end() || it->second == 0) {
 34             break;
 35         }
 36         --it->second;
 37         if (it->second == 0) {
 38             dist_set.erase(it);
 39         }
 40     }
 41     if (i == cur_n) {
 42         x.push_back(cur_far);
 43         if (turnpikeReconstruction(idx - 1, x, dist_set, far)) {
 44             return true;
 45         }
 46         x.pop_back();
 47     }
 48     cur_n = i;
 49     for (i = 0; i < cur_n; ++i) {
 50         ++dist_set[myabs(cur_far - x[i])];
 51     }
 52     
 53     cur_n = (int)x.size();
 54     cur_far = dist_set.rbegin()->first;
 55     for (i = 0; i < cur_n; ++i) {
 56         it = dist_set.find(myabs(far - cur_far - x[i]));
 57         if (it == dist_set.end() || it->second == 0) {
 58             break;
 59         }
 60         --it->second;
 61         if (it->second == 0) {
 62             dist_set.erase(it);
 63         }
 64     }
 65     if (i == cur_n) {
 66         x.push_back(far - cur_far);
 67         if (turnpikeReconstruction(idx - 1, x, dist_set, far)) {
 68             return true;
 69         }
 70         x.pop_back();
 71     }
 72     cur_n = i;
 73     for (i = 0; i < cur_n; ++i) {
 74         ++dist_set[myabs(far - cur_far - x[i])];
 75     }
 76     
 77     return false;
 78 }
 79 
 80 int main()
 81 {
 82     int i;
 83     int n, n2;
 84     vector<int> x;
 85     int dist;
 86     map<int, int> dist_set;
 87     int far;
 88     
 89     while (cin >> n2 && n2 > 0) {
 90         for (i = 0; i < n2; ++i) {
 91             cin >> dist;
 92             ++dist_set[dist];
 93         }
 94         n = (int)sqrt(n2 * 2.0) + 1;
 95         far = dist_set.rbegin()->first;
 96         --dist_set[far];
 97         if (dist_set.rbegin()->second == 0) {
 98             dist_set.erase(far);
 99         }
100         
101         x.push_back(0);
102         x.push_back(far);
103         if (!turnpikeReconstruction(n - 2, x, dist_set, far)) {
104             cout << "No solution." << endl;
105         } else {
106             sort(x.begin(), x.end());
107             for (i = 0; i < n; ++i) {
108                 cout << x[i] << ' ';
109             }
110             cout << endl;
111         }
112         
113         x.clear();
114         dist_set.clear();
115     }
116     
117     return 0;
118 }

 

转载于:https://www.cnblogs.com/zhuli19901106/p/3830299.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值