分支限界法:运输问题

该博客探讨如何利用分支定界算法解决一个实际的运输问题,即在支付不超过1500养路费的前提下,找出从甲城市到乙城市的最短路线。通过Floyd算法获取所有城市间最短路径和费用,然后采用剪枝策略优化搜索过程,减少无效扩展。博主分享了两种版本的实现,包括C++和Python,强调在扩展节点时检查可达性以提升效率。

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

优快云模仿别人的版本

先用弗洛伊德算法求出所有顶点之间的最短路径和最短花费。

先对0节点进行考察,vis置1,如果当前最短路径总长+待考察节点到49的最短路径 > 已知最短路径总长,或者当前最少花费+待考察节点到49的最短花费 >1500,就不扩展这个节点,直接return。

否则对剩下每个未被考察过的节点进行考察,并将其添加至cur_path_list。

如果此时到了49号,也需要判断剪枝,因为在k < 49判断剪枝的时候,当前最短路径总长+待考察节点到49的最短路径,即最好的情况都比已有的糟糕,然而当k == 49的时候,总路径长度是上一轮的当前最短路径总长 + 上一轮的k对应的dist[k][49],你不能保证“上一轮的当前最短路径总长 + 上一轮的k对应的dist[k][49]”一定小于等于已有情况。

凡是进入dfs函数的k,都已经在cur_path_list里面了。(这点很重要,因为每次扩展节点的时候,有一行是cur_path_list[cur_path_number++] = i;)

我的做法,速度快的原因主要是在扩展的时候,判断了子节点可不可达,即应该不等于9999。

#include <stdio.h>
#include <string.h>

int dist[50][50];
int cost[50][50];
int minDist[50][50];
int minCost[50][50];

int cur_path_list[50];
int cur_path_number;

int best_path_list[50];
int best_path_number;
int best_path_length;
int best_path_cost;
int vis[50];

void init()
{
    int i,j;
    FILE *fp = fopen("m1.txt", "r");
    if(fp)
    {
        for(i = 0; i < 50; i++)
        {
            for(j = 0; j < 50; j++)
            {
                fscanf(fp, "%d", &dist[i][j]);
                minDist[i][j] = dist[i][j];
            }
            fscanf(fp,"\n");
        }
        fclose(fp);
    }
    fp = fopen("m2.txt", "r");
    if(fp)
    {
        for(i = 0; i < 50; i++)
        {
            for(j = 0; j < 50; j++)
            {
                fscanf(fp, "%d", &cost[i][j]);
                minCost[i][j] = cost[i][j];
            }
            fscanf(fp,"\n");
        }
        fclose(fp);
    }
    memset(vis, 0, sizeof(vis));
    memset(cur_path_list, 0, sizeof(cur_path_list));
    memset(best_path_list, 0, sizeof(best_path_list));

    cur_path_list[0] = 0;
    cur_path_number = 1;

    best_path_list[0] = 0;
    best_path_list[1] = 49;
    best_path_number = 2;
    best_path_length = dist[0][49];
    best_path_cost = cost[0][49];

    vis[0] = 1;
}

void floyd()
{
    int i, j, k;
    for(k = 0; k < 50; k++)
    {
        for(i = 0; i < 50; i++)
        {
            for(j = 0; j < 50; j++)
            {
                if(minDist[i][k] + minDist[k][j] < minDist[i][j])
                {
                    minDist[i][j] = minDist[i][k] + minDist[k][j];
                }
                if(minCost[i][k] + minCost[k][j] < minCost[i][j])
                {
                    minCost[i][j] = minCost[i][k] + minCost[k][j];
                }
            }
        }
    }
}

int get_current_path_length()
{
    int i;
    int s = 0;
    for(i = 1; i < cur_path_number; i++)
    {
        s += dist[cur_path_list[i - 1]][cur_path_list[i]];
    }
    return s;
}

int get_current_path_cost()
{
    int i;
    int s = 0;
    for(i = 1; i < cur_path_number; i++)
    {
        s += cost[cur_path_list[i - 1]][cur_path_list[i]];
    }
    return s;
}

