西宁网站建设/关键词排名靠前
广度优先搜索
- 树上的广度优先搜索实际上就是层次遍历。
分支界限法
- 分支界限法通常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。
- 首先将根节点加入活节点表中,接着从活节点表中取出根结点,使其称为当前扩展节点,一次性生成其所有孩子节点,判断对孩子节点是舍弃还是保留,舍弃那些得不到可行解或最优解的节点。
- 活节点表的实现通常有两种形式:一种是普通的队列,即先进先出队列;另一种是优先级队列,按照某种优先级决定哪个节点为当前扩展节点。
队列式广度优先搜索
- 有一个背包和4个物品,每个物品的重量分别为2、5、4、2,价值分别为:6、3、5、4,背包的容量
W = 10
。求在不超过背包容量的前提下,把哪些物品放入背包才能获得最大价值。
算法设计
- 定义节点结构体
struct Node{ //定义节点,记录当前节点的解信息int cp,rp; //cp背包的物品总价值,rp剩余物品的总价值int rw; //剩余容量int id; //物品号bool x[N]; //解向量Node() {memset(x,0,sizfof(x));} //将解向量初始化为0Node(int _cp, int _rp, int _rw, int _id){cp = _cp;rp = _rp;rw = _rw;id = _id;}
};
- 定义物品结构体
struct Goods{ //物品int wight; //重量int value; //价值
}goods[N]
- 搜索解空间。首先创建一个普通队列(先进先出)然后将根节点加入队列中,如果队列不空,则取出队头元素livenode,得到当前处理的物品序号,如果当前处理的物品序号大于n,则说明搜到最后一个物品了,不需要往下搜索。如果当前的背包没有剩余容量(已经装满)了,则不再扩展。如果当前放入背包的物品价值大于或等于最优值(livenode.cp ≥\geq≥ bestp),则更新最优解和最优值。判断是否满足约束条件,满足则生成左孩子,判断是否更新最优值,左孩子入队,不满足约束条件则舍弃左孩子;判断是否满足限界条件,满足则生成右孩子,右孩子入队,不满足限界条件则舍弃右孩子。
算法实现
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=10;
bool bestx[N];
//定义结点。每个节点来记录当前的解。
struct Node{int cp,rp; //cp为当前装入背包的物品总价值,rp为剩余物品的总价值int rw; //剩余容量int id; //物品号bool x[N];//解向量Node(){}Node(int _cp,int _rp,int _rw,int _id){cp=_cp;rp=_rp;rw=_rw;id=_id;memset(x,0,sizeof(x));//解向量初始化为0}
};struct Goods{//物品int weight;//重量 int value;//价值
}goods[N];int bestp,W,n,sumw,sumv;
/*bestp用来记录最优解W为背包最大容量。n为物品的个数。sumw为所有物品的总重量。sumv为所有物品的总价值。
*/
int bfs();//队列式分支限界法
int main(){cin>>n>>W;//输入物品的个数和背包的容量bestp=0; //bestv用来记录最优解sumw=0; //sumw为所有物品的总重量。sumv=0; //sumv为所有物品的总价值for(int i=1;i<=n;i++){//输入每个物品的重量和价值,用空格分开cin>>goods[i].weight>>goods[i].value;//输入第i件物品的重量和价值。sumw+=goods[i].weight;sumv+=goods[i].value;}if(sumw<=W){bestp=sumv;cout<<"放入背包的物品最大价值为: "<<bestp<<endl;return 0;}bfs();cout<<"放入背包的物品最大价值为: "<<bestp<<endl;cout<<"放入背包的物品序号为: ";for(int i=1;i<=n;i++){// 输出最优解if(bestx[i])cout<<i<<" ";}return 0;
}int bfs(){//队列式分支限界法 int t,tcp,trp,trw;//当前处理的物品序号t,装入背包物品价值tcp,剩余容量trwqueue<Node> q; //创建一个普通队列(先进先出)q.push(Node(0,sumv,W,1)); //压入一个初始节点while(!q.empty()){Node livenode,lchild,rchild;//定义三个结点型变量livenode=q.front();//取出队头元素作为当前扩展结点livenodeq.pop(); //队头元素出队t=livenode.id;//当前处理的物品序号// 搜到最后一个物品的时候不需要往下搜索// 如果当前的背包没有剩余容量(已经装满)了,不再扩展if(t>n||livenode.rw==0){if(livenode.cp>=bestp){//更新最优解和最优值for(int i=1;i<=n;i++)bestx[i]=livenode.x[i];bestp=livenode.cp;}continue;}if(livenode.cp+livenode.rp<bestp)//判断当前结点是否满足限界条件,如果不满足不再扩展continue;tcp=livenode.cp; //当前背包中的价值trp=livenode.rp-goods[t].value; //不管当前物品装入与否,剩余价值都会减少。trw=livenode.rw; //背包剩余容量if(trw>=goods[t].weight){ //扩展左孩子,满足约束条件,可以放入背包lchild.rw=trw-goods[t].weight;lchild.cp=tcp+goods[t].value;lchild=Node(lchild.cp,trp,lchild.rw,t+1);for(int i=1;i<t;i++)//复制以前的解向量lchild.x[i]=livenode.x[i];lchild.x[t]=true;if(lchild.cp>bestp)//比最优值大才更新bestp=lchild.cp;q.push(lchild);//左孩子入队}if(tcp+trp>=bestp){//扩展右孩子,满足限界条件,不放入背包rchild=Node(tcp,trp,trw,t+1);for(int i=1;i<t;i++)//复制以前的解向量rchild.x[i]=livenode.x[i];rchild.x[t]=false;q.push(rchild);//右孩子入队}}return bestp;//返回最优值
}
输入:
4 10
2 6 5 3 4 5 2 4
输出:
放入背包的物品最大价值为: 15
放入背包的物品序号为: 1 3 4
优先队列式广度优先搜索
- 优先队列优化以当前节点的上界为优先值,把普通队列改成优先队列,这样就得到了优先队列分支限界法。
算法设计
- 优先级的定义为活节点代表的部分解所描述的已装入物品的价值上界,上界越大,优先级越高。活节点的价值上界up = 活节点的cp+剩余物品装满背包剩余容量的最大价值rp’。
- 约束条件:
cw+w[i]
≤\leq≤W
- 限界条件:
up = cp + rp'
≥\geq≥bestp
算法实现
- 定义节点和物品结构体
struct Node{ //定义节点,记录当前节点的解信息int cp; //cp背包的物品总价值,rp剩余物品的总价值double up; //价值上界int rw; //剩余容量int id; //物品号bool x[N]; //解向量Node() {}Node(int _cp, double _up, int _rw, int _id){cp = _cp;up = _up;rw = _rw;id = _id;memset(x,0,sizfof(x));}
};struct Goods{//物品int weight;//重量 int value;//价值
}goods[N];
- 定义辅助结构体和排序优先级(从大到小排序)
struct Object{ //辅助物品结构体,用于按单位重量价值(价值/重量比)排序int id; //序号double d; //单位重量价值
}S[N];bool cmp(Object a1,Object a2){ //排序优先级,按照物品的单位重量价值由大到小排序return a1.d>a2.d;
}
- 定义队列的优先级
bool operator <(const Node &a, const Node &b){ //队列优先级,up越大越优先return a.up < b.up;
}
- 计算节点的上界
double Bound(Node tnode){double maxvalue = tnode.cp; //已装入背包的物品价值int t = tnode.id; //排序后序号double left = tnode.rw; //剩余容量while(t <= n && w[t] <= left){maxvalue+=v[t];left-=w[t++];}if(t <= n){maxvalue+=double(v[t])/w[t]*left;}return maxvalue;
}
- 优先队列分支限界法。
算法实现
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int N=10;
bool bestx[N]; //记录最优解
int w[N],v[N];//辅助数组,用于存储排序后的重量和价值struct Node{//定义结点,记录当前结点的解信息int cp; //已装入背包的物品价值double up; //价值上界int rw; //背包剩余容量int id; //物品号bool x[N];Node() {}Node(int _cp,double _up,int _rw,int _id){cp=_cp;up=_up;rw=_rw;id=_id;memset(x, 0, sizeof(x));}
};struct Goods{ //物品结构体int weight;//重量int value;//价值
}goods[N];struct Object{//辅助物品结构体,用于按单位重量价值(价值/重量比)排序int id; //序号double d;//单位重量价值
}S[N];bool cmp(Object a1,Object a2){//排序优先级,按照物品单位重量价值由大到小排序return a1.d>a2.d;
}bool operator <(const Node &a, const Node &b){//队列优先级。up越大越优先return a.up<b.up;
}int bestp,W,n,sumw,sumv;
/*bestv 用来记录最优解。W为背包的最大容量。n为物品的个数。sumw为所有物品的总重量。sumv为所有物品的总价值。
*/double Bound(Node tnode){//计算节点的上界double maxvalue=tnode.cp;//已装入背包物品价值int t=tnode.id;//排序后序号double left=tnode.rw;//剩余容量while(t<=n&&w[t]<=left){maxvalue+=v[t];left-=w[t++];}if(t<=n)maxvalue+=double(v[t])/w[t]*left;return maxvalue;
}int priorbfs(){//优先队列式分支限界法int t,tcp,trw;//当前处理的物品序号t,当前装入背包物品价值tcp,当前剩余容量trwdouble tup; //当前价值上界tuppriority_queue<Node> q; //创建一个优先队列q.push(Node(0,sumv,W,1));//初始化,根结点加入优先队列while(!q.empty()){Node livenode, lchild, rchild;//定义三个结点型变量livenode=q.top();//取出队头元素作为当前扩展结点livenodeq.pop(); //队头元素出队t=livenode.id;//当前处理的物品序号// 搜到最后一个物品的时候不需要往下搜索。// 如果当前的背包没有剩余容量(已经装满)了,不再扩展。if(t>n||livenode.rw==0){if(livenode.cp>=bestp){//更新最优解和最优值for(int i=1;i<=n;i++)bestx[i]=livenode.x[i];bestp=livenode.cp;}continue;}if(livenode.up<bestp)//如果不满足不再扩展continue;tcp=livenode.cp; //当前背包中的价值trw=livenode.rw; //背包剩余容量if(trw>=w[t]){ //扩展左孩子,满足约束条件,可以放入背包lchild.cp=tcp+v[t];lchild.rw=trw-w[t];lchild.id=t+1;tup=Bound(lchild); //计算左孩子上界lchild=Node(lchild.cp,tup,lchild.rw,lchild.id);for(int i=1;i<=n;i++)//复制以前的解向量lchild.x[i]=livenode.x[i];lchild.x[t]=true;if(lchild.cp>bestp)//比最优值大才更新bestp=lchild.cp;q.push(lchild);//左孩子入队}rchild.cp=tcp;rchild.rw=trw;rchild.id=t+1;tup=Bound(rchild);//计算右孩子上界if(tup>=bestp){//扩展右孩子,满足限界条件,不放入rchild=Node(tcp,tup,trw,t+1);for(int i=1;i<=n;i++)//复制以前的解向量rchild.x[i]=livenode.x[i];rchild.x[t]=false;q.push(rchild);//右孩子入队}}return bestp;//返回最优值。
}int main(){bestp=0; //bestv用来记录最优解sumw=0; //sumw为所有物品的总重量。sumv=0; //sumv为所有物品的总价值cin>>n>>W;for(int i=1;i<=n;i++){cin>>goods[i].weight>>goods[i].value;//输入第i件物品的重量和价值。sumw+=goods[i].weight;sumv+=goods[i].value;S[i-1].id=i;S[i-1].d=1.0*goods[i].value/goods[i].weight;}if(sumw<=W){bestp=sumv;cout<<"放入背包的物品最大价值为: "<<bestp<<endl;return 0;}sort(S,S+n,cmp);//按价值重量比非递增排序for(int i=1;i<=n;i++){//把排序后的数据传递给辅助数组w[i]=goods[S[i-1].id].weight;v[i]=goods[S[i-1].id].value;}priorbfs();//优先队列分支限界法cout<<"放入背包的物品最大价值为: "<<bestp<<endl;cout<<"放入背包的物品序号为: ";for(int i=1;i<=n;i++){ //输出最优解if(bestx[i])cout<<S[i-1].id<<" ";//输出原物品序号(排序前的)}return 0;
}
输入:
4 10
2 6 5 3 4 5 2 4
输出:
放入背包的物品最大价值为: 15
放入背包的物品序号为: 1 3 4
训练1:迷宫问题
题目描述
用一个二维数组表示一个迷宫,其中1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,编写程序,找出从左上角到右下角的最短路线。
输入:一个5×55 \times 55×5的二维数组,表示一个迷宫。数据保证有唯一解。
输出:从左上角到右下角的最短路径,格式如以下输出样例所示。
算法设计
- 定义一个队列,将起点(0,0)入队,标记已走过。
- 如果队列不为空,则队头出队。
- 如果队头正好是目标(4,4),则退出。
- 沿着4个方向搜索,如果该节点未出边界,未走过且不说墙壁,则标记走过并入队,用前驱数组记录该节点。
- 转向步骤2
- 根据前驱数组输出从起点到终点的最短路径。
算法实现
#include<cstdio>
#include<queue>
using namespace std;
int mp[5][5],vis[5][5];
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
struct node{int x,y;
};
node pre[10][10];
void bfs(); //广度搜索
void print(node cur); //打印结果int main(){for(int i=0;i<5;i++)for(int j=0;j<5;j++)scanf("%d",&mp[i][j]);bfs();node ed;ed.x=ed.y=4;print(ed);return 0;
}void bfs(){queue<node> que; //创建node格式的队列node st;st.x=st.y=0; //从左上角出发que.push(st);vis[0][0]=1; //标记起点已经被走过while(!que.empty()){ //如果队列不为空node now=que.front(); //取队头que.pop(); //出队if(now.x==4&&now.y==4) //如果到达终点,结束return;for(int i=0;i<4;i++){ //向4个方向扩展node next; //新建node型结构next.x=now.x+dir[i][0];next.y=now.y+dir[i][1];if(next.x>=0&&next.x<5&&next.y>=0&&next.y<5&&!mp[next.x][next.y]&&!vis[next.x][next.y]){ //边界检查,墙体检查,路径检查vis[next.x][next.y]=1;que.push(next); //入队pre[next.x][next.y]=now; //}}}
}void print(node cur){if(cur.x==0&&cur.y==0){printf("(0, 0)\n");return;}print(pre[cur.x][cur.y]);//逆序输出(递归)printf("(%d, %d)\n",cur.x,cur.y);
}
输入:
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出:
(0, 0)
(1, 0)
(2, 0)
(2, 1)
(2, 2)
(2, 3)
(2, 4)
(3, 4)
(4, 4)
训练2:加满邮箱
题目描述
城市之间的油价是不同的,编写程序,寻找最便宜的城市间旅行方式。在旅行途中可以加满油箱。假设汽车每单位距离使用以单位燃料,从一个空油箱开始。
输入:输入的第1行包含nnn(1≤n≤10001 \leq n \leq 10001≤n≤1000)和m(0≤m≤100000 \leq m \leq 100000≤m≤10000),表示城市和道路的数量。下一行包含n个整数pip_{i}pi(1≤pi≤1001 \leq p_{i} \leq 1001≤pi≤100),其中pip_{i}pi表示第iii个城市的燃油价格。接下来的mmm行,每行都包含3个整数uuu、vvv(0≤u0 \leq u0≤u)和ddd,表示在uuu和vvv之间有一条路,长度为ddd。接下来一行是查询数qqq(1≤q≤1001 \leq q \leq 1001≤q≤100)。再接下来的qqq行,每行都包含3个整数ccc(1≤c≤1001 \leq c \leq 1001≤c≤100)、sss和eee,其中ccc是车辆的油箱容量,sss是起点城市,eee是终点城市。
输出:对于每个查询,都输出给定容量的汽车从sss到eee的最便宜旅程的价格,如果无法从sss到eee,则输出“impossible”
算法设计
- 定义一个优先队列,将起点及当前油量、花费作为一个节点(st,0,0)(st,0,0)(st,0,0)入队。
- 如果队列不空,则队头(u,vol,cost)(u,vol,cost)(u,vol,cost)出队,并标记该节点油量已扩展,
vis[u][vol] = 1
。 - 如果uuu正好是目标ededed,则返回花费costcostcost。
- 如果当前油量小于油箱容量,且uuu的油量
vol+1
未扩展,则将该节点(u,vol+1,cost+price[u])
入队。 - 检查uuu所有邻接点vvv,如果当前油量大于或等于边权www,且vvv节点的油量
vol-w
未扩展,则将该节点(v,vol-w,cost)
入队
算法实现
#include<cstdio>//加dp[][]优化
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=1005;
const int maxm=20005;struct edge{int v,w,next;
}edge[maxm];struct node{int u,vol,cost;node(int u_,int vol_,int cost_){u=u_,vol=vol_,cost=cost_;}bool operator < (const node &a) const{return cost>a.cost;}
};
int head[maxn];
bool vis[maxn][105];
int dp[maxn][105];//dp[i][j]表示在顶点i,当前油量为j时的最小花费。
int n,m,V,st,ed,cnt,price[maxn];void add(int u,int v,int w){edge[cnt].v=v;edge[cnt].w=w;edge[cnt].next=head[u];head[u]=cnt++;
}int bfs(){memset(vis,0,sizeof(vis));memset(dp,0x3f,sizeof(dp));priority_queue<node>Q;Q.push(node(st,0,0));dp[st][0]=0;while(!Q.empty()){node cur=Q.top();Q.pop();int u=cur.u,vol=cur.vol,cost=cur.cost;vis[u][vol]=1;if(u==ed) return cost;if(vol<V&&!vis[u][vol+1]&&dp[u][vol]+price[u]<dp[u][vol+1]){dp[u][vol+1]=dp[u][vol]+price[u];Q.push(node(u,vol+1,cost+price[u]));} for(int i=head[u];~i;i=edge[i].next){int v=edge[i].v,w=edge[i].w;if(vol>=w&&!vis[v][vol-w]&&cost<dp[v][vol-w]){dp[v][vol-w]=cost;Q.push(node(v,vol-w,cost));}}}return -1;
}int main(){int u,v,w;while(~scanf("%d%d",&n,&m)){for(int i=0;i<n;i++) scanf("%d",&price[i]);cnt=0;memset(head,-1,sizeof(head));while(m--){scanf("%d%d%d",&u,&v,&w);add(u,v,w),add(v,u,w);}int q;scanf("%d",&q);while(q--){scanf("%d%d%d",&V,&st,&ed);int ans=bfs();if(ans==-1) puts("impossible");else printf("%d\n", ans);}}return 0;
}
输入:
5 5
10 10 20 12 13
0 1 9
0 2 8
1 2 1
1 3 11
2 3 7
2
10 0 3
20 1 4
输出:
170
impossible