深度优先
例题
求连通性
给定一个方阵,定义连通:上下左右相邻,并且值相同。
可以想象成一张地图,不同的区域被涂以不同颜色。
输入:
整数N, (N<50)表示矩阵的行列数
接下来N行,每行N个字符,代表方阵中的元素
接下来一个整数M,(M<1000)表示询问数
接下来M行,每行代表一个询问,
格式为4个整数,y1,x1,y2,x2,
表示(第y1行,第x1列) 与 (第y2行,第x2列) 是否连通。
连通输出true,否则false
例如:
10
0010000000
0011100000
0000111110
0001100010
1111010010
0000010010
0000010011
0111111000
0000010000
0000000000
3
0 0 9 9
0 2 6 8
4 4 4 6
程序应该输出:
false
true
true
package 图及其他;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
public class 求连通性
{
static int N;
static int M;
static int arr[][];
static boolean flag=true;
public static void main(String[] args) throws NumberFormatException, IOException
{
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));
N=Integer.parseInt(bufferedReader.readLine());
arr=new int [N+2][N+2];
for (int i = 0; i < arr.length; i++)
{
arr[i][0]=100;
arr[0][i]=100;
arr[arr.length-1][i]=100;
arr[i][arr.length-1]=100;
}
for (int i = 1; i <= N; i++)
{
String string=bufferedReader.readLine();
for (int j = 0; j < string.length(); j++)
{
if (string.charAt(j)=='1')
{
arr[i][j+1]=1;
}
}
}
// for (int i = 0; i < arr.length; i++)
// {
// System.out.println(Arrays.toString(arr[i]));
// }
M=Integer.parseInt(bufferedReader.readLine());
for (int i = 0; i < M; i++)
{
String []strings=bufferedReader.readLine().split(" ");
int x1=Integer.parseInt(strings[0])+1;
int y1=Integer.parseInt(strings[1])+1;
int x2=Integer.parseInt(strings[2])+1;
int y2=Integer.parseInt(strings[3])+1;
dfs(x1, y1, x2, y2);
if (flag)
{
System.out.println(false);
}
flag=true;
}
}
private static void dfs(int x, int y, int x2, int y2)
{
if (flag==false)
{
return;
}
if (x>=N+1||y>=N+1||x<=0||y<=0)
{
return ;
}
if (x==x2&&y==y2)
{
System.out.println("true");
flag=false;
return;
}
int old=arr[x][y];
arr[x][y]=3;//探索过
if (arr[x][y+1]==old)
{
dfs(x, y+1, x2, y2);
}
if (arr[x+1][y]==old)
{
dfs(x+1, y, x2, y2);
}
if (arr[x-1][y]==old)
{
dfs(x-1, y, x2, y2);
}
if (arr[x][y-1]==old)
{
dfs(x, y-1, x2, y2);
}
arr[x][y]=old;
}
}
class Point {
int x;
int y;
public Point(int x, int y)
{
super();
this.x = x;
this.y = y;
}
}
广度优先
节点动态生成的
分酒问题
有4个红酒瓶子,它们的容量分别是:9升, 7升, 4升, 2升
开始的状态是 [9,0,0,0],也就是说:第一个瓶子满着,其它的都空着。
允许把酒从一个瓶子倒入另一个瓶子,但只能把一个瓶子倒满或把一个瓶子倒空,不能有中间状态。
这样的一次倒酒动作称为1次操作。
假设瓶子的容量和初始状态不变,对于给定的目标状态,至少需要多少次操作才能实现?
本题就是要求你编程实现最小操作次数的计算。
输入:最终状态(空格分隔)
输出:最小操作次数(如无法实现,则输出-1)
例如:
输入:
9 0 0 0
应该输出:
0
输入:
6 0 0 3
应该输出:
-1
输入:
7 2 0 0
应该输出:
2
package 图及其他;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.TreeSet;
public class 分酒问题
{
public static void main(String[] args) throws IOException
{
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));
String from="9 0 0 0";
TreeSet<String> froms=new TreeSet<>();
TreeSet<String> hists=new TreeSet<>();
froms.add(from);
hists.add(from);
String goal=bufferedReader.readLine();
System.out.println(bfs(hists,froms,goal));
}
private static int bfs(TreeSet<String> hists, TreeSet<String> froms, String goal)
{
//已经找到了
if (froms.contains(goal))
{
return 0;
}
TreeSet<String> set=new TreeSet<>();
for (String string : froms)
{
TreeSet<String> treeSet=move(string);
set.addAll(treeSet);
}
//已经访问过的不再访问
set.removeAll(hists);
if (set.isEmpty())
{
return -1;
}
//加入访问过后
hists.addAll(set);
int r=bfs(hists, set, goal);
if (r<0)
{
return r;
}
return r+1;
}
private static TreeSet<String> move(String string)
{
//容量
final int []cap={9,7,4,2};
//各种子走法
TreeSet<String >treeSet=new TreeSet<>();
//各瓶子状态
String []strings=string.split(" ");
int []data=new int [strings.length];
for (int i = 0; i < strings.length; i++)
{
data[i]=Integer.parseInt(strings[i]);
}
//from
for (int i = 0; i < data.length; i++)
{
//to
for (int j = 0; j < data.length; j++)
{
//自己倒向自己
if (i==j)
{
continue;
}
//from瓶子空的
if (cap[i]==0)
{
continue;
}
//to瓶子满的
if (data[j]==cap[j])
{
continue;
}
//倒后的容量
int vi = 0;
int vj = 0;
//全倒
if (cap[j]-data[j]>=data[i])
{
vj=data[i]+data[j];
vi=0;
}
//倒得会多
if (cap[j]-data[j]<data[i])
{
vj=cap[j];
vi=data[i]-(cap[j]-data[j]);
}
StringBuffer stringBuffer=new StringBuffer();
for (int k = 0; k < data.length; k++)
{
if (k==i)
{
stringBuffer.append(vi+" ");
}
else if (k==j)
{
stringBuffer.append(vj+" ");
}
else
{
stringBuffer.append(data[k]+" ");
}
}
treeSet.add(stringBuffer.toString().trim());
}
}
return treeSet;
}
}
生成树