void dfs(int k)
{
    int i;
    int cur_path_length;
    int cur_path_cost;
    cur_path_length = get_current_path_length();
    cur_path_cost = get_current_path_cost();

    if(k < 49)
    {
        cur_path_length += minDist[k][49];
        cur_path_cost += minCost[k][49];
    }
    if(cur_path_length > best_path_length || cur_path_cost > 1500)
    {
        return;
    }
    if(k < 49)
    {
        for(i = 1; i <= 49; i++)
        {
            if(vis[i] == 0)
            {
                vis[i] = 1;
                cur_path_list[cur_path_number++] = i;
                dfs(i);
                cur_path_number--;
                vis[i] = 0;
            }
        }
    }
    else
    {
        for(i = 0; i < cur_path_number; i++)
        {
            best_path_list[i] = cur_path_list[i];
        }
        best_path_length = cur_path_length;
        best_path_cost = cur_path_cost;
        best_path_number = cur_path_number;
        return;
    }
}

void show()
{
    int i;
    printf("best_path_length = %d\n", best_path_length);
    printf("best_path_cost = %d\n", best_path_cost);
    for(i = 0; i < best_path_number; i++)
    {
        if(i != best_path_number - 1)
        {
            printf("%d -> ", best_path_list[i]);
        }
        else
        {
            printf("%d\n", best_path_list[i]);
        }
    }
}


int main()
{
    int start, finish;
    init();
    floyd();
    start = clock();
    dfs(0);
    finish = clock();
    show();
    printf("%dms\n", finish - start);
    return 0;
}

自己另外加了两个全局变量写的版本

#include <stdio.h>
#include <string.h>

int dist[50][50]; // 原始距离矩阵
int cost[50][50]; // 原始费用矩阵
int minDist[50][50]; //弗洛伊德法求得的距离矩阵
int minCost[50][50]; // 弗洛伊德法求得的费用矩阵

int cur_path_list[50]; // 当前走过的路径,存的节点号
int cur_path_number; // 当前走过的节点数
int cur_path_length; // 当前走过的路径总长度
int cur_path_cost; // 当前的花费

int best_path_list[50]; // 最优路径
int best_path_number; // 最优路径节点数
int best_path_length; // 最优路径总长度
int best_path_cost; // 最少费用
int vis[50]; // 标志位,1表示访问过,0表示没访问过

void init()
{
    int i,j;
    FILE *fp = fopen("m1.txt", "r");
    if(fp)
    {
        for(i = 0; i < 50; i++)
        {
            for(j = 0; j < 50; j++)
            {
                fscanf(fp, "%d", &dist[i][j]);
                minDist[i][j] = dist[i][j];
            }
            fscanf(fp,"\n");
        }
        fclose(fp);
    }
    fp = fopen("m2.txt", "r");
    if(fp)
    {
        for(i = 0; i < 50; i++)
        {
            for(j = 0; j < 50; j++)
            {
                fscanf(fp, "%d", &cost[i][j]);
                minCost[i][j] = cost[i][j];
            }
            fscanf(fp,"\n");
        }
        fclose(fp);
    }
    memset(vis, 0, sizeof(vis));
    memset(cur_path_list, 0, sizeof(cur_path_list));
    memset(best_path_list, 0, sizeof(best_path_list));

    cur_path_list[0] = 0; // 初始化的时候,将0节点放进已经走过的路径
    cur_path_number = 1;
    cur_path_length = 0;
    cur_path_cost = 0;

    best_path_list[0] = 0; // 最开始的最优路径初始化为0 -> 49
    best_path_list[1] = 49;
    best_path_number = 2;
    best_path_length = dist[0][49]; // 初始最优总长度为0到49的长度
    best_path_cost = cost[0][49]; // 初始最优费用为0到49的费用

    vis[0] = 1;
}

void floyd() // 弗洛伊德算法求任意两点之间最短路径
{
    int i, j, k;
    for(k = 0; k < 50; k++)
    {
        for(i = 0; i < 50; i++)
        {
            for(j = 0; j < 50; j++)
            {
                if(minDist[i][k] + minDist[k][j] < minDist[i][j])
                {
                    minDist[i][j] = minDist[i][k] + minDist[k][j];
                }
                if(minCost[i][k] + minCost[k][j] < minCost[i][j])
                {
                    minCost[i][j] = minCost[i][k] + minCost[k][j];
                }
            }
        }
    }
}

