分支定界法求解旅行商问题

本文详细介绍了旅行商问题(TSP)的一个经典算法实现,通过随机数据生成初始概率密度函数,并利用接受-拒绝方法进行处理。代码中采用任务树结构,通过选取路径中最小元素进行规约操作,同时实现左子树和右子树的构建,以加速收敛过程。实验结果显示,算法在不同维度下表现出良好的执行效率,平均执行时间分别为6.4毫秒(维数23)和38.8毫秒(维数33)。最后,文章展示了找到的最优路径和最佳旅行距离。
摘要由CSDN通过智能技术生成

0、问题梗概

旅行商问题(TravelingSalesmanProblem,TSP)是一个经典的组合优化问题。经典的TSP可以描述为:一个商品推销员要去若干个城市推销商品,该推销员从一个城市出发,需要经过所有城市后,回到出发地。应如何选择行进路线,以使总的行程最短。

1、随机数据的初始概率密度函数

[公式]

[公式] [公式]


2、程序运行数据

运行次数时间(维数23)时间(维数33)
13.06541 ms26.9593 ms
23.65156 ms48.6972 ms
32.66832 ms43.5297 ms
46.78412 ms44.7827 ms
59.77699 ms24.6574 ms
64.44002 ms24.3777 ms
79.36337 ms20.4059 ms
87.22274 ms45.9999 ms
98.77502 ms41.7881 ms
108.07504 ms66.3793 ms

维度 23 的平均执行时间为 6.4 毫秒,维度 33 的平均执行时间为 38.8 毫秒。


3、代码分析

I)使用数据类型

using mat_d = vector<vector<double>>;
using path_t = vector<pair<size_t, size_t>>;

II) 随机数据的初始概率密度函数

double F(double x)
{
    return (5 / 6.0) * (1 + pow(x, 4));
}
// use Acceptance-Rejection Method
// max from 0 to 1 in function F is 5/3
double GetRnd()
{
    double u1, u2;
    while (true)
    {
        u1 = rand() % MAX_DIST;
        u2 = rand() % MAX_DIST;
        if (u2 <= (3 / 5.0) * F(u1))
            return u1;
    }
}

III) 任务路径树

// Task tree node represents each reduction matrix
class TaskTree
{
private:
    double bound;
    int index_row, index_col;

public:
    mat_d mat;
    path_t path;
    TaskTree(const mat_d &m, const path_t &p = {}, const double b = 0);
    void PrintMat();
    void ReduceMinElem();
    void FindMinPath();
    bool PathCompleted() { return path.size() == mat.size(); }
    double GetBound() { return bound; }
    int GetRowIndex() { return index_row; }
    int GetColIndex() { return index_col; }
};

IV) 选取路径中最小元素后进行规约操作

// Minimum statute
void TaskTree::ReduceMinElem()
{
    for (size_t i = 0; i < mat.size(); i++)
    {
        double minelem_row = INF;
        for (size_t j = 0; j < mat.size(); j++)
        {
            if (minelem_row > mat[i][j])
            {
                minelem_row = mat[i][j];
            }
        }
        for (size_t j = 0; j < mat.size(); j++)
        {
            if (mat[i][j] != INF)
            {
                mat[i][j] -= minelem_row;
            }
        }
        if (minelem_row != INF)
            bound += minelem_row;
    }
    for (size_t i = 0; i < mat.size(); i++)
    {
        double minelem_col = INF;
        for (size_t j = 0; j < mat.size(); j++)
        {
            if (minelem_col > mat[j][i])
            {
                minelem_col = mat[j][i];
            }
        }
        for (size_t j = 0; j < mat.size(); j++)
        {
            if (mat[j][i] != INF)
            {
                mat[j][i] -= minelem_col;
            }
        }
        if (minelem_col != INF)
            bound += minelem_col;
    }
    FindMinPath();
}

V) 寻找最小元素过程

// Find the shortest path possible
void TaskTree::FindMinPath()
{
    index_row = -1;
    index_col = -1;
    double max_sum = -1;
    for (size_t i = 0; i < mat.size(); i++)
    {
        for (size_t j = 0; j < mat.size(); j++)
        {
            if (mat[i][j] == 0)
            {
                double min_row = INF, min_col = INF;
                for (size_t k = 0; k < mat.size(); k++)
                {
                    if (k != j && mat[i][k] < min_row)
                        min_row = mat[i][k];
                    if (k != i && mat[k][j] < min_col)
                        min_col = mat[k][j];
                }
                double max_sum_tmp = min_row + min_col;
                if (max_sum_tmp > max_sum)
                {
                    index_row = i;
                    index_col = j;
                    max_sum = max_sum_tmp;
                }
            }
        }
    }
    path.push_back(make_pair(index_row, index_col));
}

VI) 左子树情况为选取最小元素加入路径,右子树情况为不选取该元素

