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

发布时间:2024年01月15日

分支限界

整体结构

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

例题

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); 
? ? ? ? ? ? ? ?  }
? ? ? ? ? ?  }
? ? ? ?  }
? ?  } ? 
?}
文章来源:https://blog.csdn.net/weixin_63059689/article/details/135612884
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。