package 蓝桥杯;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.Vector;
public class VO深搜_寻路问题 {
static int K,N,R;//钱数<10000,城市数<100,道路数<10000
//我们需要一个邻接表来存放道路信息
static class road{
int d,l,t;//道路终点d,长度l<100,过路费<100,按照邻接表的特点,是不需要存道路的起点的.
public road(int d,int l,int t) {
this.d=d;
this.l=l;
this.t=t;
}
}
//邻接表是一个二维数组,我们用ArrayList来实现,vector套vector也可实现自动增长的对象数组,但是java不建议使用vector
static ArrayList[] arrayLists=new ArrayList[110];//110行的邻接表 ,G[i]表示i这一点连出去的所有节点
static int minLen;//用来记录到目前为止找到的最佳的能走到终点的路径
static int totalLen;//现在正在探索的这条路已经走了多长
static int totalCost;//现在正在探索的这条路已经花了多少钱
static int[] visited=new int[110];//一个城市是否已经走过,需要一个备忘数组,默认为0
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
K=reader.nextInt();
N=reader.nextInt();
R=reader.nextInt();
//读取R条边的信息
for(int i=0;i<=N;i++) {
arrayLists[i]=new ArrayList<road>();//初始化
}
for(int i=0;i<R;i++) {
int s;//边的起点
s=reader.nextInt();
int d=reader.nextInt();
int l=reader.nextInt();
int t=reader.nextInt();
road r=new road(d,l,t);//边
//起点等于终点不理它
if(s!=r.d) {
arrayLists[s].add(r);//arrayLists[s]存放的是起点为s连出去的所有边
}
}
//测试输出
for(int i=0;i<N;i++) {
System.out.print("Start:"+i);
for(int j=0;j<arrayLists[i].size();j++) {
road test =(road)arrayLists[i].get(j);
System.out.print("——>"+test.d+",");
}
System.out.println();
}
totalLen=0;
minLen=1<<30;
totalCost=0;
visited[1]=1;
dfs(1);
if(minLen<(1<<30)) {//说明找到了路
System.out.println(minLen);
}
else {//说明没有找到路
System.out.println(-1);
}
}
static void dfs(int s) {
if(s==N) {
minLen=Math.min(minLen, totalLen);//找到了路,就要更新minLen
return;
}
//遍历从s连出去的所有的边
for(int i=0;i<arrayLists[s].size();i++) {
road r=(road)arrayLists[s].get(i);//直接get出来的是E,所以需要强制类型转换(参考在ArrayList中get的源代码,elementData是一个Object数组,被强制类型转换成E)
if(r.t+totalCost>K)//钱不够,回到循环试下一条边
continue;
if(visited[r.d]!=1) {//r.d没走过
totalCost+=r.t;
totalLen+=r.l;
visited[r.d]=1;
dfs(r.d);
visited[r.d]=0;//试下一条路之前撤销visited,totalCost和tatalLen
totalCost-=r.t;
totalLen-=r.l;
}
}
}
}
输出:
提交会超时,说明代码不够优化,需要哪里进行剪枝。
什么叫剪枝:
就是我在探索一条路的时候不需要一条路走到黑,可能走到一个中间点的时候就发现走下去不可能有前途,不会有前途是指,走不到终点,或者就算走到终点,现在走下去的结果也不会比以前找到得到的结果好。具体到这个例子,什么时候提前结束搜索呢?如果能提前预判是否能走到终点(可行性剪枝),当前程序有一个可行性剪枝就是
if(r.t+totalCost>K)//钱不够,回到循环试下一条边
continue;
但是效果不都好。有一个剪枝方法叫最优性剪枝,现在走下去的结果也不会比以前找到那条路的结果好。
怎么使用:
利用minLen(目前为止找到最优路径的长度),如果发现探索的这条路当前tatalLen已经比minLen长了,那就没必要继续探索下去了。在代码中加上if(totalLen+r.l>=minLen) continue;进行剪枝。可以避免很多无用功。
if(visited[r.d]!=1) {//r.d没走过
if(totalLen+r.l>=minLen)
continue;//剪枝
totalCost+=r.t;
totalLen+=r.l;
visited[r.d]=1;
dfs(r.d);
visited[r.d]=0;//试下一条路之前撤销visited,totalCost和tatalLen
totalCost-=r.t;
totalLen-=r.l;
}
提交还是超时,1s中之类无法完成。
还存在一个问题,我走到i城市的时候走了100米。但是前面我走到i才走了50米,说明我绕了远路,如果能记录下来走到每一个城市的所花的到目前为止的最短的路的长度k。我们就可以用这个k对每个城市进行最优性剪枝,还有可能,我虽然绕远路了,但是我花的钱更少了,我可能才是能到达终点的选择。
所以我用midL[k][m] 表示:走到城市k时总过路费为m的条件下,最优路径的长度。若在
后续的搜索中,再次走到k时,如果总路费恰好为m,且此时的路径长度已经超过
midL[k][m],则不必再走下去了。原理就是用空间换时间。
static int[][] minL=new int[110][1010];//minL[k][m]走到城市k时总过路费为m的条件下,最优路径的长度
在main里面加上
for(int i=0;i<110;i++)
for(int j=0;j<10010;j++)
minL[i][j]=1<<30;
在dfs下添加:
if (totalLen+r.l>=minL[r.d][totalCost+r.t]) {
continue;//以前我曾经也到达过r.d花费同样费用的情况下,那一次它走的路比我这次走的少
}
//更新minL的值
minL[r.d][totalCost+r.t]=totalLen+r.l;
整体:
package 蓝桥杯;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.Vector;
public class VO深搜_寻路问题 {
static int K,N,R;//钱数<10000,城市数<100,道路数<10000
//我们需要一个邻接表来存放道路信息
static class road{
int d,l,t;//道路终点d,长度l<100,过路费<100,按照邻接表的特点,是不需要存道路的起点的.
public road(int d,int l,int t) {
this.d=d;
this.l=l;
this.t=t;
}
}
//邻接表是一个二维数组,我们用ArrayList来实现,vector套vector也可实现自动增长的对象数组,但是java不建议使用vector
static ArrayList[] arrayLists=new ArrayList[110];//110行的邻接表 ,G[i]表示i这一点连出去的所有节点
static int minLen;//用来记录到目前为止找到的最佳的能走到终点的路径
static int totalLen;//现在正在探索的这条路已经走了多长
static int totalCost;//现在正在探索的这条路已经花了多少钱
static int[] visited=new int[110];//一个城市是否已经走过,需要一个备忘数组,默认为0
static int[][] minL=new int[110][10010];//剪枝所需,minL[k][m]走到城市k时总过路费为m的条件下,最优路径的长度
public static void main(String[] args) {
Scanner reader=new Scanner(System.in);
K=reader.nextInt();
N=reader.nextInt();
R=reader.nextInt();
//读取R条边的信息
for(int i=0;i<=N;i++) {
arrayLists[i]=new ArrayList<road>();//初始化
}
for(int i=0;i<R;i++) {
int s;//边的起点
s=reader.nextInt();
int d=reader.nextInt();
int l=reader.nextInt();
int t=reader.nextInt();
road r=new road(d,l,t);//边
//起点等于终点不理它
if(s!=r.d) {
arrayLists[s].add(r);//arrayLists[s]存放的是起点为s连出去的所有边
}
}
//测试输出
for(int i=0;i<N;i++) {
System.out.print("Start:"+i);
for(int j=0;j<arrayLists[i].size();j++) {
road test =(road)arrayLists[i].get(j);
System.out.print("——>"+test.d+",");
}
System.out.println();
}
totalLen=0;
minLen=1<<30;
totalCost=0;
visited[1]=1;
for(int i=0;i<110;i++)
for(int j=0;j<10010;j++)
minL[i][j]=1<<30;
dfs(1);
if(minLen<(1<<30)) {//说明找到了路
System.out.println(minLen);
}
else {//说明没有找到路
System.out.println(-1);
}
}
static void dfs(int s) {
if(s==N) {
minLen=Math.min(minLen, totalLen);//找到了路,就要更新minLen
return;
}
//遍历从s连出去的所有的边
for(int i=0;i<arrayLists[s].size();i++) {
road r=(road)arrayLists[s].get(i);//直接get出来的是E,所以需要强制类型转换(参考在ArrayList中get的源代码,elementData是一个Object数组,被强制类型转换成E)
if(r.t+totalCost>K)//钱不够,回到循环试下一条边
continue;
if(visited[r.d]!=1) {//r.d没走过
if(totalLen+r.l>=minLen)
continue;//剪枝
if (totalLen+r.l>=minL[r.d][totalCost+r.t]) {
continue;//以前我曾经也到达过r.d花费同样费用的情况下,那一次它走的路比我这次走的少
}
//更新minL的值
minL[r.d][totalCost+r.t]=totalLen+r.l;
totalCost+=r.t;
totalLen+=r.l;
visited[r.d]=1;
dfs(r.d);
visited[r.d]=0;//试下一条路之前撤销visited,totalCost和tatalLen
totalCost-=r.t;
totalLen-=r.l;
}
}
}
}