分支定界算法求解带有约束条件的最短路径问题

知识点:分支定界; Dijkstra; c++读取txt文件到二维数组

这个题目是北航研究生算法课的,因为以前主要写java,用c++写作业的时候遇到了一些读文件啊,深度优先遍历图的问题啥的,所以记录下来。

题目:
Assignment 2
用分支定界算法求以下问题:
某公司于乙城市的销售点急需一批成品, 该公司成品生产基地在甲城市。甲城市与乙城市之间共有 n 座城市,互相以公路连通。甲城市、乙城市以及其它各城市之间的公路连通情况及每段公路的长度由矩阵M1 给出。每段公路均由地方政府收取不同额度的养路费等费用, 具体数额由矩阵 M2 给出。请给出在需付养路费总额不超过 1500 的情况下,该公司货车运送其产品从甲城市到乙城市的最短运送路线。
具体数据参见文件:
M1.txt: 各城市之间的公路连通情况及每段公路的长度矩阵(有向图); 甲城市为城市 Num.1,乙城市为城市 Num.50。
M2.txt: 每段公路收取的费用矩阵(非对称)。
点此下载可执行程序和源代码以及m1.txt,m2.txt

  • 主要思路:
    • 遍历:利用栈结构,通过深度优先搜索遍历路径
    • 定界:每一个最短路径值更小的可行解确定了新的下界
    • 剪枝:先利用Dijkstra算法计算每个城市到目的城市的最短路径和最小花费,当算法运行到某个城市后,计算在栈里的城市的路径长度 + 这个城市到目的城市的最短路径,以及在栈里的城市的路径花费 + 这个城市到目的地的最小花费, 与当前最优的可行解以及约束条件进行比较,从而达到剪枝的目的

平心而论,代码的一般……

代码:

// Assignment_2.cpp : Defines the entry point for the console application.
/*
 * 主要思路: 
 *     遍历:利用栈结构,通过深度优先搜索遍历路径
 *     定界:每一个最短路径值更小的可行解确定了新的下界
 *     剪枝:先利用Dijkstra算法计算每个城市到目的城市的最短路径和最小花费,当算法运行到某个城市后,
 *          计算在栈里的城市的路径长度 + 这个城市到目的城市的最短路径,以及在栈里的城市的路径花费 + 这个城市到目的地的最小花费,
 *          与当前最优的可行解以及约束条件进行比较,从而达到剪枝的目的
 */

#include "stdafx.h"

#include <iostream>
#include <fstream>
#include <deque>
#include <time.h>

using namespace std;

#define MAX_NODE 60 // 最大节点数
#define MAX_INT 9999 // 定义最大整数
#define MAX_COST 1500 // 定义最大花费

int n1, n2; // 分别表示矩阵的行和列
deque<int> queueMinPath; // 记录已得到的最短路径
int minPath; // 记录最短路径长度
int costOfMinPath; // 记录最短路径的花费

int main(int argc, char* argv[]) {

    clock_t startTime, endTime;
    startTime = clock();

    cout << endl << "程序开始运行" << endl << endl;   

    int** readTxtToArrayWithoutKnowRowOrColumn(const char* strPath); // 把txt文件读进二维数组
    void findPathWithDFS(int **m1, int **m2, int fn, int tn); // 深度优先

    int pIn[MAX_INT] = { 0 }; // 记录某个城市是否在当前路径

    int** m1 = 0, **m2 = 0; // 分别保存m1.txt和m2.txt的矩阵

    cout << "从txt文件读取数据(请把txt文件和可执行文件放在同一路径下):" << endl << endl;

    m1 = readTxtToArrayWithoutKnowRowOrColumn("m1.txt");

    m2 = readTxtToArrayWithoutKnowRowOrColumn("m2.txt");

    cout << "行数: " << n1 << ", 列数: " << n2 << endl << endl;

    for (int i = 0; i < n1; i++) {
        for (int j = 0; j < n2; j++) {
            if (m1[i][j] == MAX_INT) {
                m2[i][j] = MAX_INT;
            }
        }
    }

    minPath = MAX_INT;
    findPathWithDFS(m1, m2, 0, n1 - 1);

    cout << "满足条件的最短路径长度为: " << minPath << endl << endl;
    cout << "满足条件的最短路径的花费为: " << costOfMinPath << endl << endl;
    cout << "最短路径为: " << endl << endl;
    for (int is = 0; is < queueMinPath.size(); is++) {
        cout << "\t" << queueMinPath[is] + 1;
    }
    cout << endl << endl;

    cout << "程序运行结束" << endl << endl;
    endTime = clock();
    cout << "总共耗时: " << (double)(endTime - startTime) / CLOCKS_PER_SEC << " 秒" << endl << endl;

    cout << "按 <Enter> 即可退出程序" << endl;
    getchar();

    return 0;
}