// Subtree containing the selected path
shared_ptr<TaskTree> SetLeft(const mat_d &m, const path_t &path, const double bound, const int index_row, const int index_col)
{
    mat_d mat = m;
    map<size_t, size_t> pathTest;
    for (size_t i = 0; i < path.size() - 1; i++)
    {
        pathTest[path[i].first] = path[i].second;
    }
    size_t pos = index_col;
    while (pathTest.find(pos) != pathTest.end())
    {
        pos = pathTest[pos];
        if (pos == index_row)
        {
            return nullptr;
        }
    }
    for (size_t i = 0; i < mat.size(); i++)
    {
        mat[index_row][i] = INF;
        mat[i][index_col] = INF;
    }
    mat[index_col][index_row] = INF;

    return make_shared<TaskTree>(mat, path, bound);
}
// Does not contain the subtree of the selected path
shared_ptr<TaskTree> SetRight(const mat_d &m, const path_t &p, const double bound, const int index_row, const int index_col)
{
    mat_d mat = m;
    mat[index_row][index_col] = INF;

    path_t path = p;
    path.pop_back();
    return make_shared<TaskTree>(mat, path, bound);
}

VII) 对旅行商问题解决进行二次封装

// Problem-solving tree
class Task
{
private:
    map<size_t, size_t> bestpath;
    list<shared_ptr<TaskTree>> tour;
    mat_d origin_mat;
    double best;
    shared_ptr<TaskTree> root;

public:
    Task(const size_t size, double (*pf)() = GetRnd);
    Task(const char *filename, const size_t size);
    void RunTask();
    void PrintPath();
};

VIII) 解决问题过程,核心思路优先考虑左子树以加快收敛过程

void Task::RunTask()
{
    tour.push_front(root);
    // root->PrintMat();
    while (tour.size() != 0)
    {
        shared_ptr<TaskTree> head = tour.front();
        tour.pop_front();
        if (head->GetBound() > best)
        {
            continue;
        }
        if (head->PathCompleted())
        {
            best = head->GetBound();
            bestpath.clear();
            for (size_t i = 0; i < origin_mat.size(); i++)
            {
                bestpath[head->path[i].first] = head->path[i].second;
            }
            continue;
        }
        if (head->GetRowIndex() == -1 && head->GetColIndex() == -1)
        {
            continue;
        }
        shared_ptr<TaskTree> left = SetLeft(head->mat, head->path, head->GetBound(), head->GetRowIndex(), head->GetColIndex());
        shared_ptr<TaskTree> right = SetRight(head->mat, head->path, head->GetBound(), head->GetRowIndex(), head->GetColIndex());
        if (left != nullptr)
        {
            if (left->path.size() < origin_mat.size())
            {
                if (left->GetBound() < right->GetBound())
                {
                    tour.push_front(right);
                    tour.push_front(left);
                }
                else
                {
                    tour.push_front(left);
                    tour.push_front(right);
                }
#if 0
                cout << "bound: " << tour.front()->GetBound() << endl;
                head->PrintMat();
#endif
            }
            else
            {
                tour.push_front(left);
            }
        }
        else
        {
            tour.push_front(right);
        }
    }
    PrintPath();
}

IX) 打印总体路径

void Task::PrintPath()
{
    cout << "Best path: 1 => ";
    size_t begin = 0;
    while (bestpath[begin] != 0)
    {
        cout << bestpath[begin] + 1 << " => ";
        begin = bestpath[begin];
    }
    cout << "1" << endl;
    cout << "best tour: " << best << endl;
}

X) 主函数

int main()
{
    const auto start_time = high_resolution_clock::now();
    srand(time(NULL));
    const size_t nDim1 = 6, nDim2 = 33;
#if 0
    Task t1("input.txt", nDim1);
    t1.RunTask();
#endif
#if 1
    Task t2(nDim2);
    t2.RunTask();
#endif
    const auto end_time = high_resolution_clock::now();
    PrintTime(start_time, end_time);
}

4、代码总评

Best path: 1 => 29 => 12 => 32 => 23 => 11 => 15 => 24 => 14 => 5 => 8 => 17 => 25 => 26 => 33 => 20 => 18 => 9 => 13 => 16 => 31 => 30 => 3 => 21 => 2 => 19 => 10 => 28 => 27 => 22 => 4 => 7 => 6 => 1
best tour: 239
Time: 19.0621 ms

智能指针设计在一定程度上简化了内存管理,避免了内存泄漏的问题。 一个显着的优点是尽可能多地使用指针,而不是重新创建对象,这样整个程序占用的内存很少。 从算法设计的角度来看,优先考虑左子树(即路径),这使得代码更加高效。

完整代码: https://github.com/KCNyu/Program_MSU-BIT/blob/master/C%2B%2B_LECTURE/TSP/TSP.cpp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值