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

通过点与点之间的距离重新构造出点集。距离集大小为N(N-1)/2,N为点的个数。

驱动程序

#include <iostream>
#include <vector>
#include <set>
using namespace std;

bool turnpike(vector<int> & x,multiset<int> d,int n)
{
    bool place(vector<int> & x,multiset<int> & d,int n,int left,int right);
    x[1]=0;                     //令第一个点的坐标为0
    x[n]=*d.rbegin();           //最大距离为最后一个点的坐标
    d.erase(--d.end());         //从距离集中删除该距离

    x[n-1]=*d.rbegin();         //放置下一个最大距离点,根据对称性,该点放在x[2]或者x[n-1]得到的解互为镜像
    d.erase(--d.end());

    if(d.find(x[n]-x[n-1])!=d.end())
    {
        d.erase(d.find(x[n]-x[n-1]));   //不能直接删除值,因为会删除所有相同的值
        return place(x,d,n,2,n-2);      //放置其余的点
    }
    else
        return false;
}

回溯部分

//x存放结果,d为距离集,n为点个数
bool place(vector<int> & x,multiset<int> & d,int n,int left,int right)
{
    int dmax;
    bool found=false;

    if(d.size()==0)                 //距离集空,表明所有点放完,得到正确的答案
        return true;

    dmax=*d.rbegin();

    bool test=true;                 //测试放置的点是否正确
    multiset<int> tmp;              //临时集合

    //先测试放在右边x[right]=dmax

    //左半部分,因为左半部分和右半部分相对于x[right]的距离可能相同,因此需要删除左半部分已经找到的值。
    //避免一值两用
    for(int j=1;j<left;++j)
        if(d.find(dmax-x[j])==d.end())      //如果不存在该距离,则放置错误
        {
            test=false;
            break;
        }
        else
        {
            tmp.insert(dmax-x[j]);
            d.erase(d.find(dmax-x[j]));
        }
    //测试右半部分
    if(test)
    {
        for(int j=right+1;j<=n;++j)
            if(d.find(x[j]-dmax)==d.end())
            {
                test=false;
                break;
            }
    }
    //将左半部分删除的值插入回去
    for(auto itr=tmp.begin();itr!=tmp.end();++itr)
    {
        d.insert(*itr);
    }

    //如果x[right]放置正确
    if(test)
    {
        x[right]=dmax;

        //删除对应的距离
        for(int j=1;j<left;++j)
            d.erase(d.find(dmax-x[j]));

        for(int j=right+1;j<=n;++j)
            d.erase(d.find(x[j]-dmax));

        //接着放置剩下的点
        found=place(x, d, n, left, right-1);

        //如果后面的所有情况全部失败,表明该点放置错误。则撤销这步操作,重新插入距离
        if(!found)
        {
            for(int j=1;j<left;++j)
                d.insert(dmax-x[j]);
            for(int j=right+1;j<=n;++j)
                d.insert(x[j]-dmax);

        }
    }


    //测试放在左边x[left]=x[n]-dmax
    if(!found)
    {

        //和测试右边right相同
        test=true;
        tmp.clear();

        for(int j=1;j<left;++j)
            if(d.find(x[n]-dmax-x[j])==d.end())
            {
                test=false;
                break;
            }
            else
            {

                tmp.insert(x[n]-dmax-x[j]);
                d.erase(d.find(x[n]-dmax-x[j]));
            }
        if(test)
        {
            for(int j=right+1;j<=n;++j)
                if(d.find(x[j]-x[n]+dmax)==d.end())
                {
                    test=false;
                    break;
                }
        }

        for(auto itr=tmp.begin();itr!=tmp.end();++itr)
        {
            d.insert(*itr);
        }


        if(test)
        {


            x[left]=x[n]-dmax;

            for(int j=1;j<left;++j)
                d.erase(d.find(x[n]-dmax-x[j]));

            for(int j=right+1;j<=n;++j)
                d.erase(d.find(x[j]-x[n]+dmax));

            found=place(x, d, n, left+1, right);

            if(!found)
            {
                for(int j=1;j<left;++j)
                    d.insert(x[n]-dmax-x[j]);
                for(int j=right+1;j<=n;++j)
                    d.insert(x[j]-x[n]+dmax);

            }
        }

    }
    return found;
}

main.cpp

int main(int argc, const char * argv[]) {
    // insert code here...
    int n=6;
    multiset<int> d={1,2,2,2,3,3,3,4,5,5,5,6,7,8,10};
    vector<int> x(n+1);
    turnpike(x, d, n);
    for(int i=1;i<=n;++i)
    {
        cout<<x[i]<<endl;
    }
    std::cout << "Hello, World!\n";
    return 0;
}

结果
0
3
5
6
8
10
Hello, World!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值