分支限界法解决0/1背包问题(C语言实现)

分支限界法的基本思想

分支限界法的基本思想是,在分支结点上,预先分别估算沿着它的各个儿子结点向下搜索的路径中,目标函数可能取得的“界”,然后把这些儿子结点和它们可能所取得的“界”保存在一张结点表中,再根据题目要求选择表中“界”最大或最小的结点向下搜索。(一般用优先队列来处理这张结点表)这样当搜索到一个叶子结点时,如果该结点所估算的目标函数值就是结点表中的最大或者最小值,那么沿叶子结点到根结点的路径所确定的解就是问题的最优解,叶子结点的目标函数值就是问题的最大值或最小值。

参考:《算法分析与设计(第三版)》(郑宗汉、郑晓明编著)

解决背包问题的基本思路

首先要将物品按重量价值比排序。

同样还是一棵二叉树,沿左孩子则选,右孩子则不选。

初始化最大上界bound = 0。对于一个结点,计算其理想状态下可能获得的最大上界bound(理想状态也就是把物体看成可分割),将结点按bound递减顺序存入优先队列中;然后队头出队(也就是bound最大的结点),对于其左孩子和右孩子分别计算bound,重复上述步骤。如果到达叶子结点,且该叶子结点的bound比当前bound大,则更新bound值。如果队列内的一些结点的值小于bound,则无需沿着小于bound的值的结点继续搜索。(随着二叉树搜索深度增加,bound值越来越接近真实值),那么如果当前结点bound值都比另一条分支上的叶子结点bound值小,那继续搜索只会更小)

源程序代码

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<malloc.h>
#include<Windows.h>
#define N 100

int n;
int M;
typedef struct {
    float weight;
    float value;
    float x;// 价值重量比
    int num;//排序前的初始序号
    int flag;
}Goods[N];

typedef struct BiTNode {
    Goods g;
    float bound;//上界
    float w;//已选道路重量
    float v;
    int k;//搜索深度
}BiTNode;

BiTNode* qbase[N];
int choose[N];//物品选择情况
int rear = 0;
int front = 0;//队列指针

//初始化
void Init(Goods goods) {
    printf("输入物品数量和背包容量:");
    scanf("%d %d", &n, &M);
    for (int i = 0; i < n; i++) {
        printf("输入第%d个物品的重量和价值:", i + 1);
        scanf("%f %f", &goods[i].weight, &goods[i].value);
        goods[i].x = goods[i].value / goods[i].weight;
        goods[i].num = i;
        goods[i].flag = 0;
    }

}

//按物品价值重量比递减排序
void sort(Goods goods) {
    Goods temp;
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            if (goods[j].x < goods[j + 1].x) {
                temp[0] = goods[j + 1];
                goods[j + 1] = goods[j];
                goods[j] = temp[0];
            }
        }
    }
}

//入队
void Q_Insert(BiTNode* qbase[], BiTNode* xnode) {
    qbase[rear] = xnode;
    rear++;
}


//将最大值放在队首
BiTNode* Max_Q(BiTNode* qbase[]) {
    float max = 0;
    for (int i = front;i < rear;i++) {
        if (qbase[i]->bound > max) {
            max = qbase[i]->bound;
        }
    }
    for (int i = front;i < rear;i++) {
        if (qbase[i]->bound == max) {
            BiTNode* xnode = new BiTNode;
            xnode = qbase[i];
            qbase[i] = qbase[front];
            qbase[front] = xnode;
        }
    }
    return qbase[front];
}



//计算结点上界
void knap_bound(BiTNode* node, int M, int n) {
    float w = node->w;
    float v = node->v;
    int m = node->k;
    if (node->w > M) {
        //已选道路重量如果已经大于背包重量
        node->bound = 0;
    }
    else {
        while (m < n && node->w + node->g[m].weight <= M) {
            //按实际情况(不可分割)将背包容量尽可能达到最大值
            //也就是说加下一个物品超重,但不加则不超重
            w += node->g[m].weight;
            v += node->g[m].value;
            m++;
        }
        if (m < n) {
            //对物品进行分割
            //背包剩余容量*物品的价值重量比
            //就是该物品一部分(这一部分刚好满足背包装满)的价值
            node->bound = v + (M - w) * node->g[m].x;
        }
        else {
            node->bound = v;
        }
    }
}