void dfs(int k) // 深搜+剪枝
{
    int i;
    if(k < 49)
    {
        // 先判断是否有必要扩展该节点,如果该节点到49的最短路径或最少花费都比已有的最优解差,就没必要扩展节点
        if(cur_path_length + minDist[k][49] > best_path_length || cur_path_cost + minCost[k][49] > 1500)
        {
            return;
        }
        // 对该节点附近可达且未被访问的节点进行考察
        for(i = 1; i <= 49; i++)
        {
            if(vis[i] == 0 && dist[k][i] != 9999)
            {
                cur_path_length += dist[k][i];
                cur_path_cost += cost[k][i];
                vis[i] = 1;
                cur_path_list[cur_path_number++] = i;
                dfs(i);
                // 弹栈
                cur_path_number--;
                vis[i] = 0;
                cur_path_length -= dist[k][i];
                cur_path_cost -= cost[k][i];
            }
        }
    }
    else
    {
        // 先判断是否比最优解差,如果差就剪枝
        if(cur_path_length > best_path_length || cur_path_cost > 1500)
        {
            return;
        }
        // 保存最优解并返回
        for(i = 0; i < cur_path_number; i++)
        {
            best_path_list[i] = cur_path_list[i];
        }
        best_path_length = cur_path_length;
        best_path_cost = cur_path_cost;
        best_path_number = cur_path_number;
        return;
    }
}

void show()
{
    int i;
    printf("最优路径的总长度 = %d\n", best_path_length);
    printf("最优路径对应的费用 = %d\n", best_path_cost);
    printf("最优路径为:");
    for(i = 0; i < best_path_number; i++)
    {
        if(i != best_path_number - 1)
        {
            printf("%d -> ", best_path_list[i]);
        }
        else
        {
            printf("%d\n", best_path_list[i]);
        }
    }
}

int main()
{
    int start, finish;
    init();
    floyd();
    start = clock();
    dfs(0);
    finish = clock();
    show();
    printf("%dms\n", finish - start);
    return 0;
}

自己写的版本深搜剪枝的时间快了近10ms。

再加一个Python版本

# -*- coding: utf-8 -*-

dist = []
cost = []
minDist = []
minCost = []

cur_path_list = []
cur_path_cost = 0
cur_path_length = 0

best_path_list = []
best_path_cost = 0
best_path_length = 0

vis = [False] * 50

def init():
    global minDist
    global minCost
    global best_path_length
    global best_path_cost
    try:
        f = open('m1.txt', 'r')
        for line in f.readlines():
            lst = list(map(int, line.strip().split('\t')))
            dist.append(lst)
    finally:
        f.close()

    try:
        f = open('m2.txt', 'r')
        for line in f.readlines():
            lst = list(map(int, line.strip().split('\t')))
            cost.append(lst)
    finally:
        f.close()

    minDist = [ele[:] for ele in dist]
    minCost = [ele[:] for ele in cost]
    best_path_list.extend([0, 49])
    best_path_length = dist[0][49]
    best_path_cost = cost[0][49]
    cur_path_list.append(0)
    vis[0] = True

def floyd():
    for k in range(0, 50):
        for i in range(0, 50):
            for j in range(0, 50):
                if(minDist[i][k] + minDist[k][j] < minDist[i][j]):
                    minDist[i][j] = minDist[i][k] + minDist[k][j]
                if(minCost[i][k] + minCost[k][j] < minCost[i][j]):
                    minCost[i][j] = minCost[i][k] + minCost[k][j]

def dfs(k):
    global cur_path_list
    global cur_path_length
    global cur_path_cost
    global best_path_list
    global best_path_length
    global best_path_cost
    if(k < 49):
        if(cur_path_length + minDist[k][49] > best_path_length or cur_path_cost + minCost[k][49] > 1500):
            return
        for i in range(1, 50):
            if(vis[i] == False and dist[k][i] != 9999):
                cur_path_length += dist[k][i]
                cur_path_cost += cost[k][i]
                cur_path_list.append(i)
                vis[i] = True
                dfs(i)
                vis[i] = False
                cur_path_length -= dist[k][i]
                cur_path_cost -= cost[k][i]
                cur_path_list.pop()
    else:
        if(cur_path_length > best_path_length or cur_path_cost > 1500):
            return
        best_path_list = cur_path_list[:]
        best_path_length = cur_path_length
        best_path_cost = cur_path_cost
        return

def show():
    print('最小距离为', best_path_length)
    print('最小举例对应的花费为', best_path_cost)
    print('最佳路径为', ' -> '.join(map(str, best_path_list)))

init()
floyd()
dfs(0)
show()

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值