void findPathWithDFS(int **m1, int **m2, int fn, int tn) {
    int calculateShortestByDijkstra(int **m1, int fn, int tn); // 根据a矩阵计算从fn到tn的最短距离或者最小花费
    int p1[MAX_NODE] = { 0 }, c1[MAX_NODE] = { 0 }; // 分别保存各个城市到目的地的最短路径和最小花费
    int pMin = MAX_INT, cNow = MAX_INT; // 分别记录当前到的路径的最小路径长度和当前花费

    cout << "先用Dijkstra算法求出每个城市到目的城市的最短路径和最小花费" << endl << endl;
    // 各个城市到达目的城市的最短路径
    for (int i = 0; i < n1; i++) {
        p1[i] = calculateShortestByDijkstra(m1, i, n1 - 1);
    }
    // 各个城市到达目的城市的最小花费
    for (int i = 0; i < n1; i++) {
        c1[i] = calculateShortestByDijkstra(m2, i, n1 - 1);
    }

    cout << "再用分枝定界算法求满最大花费不超过" << MAX_COST << "的最短路径" << endl << endl;
    deque<int> stackPathNode;
    stackPathNode.push_back(fn);
    int nPath[MAX_NODE] = { 0 };
    nPath[fn] = 1;
    int lastOut = -1;
    int tag = 0; // 标记是否找到下一个城市
    int pathLengthNow = 0; // 当前已经走到的路径长度
    int pathCostNow = 0; // 当前已经走到的路径的花费
    int nNow;
    while (!stackPathNode.empty()) { // 栈为空
        nNow = stackPathNode.back();
        for (int i = 0; i < n1; i++) {
            if (nPath[i] == 1 || i <= lastOut || nPath[i] == 1)
                continue;
            if (m1[nNow][i] < MAX_INT) {
                // 根据当前能够获取的最短路径长度和最小花费确定是否剪枝
                if (pathLengthNow + p1[i] >= minPath || pathCostNow + c1[i] > MAX_COST) {
                    // 当前路径的最优解没有已有的可行解好,或者当前路径的最小花费超过 MAX_COST 
                    continue;
                }
                if (pathLengthNow + m1[nNow][i] >= minPath || pathCostNow + m2[nNow][i] > MAX_COST){
                    continue;
                }
                if (i == tn) {
                    // 找到一条更好的路径,如下
                    minPath = pathLengthNow + m1[nNow][i];
                    costOfMinPath = pathCostNow + m2[nNow][i];
                    pathLengthNow = minPath;
                    pathCostNow = costOfMinPath;
                    stackPathNode.push_back(i);
                    queueMinPath = stackPathNode;
                    break;

                }
                else {
                    // 当前城市有下一个城市可以寻得最优解,下一个城市入栈
                    tag = 1;
                    stackPathNode.push_back(i);
                    nPath[i] = 1;
                    pathLengthNow += m1[nNow][i];
                    pathCostNow += m2[nNow][i];
                    lastOut = -1;
                    break;
                }
            }

        }

        if (tag == 0) {
            // 当前城市没有下一个城市可以寻得最优解,当前城市出栈
            lastOut = stackPathNode.back();
            stackPathNode.pop_back();
            if (stackPathNode.empty()) {
                break;
            }
            nNow = stackPathNode.back();
            nPath[lastOut] = 0;
            pathLengthNow -= m1[nNow][lastOut];
            pathCostNow -= m2[nNow][lastOut];
        }
        tag = 0;
    }
}

int** readTxtToArrayWithoutKnowRowOrColumn(const char* strPath) { // 把txt文件读进二维数组
    int** a = new int*[MAX_NODE];
    for (int i = 0; i < MAX_NODE; i++) {
        a[i] = new int[MAX_NODE];
    }
    int nTmp = 0;
    n1 = 0;
    n2 = 0;
    ifstream is(strPath);
    if (!is) {
        cout << "open file error!" << endl;
    }
    else {
        char p;
        int num;
        int j = 0;
        while (is.get(p)) { // 判断是否到达文末
            do {
                if (p == '\n') {
                    n1++; // 统计行数
                    if (n2 == 0) {
                        n2 = nTmp; // 实际上只统计了第一行的列数
                    }
                    j = 0;
                    // cout << endl; // 一行之后输出一个回车符
                }
            } while (isspace((int)p) && is.get(p));
            if (!is)
                break;
            nTmp++; // 统计列数
            is.putback(p); // 如果前面读入的不是空格或者回车符,则把刚才读入的字符返回文件流
            is >> num;
            // cout << num << "\t";
            a[n1][j++] = num;
        }
    }
    is.close();
    return a;
}

int calculateShortestByDijkstra(int **m1, int fn, int tn) { // 根据距离矩阵计算从fn到tn的最短距离或者最小花费

    if (fn == tn) {
        return 0;
    }

    int p[MAX_NODE] = { 0 }; // 记录当前路径长度
    int pt[MAX_NODE] = { 0 }; // 记录当前路径是否是最短路径,1表示是

    for (int i = 0; i < MAX_NODE; i++) {
        if (i != fn)
            p[i] = MAX_INT;
    }

    p[fn] = 0; // nf到nf的路径为0
    pt[fn] = 1; // 当前nf到nf的路径是最短路径

    for (int i = 0; i < n1 - 1; i++) { // 求n个节点中1个节点到另一个节点的最短路径最多需要n-1步
        for (int j = 0; j < n1; j++) {
            if (pt[j] != 1) {
                // 还没有求出第nf第j个节点的最短路径
                for (int k = 0; k < n1; k++) {
                    // 计算nf到第j个节点的路径
                    if (pt[k] == 1) {
                        // k是已经求出最短路径的节点
                        if (m1[k][j] < MAX_INT) {
                            // 如果k到j有路径
                            if ((p[k] + m1[k][j]) < p[j]) {
                                p[j] = p[k] + m1[k][j];
                            }
                        }
                    }

                }
            }
        }

        int tmp = MAX_INT;
        for (int j = 0; j < n1; j++) {
            if (pt[j] != 1) {
                if (p[j] < tmp) {
                    tmp = p[j];
                }
            }
        }

        for (int j = 0; j < n1; j++) {
            if (pt[j] != 1) {
                if (p[j] == tmp) {
                    pt[j] = 1;
                    if (j == tn) {
                        return p[j];
                    }
                }
            }
        }
    }

    return -1;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值