本质上感觉还是遍历解树+剪枝,但是配合优先队列使用以后可以更好的找到最优解。
对于迷宫问题,某一节点的关联节点指的是它四个方向上相邻的节点。
要利用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; ? ? ? ? } ? ? } ?}
核心在于选和不选两个分支的剪枝,总体框架依然是取队头-判断剪枝-拓展-判断剪枝-入队。
?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); //插入优先队列 ? ? ? ? } ? ? } ?}
总体框架不变,确定下界的自定义函数变了,多了一个枚举任务的逻辑,多了一个任务分配状态的记录,其他就比较常规啦。
?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); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } ? ? } ? ?}