回溯算法之收费公路重建问题

收费公路重建问题

设给定N个点 p 1 , p 2 , . . . , p N 位于x轴上, x i p i 的坐标,假设 x 1 = 0 且这些点从左至右分布。则每一对点之间都对应一个距离 | x i , x j | ,   ( i j ) ,共计有N(N-1)/2对点产生的距离。
收费公路重建问题就是根据距离的集合重新构造或确定一个点集合的分布,即已知M=N(N-1)/2个距离值,反解出N个点(的坐标)。


回溯算法之收费公路重建问题简单示例

令D是距离集合:

D = { 1 , 2 , 2 , 2 , 3 , 3 , 3 , 4 , 5 , 5 , 5 , 6 , 7 , 8 , 10 }

由于|D|=M=N(N-1)/2=15,所以我们知道要求解一个大小为6的点集合X。

算法过程

  • (1)算法从置 x 1 = 0 开始,显然 x 6 = 10 ,因为D中最大元素为10即点集合X中两个点最大相聚为10,而点从左至右分布,所以最大距离只能是 ( x 1 , x 6 ) = 10 ,将10从D中删除。
  • 此时点分布用图表示为:
    这里写图片描述
    距离集合D更新为
    D = { 1 , 2 , 2 , 2 , 3 , 3 , 3 , 4 , 5 , 5 , 5 , 6 , 7 , 8 }
  • (2)D中剩下最大距离为8,则有两种可能, x 5 = 8 或者 x 2 = 2 ,由对称性,二者皆可。那么置 x 5 = 8 ,从而接着从D中删除距离值 x 6 x 5 = 2 x 5 x 1 = 8
  • 此时点分布图为:
    这里写图片描述
    距离集合D更新为
    D = { 1 , 2 , 2 , 3 , 3 , 3 , 4 , 5 , 5 , 5 , 6 , 7 }
  • (3)这时,D中最大数为7,因此有两种可能: x 4 = 7 或者 x 2 = 3 。如果 x 4 = 7 ,则距离值 x 6 x 4 = 3 x 5 x 4 = 1 也必须出现在当前D中(满足),如果置 x 2 = 3 ,则 x 2 x 1 = 3 x 5 x 2 = 5 也必须出现在当前D中(满足)。这时,先尝试其中一种可能看是否出现导致问题的解。如果不可以,则再退回来再尝试另外的选择。这里,先尝试置 x 4 = 7 的情况
  • 此时点分布图为:
    这里写图片描述
    距离集合D更新为
    D = { 2 , 2 , 3 , 3 , 4 , 5 , 5 , 5 , 6 }
  • (4)这时,D中最大距离值为6,因此 x 3 = 6 或者 x 2 = 4 。但是,如果 x 3 = 6 ,则距离值 x 4 x 3 = 1 不在当前距离集合D中,否定这一情况;此为,如果 x 2 = 4 ,则 x 2 x 0 = 4 x 5 x 2 = 4 ,出现两个距离值4,而当前距离集合D只有一个4,也不可能。因此,这时推导思路得不到问题的最终解,需要回溯,回溯至(3)中。
  • (5)由于 x 4 = 7 不能产生问题的解,因此尝试另外的选择 x 2 = 3 ,如果这个选择也不行,则原问题无解,停止算法过程。
  • 此时点分布图为:
    这里写图片描述
    距离集合D更新为
    D = { 1 , 2 , 2 , 3 , 3 , 4 , 5 , 5 , 6 }
  • (6)此时,D中最大数为6,再一次在 x 4 = 6 x 3 = 4 中选择,经过分析只有 x 4 = 6 可能,于是点分布图为:
    这里写图片描述
    距离集合D更新为
    D = { 1 , 2 , 3 , 5 , 5 }
  • (7)剩下唯一的选择 x 3 = 5 能使得距离集合D变成空集,因此算法结束,得到了问题的最终解。点分布图为:
    这里写图片描述
    距离集合D更新为
    D = { }

回溯算法的决策树

回溯算法的过程可以看成一个决策树生成的过程,根节点代表算法起始点,每个分枝代表算法求解问题的一种思路,当分枝得不到问题的解时,回溯到根节点从而尝试另一个分枝的求解问题思路。下图表示回溯算法解决收费公路重建问题的决策树

这里写图片描述


代码实现

#include<iostream>
#include<set>
#include<vector>
#include<cmath>

using namespace std;

