最小重量机器设计问题(C++)

编译环境:Dev-C++

运用回溯法和分支限界法解决最小重量机器设计问题,并分析时间复杂性。


算法描述及步骤

问题描述:

        设某一机器由n个部件组成,每种部件都可以从m个不同的供应商处购得。设wij是从供应商j处购得的部件i的重置,cij是相应的价格。设计一个优先队列式分支限界法,给出总价格不超过d的最小重量机器设计。

算法设计:

对于给定的机器部件重量和机器部件价格,计算总价格不超过d的最小重量机器设计。

数据输入:

第1行有3个正整数n,m和d。接下来的2n行,每行n个数。前n行是c,后n行是w。


算法描述:

回溯法:

        用回溯法求解问题时,应明确定义问题的解空间。问题的解空间至少应包含问题的一个(最优)解。该空间包含对变量的所有可能赋值即供应商与部件数组。

        确定了解空间的组织结构后,回溯法从开始结点(根节点)出发,以深度优先方式搜索整个解空间。这个开始结点成为活结点,同时成为当前扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,即节点到叶子结点重量和大于当前最小重量或该节点到叶子节点价格和超过d时,则当前扩展结点就成为死结点。此时,应回溯至最近的一个活动结点处,并使这个活动结点成为当前的扩展结点。.

        回溯法以这种工作方式递归地在解空间中搜索,直至找到所要求的解或者解空间中已无活动结点为止,遍历完之后的最新最小重量即为最优值。

分支限界法:

        最小机器重量设计问题的优先队列式分支限界法用优先队列存储活结点表。

      优先队列中的优先级定义为:活结点m对应的子树最小重量bestweight = node.weight + weightlist[t][i]。weight为该节点重量,weightlist为重量数组列表。从根节点开始遍历,遍历该扩展节点的所有子节点,对于符合要求的子节点即插入优先队列中

        其中剪枝函数为bestweight < Bestweight且bestprice <= D。限界函数即该节点到叶子结点重量和小于当前最小重量;约束函数即该节点到叶子节点价格和不超过d。最小值和遍历完之后成为死节点。此时从优先队列中将当先优先级最高即重量最小的节点向下遍历,当遍历到叶子节点,若当前节点重量小于目前最小重量即更新最优值。

        回溯法的求解目标是找出解空间中满足约束条件的所有解,以深度优先的方式搜索解空间树,当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。

        分支限界法的求解目标则是找出解空间中满足约束条件的一个解,以广度优先或以最小耗度优先的方式搜索解空间树。一个活结点只有一次机会成为扩展结点,一旦成为扩展结点,就一次性产生所有儿子结点,并舍弃不可行解或非最优解的子结点。其余儿子结点加入活结点表。此后,从活动结点表中取出下一结点成为当前扩展结点,并重复以上过程。 


调试过程及结果

在开始时,最小堆是打算用链表来实现,但在具体实践时出现了很多问题,所以将最小堆改用优先队列来实现,这样每次加入的活结点能够根据设定的优先级进行自动排序。

在分支限界法里,对优先级的判断中,当两个结点计算出的可能的最小重量相同时,应该设定层次更深的结点优先级更高,在一开始时忽略了这一点,导致测试时出现结果错误等问题,把优先级的判定加上层次这一条件后,结果正确。

实验结果为:

回溯法:

分支限界法:


源代码:

回溯法

//回溯法解最小重量机器设计问题 
#include<stdio.h>
#define maxn 110

int w[maxn][maxn];
int c[maxn][maxn];
int bestx[maxn];
int x[maxn];
int n, m, d;
int cw = 0, cc = 0, bestw = 0x3f3f3f3f;

void Backtrack(int t) {
    if (t > n) {
        bestw = cw;
        for (int i = 1; i <= n; i++)
            bestx[i] = x[i];
        //return;
    }else {
        for (int i = 1; i <= m; i++) {
            if (cc + c[t][i] <= d && cw + w[t][i] < bestw) {
                x[t] = i;
                cc += c[t][i];
                cw += w[t][i];
                Backtrack(t + 1);
                cc -= c[t][i];
                cw -= w[t][i];
            }
        }
    }
}

int main() {
    puts("请依次输入部件数,供应商数,限定价格:");
    scanf("%d %d %d",&n,&m,&d);
    puts("请输入各部件的在不同供应商的重量:");
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d",&w[i][j]);
    puts("请输入各部件的在不同供应商的价格:");
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d",&c[i][j]);
    Backtrack(1);
    printf("最小重量为:%d\n",bestw);
    puts("每个部件的供应商:");
    for (int i = 1; i <= n; i++)
    	if(i==1) printf("%d",bestx[i]); 
        else printf(" %d",bestx[i]);
    return 0;
}

 分支界限法

//分支限界法解最小重量机器设计问题 
#include <bits/stdc++.h>
using namespace std;

int n;    //部件数量
int m;    //供应商数量
int d;    //价格上限
int bestw;   //最小的重量
int** c = NULL;    //二维数组,每个部件不同商家的价格
int** w = NULL;     //二维数组,每个部件不同商家的重量
//每一个部件的信息
class Node {
public:
    int weight;        //当前已选机器的重量和
    int val;           //当前已选机器的价值和
    int source;        //哪个供货商
    int level;         //第几层,也代表了第几个部件
    int priority;      //优先级
    Node* father;
};

