1.大臣的旅费
1)题目 :
深度优先搜索
图
邻接表
问题描述
很久以前,T王国空前繁荣。为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。
为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。
J是T国重要大臣,他巡查于各大城市之间,体察民情。所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。他有一个钱袋,用于存放往来城市间的路费。
聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。
J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?
输入格式
输入的第一行包含一个整数n,表示包括首都在内的T王国的城市数
城市从1开始依次编号,1号城市为首都。
接下来n-1行,描述T国的高速路(T国的高速路一定是n-1条)
每行三个整数Pi, Qi, Di,表示城市Pi和城市Qi之间有一条高速路,长度为Di千米。
输出格式
输出一个整数,表示大臣J最多花费的路费是多少。
2)题解:
方法一: 常规dfs
import java.util.*;
public class _大臣的旅费 {
private static int[][] distances;
private static boolean[] hashtable;
private static int n;
private static int sum_dis=0;
private static int fee=0;
private static int fee_max=0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
distances = new int[n][n];
hashtable = new boolean[n];
int a1=0,a2=0,a3=0;
for(int i=0;i<n-1;i++) {
a1 = sc.nextInt();
a2 = sc.nextInt();
a3 = sc.nextInt();
distances[a1-1][a2-1]=a3;
distances[a2-1][a1-1]=a3;
}
for(int j=0;j<n;j++) {
dfs(j);
}
System.out.println(fee_max);
}
private static void dfs(int current) {
int fee_current=0;
hashtable[current]=true;
for(int j=0;j<n;j++) {
if(distances[current][j]!=0) {
if(hashtable[j]==false) {
fee_current=distances[current][j]*(10+sum_dis)+(distances[current][j]+1)*distances[current][j]/2;
fee+=fee_current;
sum_dis+=distances[current][j];
if(fee>fee_max) {
fee_max=fee;
}
dfs(j);
sum_dis-=distances[current][j];
fee-=fee_current;
}
}
}
hashtable[current]=false;
}
}
可通过大部分(75%)测试用例,但是耗时、耗内存
方法二:
因为任何一个大城市都能从首都直接或者通过其他大城市间接到达
,所以将地图看成是树,根节点为首都,则题目所求的可以看成是树的直径
,可用两次dfs(bfs)求解:第一次dfs找出离首都最远的城市fartest_node
,第二次dfs以这个城市fartest_node
为起点搜索
import java.util.*;
public class _大臣的旅费 {
private static int[][] distances;
private static boolean[] hashtable;
private static int n;
private static int sum_dis=0;
private static int fee=0;
private static int fee_max=0;
private static int fartest_node=0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
distances = new int[n][n];
hashtable = new boolean[n];
int a1=0,a2=0,a3=0;
for(int i=0;i<n-1;i++) {
a1 = sc.nextInt();
a2 = sc.nextInt();
a3 = sc.nextInt();
distances[a1-1][a2-1]=a3;
distances[a2-1][a1-1]=a3;
}
dfs(0);
dfs(fartest_node);
System.out.println(fee_max);
}
private static void dfs(int current) {
int fee_current=0;
hashtable[current]=true;
for(int j=0;j<n;j++) {
if(distances[current][j]!=0) {
if(hashtable[j]==false) {
fee_current=distances[current][j]*(10+sum_dis)+(distances[current][j]+1)*distances[current][j]/2;
fee+=fee_current;
sum_dis+=distances[current][j];
if(fee>fee_max) {
fee_max=fee;
fartest_node=j;
}
dfs(j);
sum_dis-=distances[current][j];
fee-=fee_current;
}
}
}
hashtable[current]=false;
}
}
速度提升但是仍然超内存,因为仍然采用邻接矩阵存储数据,但是数据稀疏,造成空间浪费。
由于最后一个测试用例有10000个点,所以使用二维数组占用内存非常大。
方法三:
用邻接表存储结构,改善内存使用。
import java.util.*;
public class Main {
private static boolean[] hashtable;
private static int n;
private static int sum_dis=0;
private static int fee=0;
private static int fee_max=0;
private static int fartest_node=0;
private static LinkedList<Edge>[] E1;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
//注意边集的初始化
E1 = new LinkedList[n];
for(int i=0;i<n;i++) {
E1[i] = new LinkedList<Edge>();
}
hashtable = new boolean[n];
for(int i=0;i<n-1;i++) {
int V1 = sc.nextInt();
int V2 = sc.nextInt();
int D = sc.nextInt();
E1[V1-1].add(new Edge(V1-1,V2-1,D));
E1[V2-1].add(new Edge(V2-1,V1-1,D));
}
dfs(0);
System.out.println(fartest_node);
dfs(fartest_node);
System.out.println(fee_max);
}
//图的边
private static class Edge{ //类应该用static修饰
private int Vertex_1;
private int Vertex_2;
private int distance;
public Edge(int Vertex_1,int Vertex_2 ,int distance) { //该方法需要为public
this.Vertex_1 = Vertex_1;
this.Vertex_2 = Vertex_2;
this.distance = distance;
}
}
private static void dfs(int current) {
int fee_current=0;
hashtable[current]=true;
for(int j=0;j<E1[current].size();j++) {
int index = E1[current].get(j).Vertex_2;
int distance = E1[current].get(j).distance;
if(hashtable[index]==false) {
fee_current=distance*(10+sum_dis)+(distance+1)*distance/2;
fee+=fee_current;
sum_dis+=distance;
if(fee>fee_max) {
fee_max=fee;
fartest_node=index;
}
dfs(index);
sum_dis-=distance;
fee-=fee_current;//此处注意回退时,费用也要减掉
}
}
hashtable[current]=false;
}
}
其中边集的初始化需要注意
E1 = new LinkedList[n];
for(int i=0;i<n;i++) {
E1[i] = new LinkedList<Edge>();
}
内存占用明显减少
2.九宫重排
广度优先搜索
剪枝
1)题目
问题描述
如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。
我们把第一个图的局面记为:12345678.
把第二个图的局面记为:123.46758
显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。
输入格式
输入第一行包含九宫的初态,第二行包含九宫的终态。
输出格式
输出最少的步数,如果不存在方案,则输出-1。
样例输入:
12345678.
123.46758
样例输出
3
2)题解
方法一:
BFS,一开始没有思考,直接用了字符串操作,字符串拼接比较耗时。因为如果是数组操作,每次移动只需要交换两个元素,而如果是字符串操作,每次移动至少需要拼接三次。
import java.util.*;
public class Main {
static Set<String> visited = new HashSet<String>();
static int steps = 0;
static int min_steps = -1;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String origin = sc.nextLine();
String target = sc.nextLine();
bfs(origin,target);
System.out.println(min_steps);
}
static void bfs(String start,String target) {
Queue<String> queue = new LinkedList<String>();
queue.offer(start);
queue.offer(null);
while(!queue.isEmpty()) {
String cur = queue.poll();
if(cur==null&&queue.peek()!=null) {
steps+=1;
queue.offer(null);
}
else if(cur.equals(target)) { //注意要使用equals()方法判断字符串内容是否相同
if(min_steps==-1||steps<min_steps) {
min_steps=steps;
return;
}
}
else if(cur!=null) {
visited.add(cur);
int blank=cur.indexOf('.');
if(blank>2) {//空格上移
String subString = cur.substring(0, blank-3)+"."+cur.substring(blank-2,blank)+cur.substring(blank-3,blank-2)+cur.substring(blank+1);
if(!visited.contains(subString)) {
queue.offer(subString);
}
}
if(blank<6) {//空格下移
String subString = cur.substring(0,blank)+cur.substring(blank+3,blank+4)+cur.substring(blank+1,blank+3)+"."+cur.substring(blank+4);
if(!visited.contains(subString)) {
queue.offer(subString);
}
}
if(blank%3>0) {//空格左移
String subString = cur.substring(0,blank-1)+"."+cur.substring(blank-1,blank)+cur.substring(blank+1);
if(!visited.contains(subString)) {
queue.offer(subString);
}
}
if(blank%3<2) {//空格右移
String subString = cur.substring(0,blank)+cur.substring(blank+1,blank+2)+"."+cur.substring(blank+2);
if(!visited.contains(subString)) {
queue.offer(subString);
}
}
}
}
}
}
优化成为字符数组操作,节省时间。
public class Main {
static Set<String> visited = new HashSet<String>();
static int steps = 0;
static int min_steps = -1;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String origin = sc.nextLine();
String target = sc.nextLine();
bfs(origin,target);
System.out.println(min_steps);
}
static void bfs(String start,String target) {
Queue<String> queue = new LinkedList<String>();
queue.offer(start);
queue.offer(null);
while(!queue.isEmpty()) {
String cur = queue.poll();
if(cur==null&&queue.peek()!=null) {
steps+=1;
queue.offer(null);
}
else if(cur.equals(target)) {
if(min_steps==-1||steps<min_steps) {
min_steps=steps;
return;
}
}
else if(cur!=null) {
visited.add(cur);
int blank=cur.indexOf('.');
if(blank>2) {//空格上移
char[] ch = cur.toCharArray();
swap(blank,blank-3,ch);
String subString = new String(ch);
if(!visited.contains(subString)) {
queue.offer(subString);
}
}
if(blank<6) {//空格下移
char[] ch = cur.toCharArray();
swap(blank,blank+3,ch);
String subString = new String(ch);
if(!visited.contains(subString)) {
queue.offer(subString);
}
}
if(blank%3>0) {//空格左移
char[] ch = cur.toCharArray();
swap(blank,blank-1,ch);
String subString = new String(ch);
if(!visited.contains(subString)) {
queue.offer(subString);
}
}
if(blank%3<2) {//空格右移
char[] ch = cur.toCharArray();
swap(blank,blank+1,ch);
String subString = new String(ch);
if(!visited.contains(subString)) {
queue.offer(subString);
}
}
}
}
}
static void swap(int x,int y,char[] ch) {
char temp = ch[x];
ch[x] = ch[y];
ch[y] = temp;
}
}
注意:
- 区分ch.toString()和new String(ch)
- 区分a.equals(b)和a==b
- 明确字符数组操作和字符串操作优缺点
方法二:
dfs和bfs都属于盲目的图搜索,A*搜索,属于启发式搜索的一种,暂未实现。