int Place(vector<int> &X, multiset<int> DSet, int N, int left, int right)
{
    int MaxDist, Found = false;
    if (DSet.empty())       // 如果距离集合为空,表示所有距离都被用上
        return true;
    MaxDist = *DSet.rbegin();   // 当前距离的最大值
    multiset<int> DSetcopy = DSet;      // 创建DSet副本
    // 检查当前被放置的X[Right]==MaxDist是否可行
    int flag = 1;   
    X[right] = MaxDist;     // 尝试在right位置放置当前最大距离值行不行,不行就回溯
    for (int i = 1;i < left;i++)
    {// 如果DSet中没有已放置好的X[i](0<i<left)减去当前最大距离MaxDist的绝对值(Dset中距离值)时
        if (DSetcopy.find(abs(X[i] - MaxDist)) == DSetcopy.end())
        {
            flag = 0;break; // flag置为0
        }
        DSetcopy.erase(DSetcopy.find(abs(X[i] - X[right])));    // 删除距离集合中相关距离值
    }
    for (int i = right + 1;i <= N;i++)
    {// 如果DSet中没有已放置好的X[i](right<i<=N)减去当前最大距离MaxDist的绝对值时
        if (DSetcopy.find(abs(X[i] - MaxDist)) == DSetcopy.end())
        {
            flag = 0;   // flag置为0
            break;
        }
        DSetcopy.erase(DSetcopy.find(abs(X[i] - X[right])));
    }
    if (flag)
    {
        DSet = DSetcopy;
        Found = Place(X, DSet, N, left, right - 1);     // 继续在下一个位置right-1处插入下一个最大的距离值
        if (!Found) // 如果Found==false表示距离集合不为空,则进行回溯
        {
            for (int i = 1;i < left;i++)
                DSet.insert(abs(X[i] - X[right]));  // 回插距离集合中相关距离值,将删除的距离值返回
            for (int i = right + 1;i <= N;i++)
                DSet.insert(abs(X[i] - X[right]));
        }
    }

    flag = 1;
    DSetcopy = DSet;        // 创建DSet副本
    if ((!Found))           // 如果尝试在右边放置失败,则尝试在左边放置
    {
        X[left] = X[N] - MaxDist;
        for (int i = 1;i < left;i++)
        {   // 如果DSet中没有已放置好的X[i](0<i<left)使得X[N]减去当前最大距离MaxDist再减去X[i]的结果绝对值时
            if (DSetcopy.find(abs(X[left] - X[i])) == DSetcopy.end())
            {
                flag = 0;   // flag置为0
                break;
            }
            DSetcopy.erase(DSetcopy.find(abs(X[left] - X[i]))); // 删除距离集合中相关距离值
        }
        for (int i = right + 1;i <= N;i++)
        {
            // 如果DSet中没有已放置好的X[i](right<i<=N)减去当前最大距离MaxDist再减去X[i]的结果绝对值时
            if (DSetcopy.find(abs(X[left] - X[i])) == DSetcopy.end())
            {
                flag = 0;   // flag置为0
                break;
            }
            DSetcopy.erase(DSetcopy.find(abs(X[left] - X[i])));
        }
        if (flag)       // 表示距离集合DSet中元素分布满足插入X[left]的条件
        {
            DSet = DSetcopy;
            Found = Place(X, DSet, N, left + 1, right);
            if (!Found) // 如果Found==false表示距离集合不为空,则进行回溯
            {
                for (int i = 1;i < left;i++)
                    DSet.insert(abs(X[N] - MaxDist - X[i]));    // 回插距离集合中相关距离值,将删除的距离值返回
                for (int i = right + 1;i <= N;i++)
                    DSet.insert(abs(X[N] - MaxDist - X[i]));
            }
        }
    }
    return Found;
}

int main()
{
    const int N = 6;
    int DistSet[] = { 1,2,2,2,3,3,3,4,5,5,5,6,7,8,10 };     // 距离集合
    multiset<int> DSet;     // 将距离集合存放至multiset中,方便后续操作
    vector<int> X(N + 1);           // 向量X用于存放点集合结果
    for (int i = 0;i < sizeof(DistSet) / sizeof(int);i++)
    {
        DSet.insert(DistSet[i]);
    }
    X[1] = 0;           // X[1]置为1
    X[N] = *DSet.rbegin();  // X[N]置为距离集合中的最大值
    DSet.erase(*DSet.rbegin()); // 删除最大距离值
    Place(X, DSet, N, 2, N - 1);    // 回溯算法来确定每个点的位置
    for (vector<int>::iterator iter = X.begin()+1;iter != X.end();iter++)
    {// 打印点集合中的点
        cout << *iter << " ";
    }
    cout << endl;
    system("pause");
    return 0;
}

输出结果

0 3 5 6 8 10
请按任意键继续. . .

参考资料

Mark Allen Weiss: 数据结构与算法分析

猜你喜欢

转载自blog.csdn.net/weixin_40170902/article/details/80856799