Node* leaf;//叶子结点
void Input() {
    puts("请依次输入部件数,供应商数,限定价格:");
    scanf("%d %d %d",&n,&m,&d);
    w = (int**)malloc(sizeof(int*)*(n + 1));
    c = (int**)malloc(sizeof(int*)*(n + 1));
    for (int i = 1; i <= n; i++) {
        w[i] = (int*)malloc(sizeof(int)*(m + 1));
        c[i] = (int*)malloc(sizeof(int)*(m + 1));
    }
    leaf = NULL;
    puts("请输入各个部件在各个供应商处购买的价格:");
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d",&c[i][j]);
    puts("请输入各个部件在各个供应商处购买的重量:");
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d",&w[i][j]);
}

//优化的优先级设定
bool operator<(Node a, Node b)  //level按照减序
{
    if (a.priority == b.priority)return a.level < b.level;  //如果重量相同,选择level大的。
    return a.priority > b.priority;//否则,重量小的先出队
}

//计算当前节点的优先级
void QueuePriority(Node a) {
    int currentMinW;
    a.priority = a.val;
    //int temp_min_c = INT_MAX;
    for (int i = a.level + 1; i <= n; i++)//选出剩余的部件在售货商中购买的最小质量,就是选择每一层最小的质量
    {
        currentMinW = 999999;
        for (int j = 1; j <= m; j++)  //每一层找最小的
        {
            currentMinW = currentMinW < w[i][j] ? currentMinW : w[i][j];//从m个商家中选择当层重量最小的
        }
        a.priority += currentMinW;
    }
}

//约束函数
bool constraint(Node* pNode, int i) {
    return pNode->val + c[pNode->level + 1][i] <= d || pNode->weight + w[pNode->level + 1][i] <= bestw;
}

//创建节点
Node createNode(int level, Node* father, int source, int val, int weight) {
    Node newNode{};
    newNode.level = level;//层次加1
    newNode.father = father;
    newNode.source = source;
    newNode.val = val;
    newNode.weight = weight;
    return newNode;
}

void MinWeightMachine() {
    int i, j;
    bestw = 999999;
    Node initial{};
    initial = createNode(0, NULL, 0, 0, 0);
    QueuePriority(initial);      //计算优先级
    priority_queue<Node> heap;   //用优先队列,建立一个最小堆。加入进去就会自动排好序的。
    heap.push(initial);
    while (!heap.empty()) {
        Node* pNode = new Node(heap.top());
        heap.pop();//队首元素作为父节点出队,即优先级值小的活结点先扩展
        if (pNode->level == n)//到达叶节点,不能扩展 ,得到一个解
        {
            if (pNode->weight < bestw)   //更新
            {
                bestw = pNode->weight;
                //MinValue  = pNode ->val;
                leaf = pNode;   //记录是最后是哪个结点数据,便于回溯找最优解
            }
        }
        else {
            for (i = 1; i <= m; i++)//扩展结点,依次选择每个售货商,每次都是m叉树
            {
                //可行性剪枝和限界剪枝
                if (constraint(pNode, i)) {
                    Node newNode{};//生儿子结点
                    newNode = createNode(pNode->level + 1, pNode, i, pNode->val + c[pNode->level + 1][i], pNode->weight + w[pNode->level + 1][i]);
                    QueuePriority(newNode);     //计算优先值
                    heap.push(newNode);//儿子入队
                }
            }
        }
    }

}

void Output() {
    printf("求得的最小重量为:%d\n",bestw);
    int* result = (int*)malloc(sizeof(int)*(n + 1));
    for (int i = n; i >= 1; i--) {
        result[i] = leaf->source;//从最后叶子结点回溯到根节点
        leaf = leaf->father;
    }
    puts("各个部件的供应商分别为:");
    for (int i = 1; i <= n; i++)
        if(i==1) printf("%d",result[i]);
        else printf(" %d",result[i]);
    putchar('\n');
}
int main() {
    Input();
    MinWeightMachine();
    Output();
    return 0;
}

分支界限法(Branch and Bound)是一种求解最优化问题的方法,它通过将问题分解成若干子问题,并且通过限定问题的上、下界,来避免计算全部可能的解空间,从而加速计算时间。在最小重量机器设计问题中,我们希望找到一个最小重量机器,满足一定的性能要求。 具体来说,最小重量机器设计问题可以被定义为:给定一组可行解空间$S$和一个目标函数$f(x)$,其中$x$是一个$n$维向量,表示机器设计参数向量。我们的目标是找到一个向量$x^*$,使得$f(x^*)$取得最小值,并且$x^*$满足一定的约束条件,例如机器体积、强度、成本等。 使用分支界限法求解最小重量机器设计问题的步骤如下: 1. 确定目标函数$f(x)$和约束条件。 2. 确定问题的可行解空间$S$。 3. 初始化一个当前最优解$x^*$和一个当前最优值$f(x^*)$,并将它们的初值设为无穷大。 4. 选择一个分支变量$x_i$,将$x_i$的取值范围分成若干个子区间,即$x_i\in[l_i,u_i]$,其中$l_i$和$u_i$是$x_i$的下界和上界。 5. 对于每个子区间,计算该子区间的上、下界,并将该子问题加入到一个待解问题列表中。 6. 从待解问题列表中选择一个问题进行求解。如果该问题的下界大于当前最优值,则舍弃该问题;否则,继续执行步骤4-6,直到待解问题列表为空。 7. 当待解问题列表为空时,返回当前最优解$x^*$和最优值$f(x^*)$。 在这个算法中,分支变量的选择非常重要。一般来说,我们希望选择具有最大分支因子(即可选取的子区间数量最多)的变量作为分支变量,这可以使得算法的效率更高。 最小重量机器设计问题是一个NP难问题,因此使用分支界限法可以在有限时间内找到较好的解。例如,在设计飞机机翼的过程中,可以使用分支界限法来优化机翼形状,以达到最小重量和最佳性能的目标。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瓦特的代码小屋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值