【算法笔记】分支限界专题

分支限界

整体结构

本质上感觉还是遍历解树+剪枝,但是配合优先队列使用以后可以更好的找到最优解。

例题

P8011 ⾛迷宫

对于迷宫问题,某一节点的关联节点指的是它四个方向上相邻的节点。

要利用flag数组确保不会重复访问。

 void bfs(){
     //1、初始化队列queue ,将第一个节点放入队列 
     t++; 
     q[t].x = 1;
     q[t].y = 1;
     q[t].step = 0;
     flag[1][1] = true;
     //2.循环遍历队列
     while(h <= t){ //队列不空 
         // 3.取出队头,存入cur
         Node cur = q[h]; 
         h++;
         // 4. 利用产生式规则,拓展cur的关联节点入队
         for(int i = 0; i < 4; i++) { //四个方向拓展
             int nx = cur.x + z[0][i];
             int ny = cur.y + z[1][i];
             if(nx < 1 || nx > n || ny < 1 || ny > m) continue;//越界 
             if(a[nx][ny] == 1) continue; //有墙 
             if(flag[nx][ny]) continue; //走过 
             // 如果找到答案,结束搜索
             if(nx == n && ny == m){
                 ans = cur.step + 1;//拓展出的节点到了终点,所以要把最后一步加上
                 return ;
             }
             //既没跳过也没到终点,那就先存起来
             flag[nx][ny] = true;//拓展的过程中就已经用过
             t++;//存起来只是为了继续拓展
             q[t].x = nx;
             q[t].y = ny;
             q[t].step = cur.step + 1;
         }
     } 
 } 

P8012 01背包

核心在于选和不选两个分支的剪枝,总体框架依然是取队头-判断剪枝-拓展-判断剪枝-入队。

 void bfs(){
     //1、初始化队列queue ,将第0个物品放入队列 
     t++;
     q[t].i = 0; //第0个物品 
     q[t].cp = 0;
     q[t].cv = 0;
     bestp = 0; 
     //2.循环遍历队列
     while(h <= t){ //队列不空 
         // 3.取出队头,存入cur
         Node cur = q[h];
         h++;
         int n_i = cur.i+1;
         if(n_i > n) continue;//物品用完了
         // 4. 利用产生式规则,拓展cur的关联节点入队
         // 选择下一个物品,并入队 
         if(cur.cv + items[n_i].volume <= V){ //装不下则剪掉选择该物品的分支
             t++;
             q[t].i = n_i;
             q[t].cp = cur.cp + items[n_i].price;
             q[t].cv = cur.cv + items[n_i].volume;
             bestp = max(bestp,q[t].cp);
         } 
         // 计算上界
         double b = bound(n_i + 1, cur.cv, cur.cp);
         // 不选择下一个物品,且上界大于当前最佳解时,才将节点入队
         if (b > bestp) {
             t++;
             q[t].i = n_i;
             q[t].cp = cur.cp;
             q[t].cv = cur.cv;   
         }
     } 
 } 

把手动固定队列换成优先队列的版本。由于优先队列会把价值密度更高的物品推到前面,所以程序会优先沿着局部最优的路径往下走,更便于找到最优路径。

 // 更快找到最优解:先探索最有希望的节点(即优先级最高的节点)
 // 更有效的剪枝:优先处理那些最有可能导致最优解的节点。
 double bound(int i, int cv, int cp) { 
     double maxp = cp;
     int totv = cv;
     // 使用贪心策略继续添加物品
     while (i <= n && totv + items[i].volume <= V) {
         totv += items[i].volume;
         maxp += items[i].price;
         i++;
     }
     // 如果还有剩余空间,则按比例取最后一个物品的价值
     if (i <= n) {
         maxp += (V - totv) * (items[i].density);
     }
     return maxp;  
 }
 void bfs(){
     //1、初始化队列queue ,将第0个物品放入队列 
     Node cur;
     priority_queue<Node> p_q;
     cur.i = 0; //第0个物品 
     cur.cp = 0;
     cur.cv = 0;
     cur.ub  = bound(cur.i+1, cur.cv, cur.cp);//计算上界值 
     p_q.push(cur); //插入优先队列 
     //2.循环遍历队列
     while(!p_q.empty()){ //队列不空 
         // 3.取出队头,存入cur
         cur = p_q.top();
         p_q.pop();
         int n_i = cur.i+1;
         if(n_i > n) continue;
         // 4. 利用产生式规则,拓展cur的关联节点入队
         // 选择下一个物品,并入队 
         if(cur.cv + items[n_i].volume <= V){ // 左剪枝 
             Node t;
             t.i = n_i;
             t.cp = cur.cp + items[n_i].price;
             t.cv = cur.cv + items[n_i].volume;
             t.ub  = bound(cur.i+1, cur.cv, cur.cp);//计算上界值 
             bestp = max(bestp,t.cp);
             p_q.push(t); //插入优先队列 
         } 
         // 不选择下一个物品,并入队 
         // 计算上界
         Node t2;
         t2.i = n_i;
         t2.cp = cur.cp;
         t2.cv = cur.cv;
         t2.ub  = bound(cur.i+1, cur.cv, cur.cp);//计算上界值 
         if (t2.ub > bestp) {
             p_q.push(t2); //插入优先队列 
         }
     } 
 } 

P8013 任务分配

总体框架不变,确定下界的自定义函数变了,多了一个枚举任务的逻辑,多了一个任务分配状态的记录,其他就比较常规啦。

 void bound(Node &e){//当前还有可能得到的最小代价
     int minsum = 0;
     for(int i = e.i + 1; i <= n; i++){ // 枚举人 
         int min_v = 2e9;
         // 贪心 
         for(int j = 1; j <= n; j++){ // 枚举任务 
             if(e.used[j] == 0 && a[i][j] <= min_v)
                 min_v = a[i][j];
         }
         minsum += min_v;
     } 
     e.lb = e.cost + minsum;
 } 
 ​
 void bfs(){
     // 定义一个优先队列 
     priority_queue<Node> p_q;
     Node cur,next;
     cur.i = 0; //根节点
     cur.cost =0;
     cur.used.resize(n+10);
     cur.ve.resize(n+10);
     bound(cur);
     p_q.push(cur); //根节点入队 
     while(!p_q.empty()){ // 队列不为空 
         cur = p_q.top();
         p_q.pop();
         for(int j = 1; j <= n; j++){ //枚举任务 
             if(cur.used[j] == 1) continue; //任务被分配过 
             next.i = cur.i + 1;
             next.used = cur.used;
             next.ve = cur.ve;
             next.used[j] = 1;
             next.ve[next.i] = j; //第i个人,被分配了第j个任务 
             next.cost = cur.cost + a[next.i][j];
             bound(next);//计算下界 
             if(next.lb < ans){//剪枝 
                 if(next.i == n){
                     //更新最优解 
                     if(next.cost < ans){
                         ans = next.cost; 
                     }   
                 }else{
                     //入队 
                     p_q.push(next); 
                 }
             }
         }
     }   
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值