package 洛谷;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Prime算法思路:
* 不断从没有连接的节点(V-U)中选择权值最小的加入已联通的(U),加入后修改(V-U)与(U)的权值
* 加入当(V-U)=空集时候树生成完毕
* @author 零
*
*/
public class P3366最小生成树
{
static int n;
static int m;
public static void main(String[] args) throws IOException
{
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String[] strings = bufferedReader.readLine().trim().split(" ");
n = Integer.parseInt(strings[0]);// 节点数目
m = Integer.parseInt(strings[1]);// 边数目
int arr[][] = new int[n][n];
ArrayList<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < arr.length; i++)
{
for (int j = 0; j < arr[i].length; j++)
{
arr[i][j] = Integer.MAX_VALUE;
}
arrayList.add(i + 1);
}
Group group = new Group(arrayList, arr);
for (int i = 0; i < m; i++)
{
strings = bufferedReader.readLine().trim().split(" ");
int v1 = Integer.parseInt(strings[0]);// v1
int v2 = Integer.parseInt(strings[1]);// v2
int power = Integer.parseInt(strings[2]);// 权值
// 避免重边 无向图重边取短的
if (group.group[v1 - 1][v2 - 1] > power)
{
group.group[v1 - 1][v2 - 1] = power;
group.group[v2 - 1][v1 - 1] = power;
}
}
SpanTree[] spanTrees = new SpanTree[n];
for (int i = 0; i < spanTrees.length; i++)
{
spanTrees[i] = new SpanTree();
}
prim(group, spanTrees);
int sum=0;
for (int i = 0; i < spanTrees.length; i++)
{
System.out.println(spanTrees[i]);
sum+=spanTrees[i].weight;
}
System.out.println(sum);
}
private static void prim(Group group, SpanTree[] spanTrees)
{
int n = group.list.size();
int lowCount[] = new int[n];
for (int i = 0; i < lowCount.length; i++)
{
lowCount[i] = group.group[0][i];// 初始化lowCount数组
}
lowCount[0] = -1;//将第一个节点加入U
spanTrees[0].index = group.list.get(0);
int mincount = 0, k = 0;
for (int i = 1; i < n; i++)
{
mincount = Integer.MAX_VALUE;
for (int j = 0; j < n; j++)
{
// 找出(V-U)中距离(U)最近的
// 最近的 (V-U)中
if (lowCount[j] < mincount && lowCount[j] > 0)
{
mincount = lowCount[j];
k = j;
}
}
// 将最近的加入(V-U)中
lowCount[k] = -1;
spanTrees[i].index = group.list.get(i);
spanTrees[i].weight = mincount;
// 根据加入的k 修改找出(V-U)中距离(U)的值
for (int j = 0; j < n; j++)
{
// 如果这个节点 K 到其它节点(V-U)比原来U到(V-U)的要小 那么就修改
if (group.group[k][j] < lowCount[j])
{
lowCount[j] = group.group[k][j];
}
}
}
}
}
class Group
{
List<Integer> list;
int group[][];
public Group(List<Integer> list, int[][] group)
{
super();
this.list = list;
this.group = group;
}
@Override
public String toString()
{
return "GroupPoint [" + (list != null ? "list=" + list + ", " : "")
+ (group != null ? "group=" + Arrays.toString(group) : "") + "]";
}
public Group()
{
super();
// TODO Auto-generated constructor stub
}
}
class SpanTree
{
int index;
int weight;
public SpanTree(int index, int weight)
{
super();
this.index = index;
this.weight = weight;
}
public SpanTree()
{
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString()
{
return "SpanTree [index=" + index + ", weight=" + weight + "]";
}
}
最后扯一句,为什么这道题我不去AC呢,128MB的空间限制就算我换成邻接表的物理结构也会内存不够的。正规Java算法基本会给256MB以上的内存
7.3 生成树 把图变成树,树是没有环的
风险系数
标题:风险度量
X星系的的防卫体系包含 n 个空间站。这 n 个空间站间有 m 条通信链路,构成通信网。
两个空间站间可能直接通信,也可能通过其它空间站中转。
对于两个站点x和y (x != y), 如果能找到一个站点z,使得:
当z被破坏后,x和y不连通,则称z为关于x,y的关键站点。
显然,对于给定的两个站点,关于它们的关键点的个数越多,通信风险越大。
你的任务是:已经网络结构,求两站点之间的通信风险度,即:它们之间的关键点的个数。
输入数据第一行包含2个整数n,m(2 <= n <= 1000), m(0 <= m <= 2000),分别代表站点数,链路数。
空间站的编号从1到n。通信链路用其两端的站点编号表示。
接下来m行,每行两个整数 u,v (1 <= u, v <= n; u != v)代表一条链路。
最后1行,两个数u,v,代表被询问通信风险度的两个站点。
输出:一个整数,如果询问的两点不连通则输出-1.
例如:
用户输入:
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
则程序应该输出:
2
生成了树后 y的所有祖先都可能是割点(割掉以后不连通,候选集合)
如果此候选点为根的子树有孩子返祖超过了当前候选点的高度,那么就不是敏感的割点
最高反祖:以我当前节点为根的子树家族的所有节点,返回到最祖先的那个节点返祖值(最高的)
package 图及其他;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class 生成树风险系数
{
static final int VISED=0;
static final int DEEP=1;
static final int FATHER=2;
static final int HIGH_ORIGIN=3;
public static void main(String[] args) throws NumberFormatException, IOException
{
BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));
String []strings=bufferedReader.readLine().trim().split(" ");
//节点数
int n=Integer.parseInt(strings[0]);
//边数
int m=Integer.parseInt(strings[1]);
List<Integer>[] list=new List[m+1];//每个顶点->其他顶点
for (int i = 0; i < list.length; i++)
{
list[i]=new ArrayList<>();
}
int [][]resultTree=new int[m+1][4];//生成树结果 1.访问过 1/0 2.深度 3.父节点 4.最高反祖
//input
for (int i = 0; i < m; i++)
{
strings=bufferedReader.readLine().trim().split(" ");
int v1=Integer.parseInt(strings[0]);
int v2=Integer.parseInt(strings[1]);
list[v1].add(v2);
list[v2].add(v1);
}
strings=bufferedReader.readLine().split(" ");
int a=Integer.parseInt(strings[0]);
int b=Integer.parseInt(strings[1]);
resultTree[a][VISED]=1;//访问过
resultTree[a][DEEP]=0;//深度
resultTree[a][FATHER]=0;//父节点
resultTree[a][HIGH_ORIGIN]=0;//最高反祖
dfs(list,resultTree,a,b);
System.out.println(solve(resultTree,a,b));
}
/**
*
* @param resultTree 生成树
* @param root 根
* @param leaf 叶子
* @return
*/
private static int solve(int[][] resultTree, int root, int leaf)
{
int sum=0;
int p=leaf;
int high_fan_zu=resultTree[p][HIGH_ORIGIN];//当前最高(小)反祖
while(true)
{
int pa=resultTree[p][FATHER];
System.out.println("pa "+pa);
//达到了root
if (pa==0||pa==root)
{
break;
}
//父节点深度小于high_fan_zu
//说明当前已知 还没有高过这个父节点 如果我删掉 下面的孩子就连不上了
//说明是合格的割点
if (resultTree[pa][DEEP]<=high_fan_zu)
{
System.out.println("gedian pa "+pa);
sum++;
}
//pa的反祖级更高
if (resultTree[pa][HIGH_ORIGIN]<high_fan_zu)
{
high_fan_zu=resultTree[pa][HIGH_ORIGIN];
}
p=pa;
}
return sum;
}
/**
* dfs生成树
* @param list 那个节点连接的那个节点
* @param resultTree 生成树
* @param a 当前的根
* @param b 目标的目标
*/
private static void dfs(List<Integer>[] list, int[][] resultTree, int v1, int v2)
{
if (v1==v2)
{
return ;
}
//遍历所有与v1连着的节点
for (Integer is : list[v1])
{
//遍历过程中碰到了访问过的节点 就做返祖
if (resultTree[is][VISED]>0)
{
//试图找孩子的时候装上了祖先
fan_zu(resultTree,v1,resultTree[is][DEEP]);
continue;
}
resultTree[is][VISED]=1;//标记为访问过
resultTree[is][DEEP]=resultTree[v1][DEEP]+1;//深度+1
resultTree[is][FATHER]=v1;//父节点
resultTree[is][HIGH_ORIGIN]=resultTree[v1][DEEP];//刚加入进来返祖为父节点
dfs(list, resultTree, is, v2);
}
}
/**
* 返祖
* @param resultTree 生成树结果
* @param me 当前找孩子节点碰到已经访问过了的
* @param goal 访问过的目标深度
*/
private static void fan_zu(int[][] resultTree, int me, int goal)
{
//此节点深度没有最大返祖大
if (resultTree[me][HIGH_ORIGIN]<=goal)
{
return;
}
//改变返祖
resultTree[me][HIGH_ORIGIN]=goal;
//继续让父节点更新返祖值 将祖先返祖
fan_zu(resultTree, resultTree[me][FATHER], goal);
}
}