//背包问题
float KnapSack(Goods goods) {
    BiTNode* xnode, * ynode, * znode;
    float bound = 0;
    xnode = new BiTNode;
    xnode->w = xnode->v = 0;
    xnode->k = 0;
    for (int i = 0;i < n;i++) {
        //将物品信息复制到结点中
        xnode->g[i] = goods[i];
    }

// ----------------初始化结束-----------------
    while (xnode->k < n) {
        ynode = new BiTNode;
        *ynode = *xnode;
        ynode->g[ynode->k].flag = 1;//选
        ynode->w += goods[ynode->k].weight;
        ynode->v += goods[ynode->k].value;
        ynode->k += 1;//搜索深度加一
        knap_bound(ynode, M, n);
        if (ynode->bound > bound) {
            Q_Insert(qbase, ynode);
            if (ynode->k == n) {
            //叶子结点则更新bound
                bound = ynode->bound;
            }
        }
        else {
            delete ynode;
        }
        znode = new BiTNode;
        *znode = *xnode;
        znode->g[znode->k].flag = 0;//不选
        znode->k += 1;
        knap_bound(znode, M, n);
        if (znode->bound > bound) {
            Q_Insert(qbase, znode);
            if (znode->k == n) {
                bound = znode->bound;
            }
        }
        else {
            delete znode;
        }
        delete xnode;//不要忘记释放结点空间
        xnode = Max_Q(qbase);//优先对列中bound最大的结点赋值给xnode
        front++;//队头出队,front++
    }
    
    float v = xnode->v;//输出最优解
    for (int i = 0;i < n;i++) {
        if (xnode->g[i].flag != 0) {
            //输出最优解向量
            choose[xnode->g[i].num] = 1;//num是未排序前的原始序号
        }
    }
    delete xnode;
    return v;
}

int main() {
    Goods goods;
    Init(goods);
    sort(goods);
    printf("选择物品情况:");
    float value = KnapSack(goods);
    for (int i = 0;i < n;i++) {
        printf("%d ", choose[i]);
    }
    printf("\n最大价值:");
    printf("%.1f", value);
    system("pause");
    return 0;
}

运行结果

 ps:由于笔者水平有限,并且也是刚刚学习分支限界法,花了很长的时间写+调试,若有错误,敬请指正。

0/1背包问题是一个经典的组合优化问题,它的目标是在给定的一组物品中选择尽可能多的物品放入一个容量为C的背包中,使得这些物品的总重量不超过C,同时总价值最大。 分支限界法是一种常用的求解0/1背包问题的方法。其基本思想是将问题分解成若干个子问题,并对每个子问题进行求解,直到找到最优解为止。 以下是分支限界法求解0/1背包问题的步骤: 1. 定义节点数据结构。每个节点包括以下属性:当前已选物品的重量、当前已选物品的价值、当前已选物品的数量、剩余可选物品的集合。 2. 定义优先级队列。用于存储待扩展的节点,按照节点价值的上界(即当前已选物品的总价值加上剩余可选物品的价值上界)从大到小排序。 3. 初始化根节点。将根节点的属性设置为:当前已选物品的重量、价值和数量均为0,剩余可选物品的集合为所有物品。 4. 进入循环。如果优先级队列不为空,则取出队首节点进行扩展。对于每个节点,分别考虑选择下一个物品和不选择下一个物品两种情况,生成两个子节点,并计算它们的上界。如果子节点表示的问题仍然有解,则将它们加入优先级队列中。 5. 判断是否找到最优解。如果当前已选物品的价值加上剩余可选物品的价值上界小于已知的最优解,则说明当前节点不可能得到更优的解,直接舍弃。 6. 返回最优解。当优先级队列为空时,说明已经遍历完所有节点,返回最优解。 分支限界法是一种非常高效的求解0/1背包问题的方法,时间复杂度为O(2^n),其中n为物品数量。它可以用于求解大规模的组合优化问题,例如旅行商问题、集合覆盖问题等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值