图表与其他

邻接矩阵

逻辑结构分为两部分:V和E集合,其中,V是顶点,E是边。因此,用一个一维数组存放图中所有顶点数据;用一个二维数组存放顶点间关系(边或弧)的数据,这个二维数组称为邻接矩阵。邻接矩阵又分为有向图邻接矩阵和无向图邻接矩阵。

因此只要对任意一个图,都可以使用邻接矩阵表示。

邻接矩阵(Adjacency Matrix)是表示顶点之间相邻关系的矩阵。设G=(V,E)是一个图,其中V={v1,v2,…,vn} 。G的邻接矩阵是一个具有下列性质的n阶方阵:
1. 对无向图而言,邻接矩阵一定是对称的,而且主对角线一定为零(在此仅讨论无向简单图),副对角线不一定为0,有向图则不一定如此。
2. 在无向图中,任一顶点i的度为第i列(或第i行)所有非零元素的个数,在有向图中顶点i的出度为第i行所有非零元素的个数,而入度为第i列所有非零元素的个数。
3. 用邻接矩阵法表示图共需要n^2个空间,由于无向图的邻接矩阵一定具有对称关系,所以扣除对角线为零外,仅需要存储上三角形或下三角形的数据即可,因此仅需要n(n-1)/2个空间。

这样我们就可以把一个抽象的图,转化为程序可以识别的矩阵数学逻辑问题了。

深度优先遍历

深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search。其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。
深度优先遍历图的方法是,从图中某顶点v出发:
(1)访问顶点v;
(2)依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
(3)若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。

深度优先遍历具有探索特性,可以优先寻找一切能够实现的路径。因此,连通性类的所有题目适用于深度优先遍历。

连通性判断

【问题描述】给定一个方阵,定义连通:上下左右相邻,并且值相同。
可以想象成一张地图,不同的区域被涂以不同颜色。
输入:
整数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

import java.util.Scanner;

public class Connectivity {
    
    
	static boolean connect(char[][] data,int y1,int x1,int y2,int x2) {
    
    
		if(y1 == y2 && x1 == x2)
			return true;
		char old = data[y1][x1];
		data[y1][x1] = '*';
		try {
    
    
			if(y1 > 0 && data[y1 - 1][x1] == old && connect(data,y1 - 1,x1,y2,x2))
				return true;
			if(y1 < data.length-1 && data[y1 + 1][x1] == old && connect(data,y1 + 1,x1,y2,x2))
				return true;
			if(x1 > 0 && data[y1][x1 - 1] == old && connect(data,y1,x1 - 1,y2,x2))
				return true;
			if(x1 < data.length-1 && data[y1][x1 + 1] == old && connect(data,y1,x1 + 1,y2,x2))
				return true;
		}
		finally{
    
    
			data[y1][x1] = old;
		}
		return false;
	}
	public static void main(String[] args) {
    
    
		Scanner sc = new Scanner(System.in);
		int N = Integer.parseInt(sc.nextLine());
		char[][] data = new char[N][];
		for(int i=0; i<N; i++){
    
    
			data[i] = sc.nextLine().toCharArray();
		}
		
		int M = Integer.parseInt(sc.nextLine());
		for(int i=0; i<M; i++){
    
    
			String[] ss = sc.nextLine().split(" ");
			int y1 = Integer.parseInt(ss[0]);
			int x1 = Integer.parseInt(ss[1]);
			int y2 = Integer.parseInt(ss[2]);
			int x2 = Integer.parseInt(ss[3]);
			
			System.out.println(connect(data,y1,x1,y2,x2));
		}
	} 
}

风险度量

【问题描述】
标题:风险度量
X星系的的防卫体系包含 n 个空间站。这 n 个空间站间有 m 条通信链路,构成通信网。
两个空间站间可能直接通信,也可能通过其它空间站中转。
对于两个站点x和y (x != y), 如果能找到一个站点z,使得:
当z被破坏后,x和y不连通,则称z为关于x,y的关键站点。
显然,对于给定的两个站点,关于它们的关键点的个数越多,通信风险越大。
你的任务是:已经网络结构,求两站点之间的通信风险度,即:它们之间的关键点的个数。
输入数据第一行包含2个整数n(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

import java.util.List;
import java.util.Scanner;
import java.util.Vector;

public class RiskMeasurement {
    
    
	static void fan_zu(int[][] re, int me, int goal){
    
    
		if(re[me][3] <= goal) return;
		re[me][3] = goal;
		fan_zu(re, re[me][2], goal); 
	}
	
	static void dfs(List[] gr, int[][] re, int v1, int v2){
    
    
		if(v1==v2) return;
		
		for(Object obj: gr[v1]){
    
    
			int it = (Integer)obj;
			if(re[it][0] > 0){
    
    
				fan_zu(re,v1,re[it][1]);  // 更新整个家族的返祖级
				continue;
			}
			
			re[it][0] = 1;
			re[it][1] = re[v1][1]+1;
			re[it][2] = v1;
			re[it][3] = re[v1][1];
			dfs(gr,re,it,v2);
		}
	}
	
	static int solve(int[][] re, int root, int leaf){
    
    
		int sum = 0;
		int p = leaf;
		int min = re[p][3];  // 当前最高(小)返祖级
		while(true){
    
    
			int pa = re[p][2];
			System.out.println("pa " + pa);
			if(pa==0 || pa==root) break;
			if(re[pa][1] <= min) sum++;
			if(re[pa][3] < min) min = re[pa][3];
			p = pa;
		}
		return sum;
	}
	
	public static void main(String[] args)
	{
    
    
		Scanner scan = new Scanner(System.in);
		String[] ss = scan.nextLine().trim().split(" ");
		int m = Integer.parseInt(ss[0]); //节点数
		int n = Integer.parseInt(ss[1]); //边数
		
		List[] gr = new List[m+1];    // 顶点 --> list(顶点)
		for(int i=0; i<gr.length; i++) gr[i] = new Vector();
		
		int[][] re = new int[m+1][4]; // dfs生成树结果: 顶点 --> (可见性, 深度, 父节点, 最高返祖) 
		
		for(int i=0; i<n; i++){
    
    
			ss = scan.nextLine().trim().split(" ");
			int v1 = Integer.parseInt(ss[0]);
			int v2 = Integer.parseInt(ss[1]);
			gr[v1].add(v2);
			gr[v2].add(v1);
		}
		
		ss = scan.nextLine().trim().split(" ");
		int a = Integer.parseInt(ss[0]);
		int b = Integer.parseInt(ss[1]);
		
		re[a][0] = 1;
		re[a][1] = 0;
		re[1][3] = 0;
		dfs(gr,re,a,b);	
		
		System.out.println(solve(re,a,b));	
	}
	
	static void debug(int[][] re){
    
    
		for(int i=0; i<re.length; i++){
    
    
			System.out.println(i + ": " + re[i][0] + "," + re[i][1] + "," + re[i][2] + "," + re[i][3]);
		}
	}
}

广度优先遍历

宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。

广度优先遍历方法类似于层序遍历,从一个点开始,所有路径同时开始遍历,像一个潮水,从开始到结尾,所有路径将被一次性遍历。

广度优先遍历具有扫描特性,可以同时探索所有路径,因此首先返回的一定是最短路径。因此,广度优先遍历广泛用于寻找最短路径算法和最少操作数的题目。

迷宫问题

【问题描述】
…11111111111111111111111111111
11.111111…1111111111.1111
11.111111…111.11111111…1111
11.11111111111.1111111111.111111
11.111111…111111
11.111111.11111111111.11111.1111
11.111111.11111111111.11111…111
11…111111111.11111.1111
11111.111111111111111.11…1111
11111.111111111111111.11.11.1111
11111.111111111111111.11.11.1111
111…111111111111111.11.11.1111
111.11111111111111111…11.1111
111.11111111111111111111111.1111
111.1111.111111111111111…11
111.1111…111111111.1111.11
111.1111.11111.111111111.1111.11
111…11111.111111111.1111111
11111111111111.111111111.111…1
11111111111111…1.1
111111111111111111111111111111…
如上图的迷宫,入口,出口分别:左上角,右下角
“1"是墙壁,”."是通路
求最短需要走多少步?

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class MazeProblem {
    
    		
	static int maze(char[][] data,Set come,String goal) {
    
    
		if(come.contains(goal))
			return 0;
		
		Set set = new HashSet();
		
		for(Object object : come) {
    
    
			String[] crr = ((String)object).split(",");
			
			int y = Integer.parseInt(crr[0]);
			int x = Integer.parseInt(crr[1]);
			
			if(y > 0 && data[y - 1][x] == '.') {
    
    
				data[y - 1][x] = '*';
				set.add(y-1 + "," + x);
			}
			
			if(y < data.length - 1 && data[y + 1][x] == '.') {
    
    
				data[y + 1][x] = '*';
				set.add(y+1 +","+x);
			}
			
			if(x > 0 && data[y][x - 1] == '.') {
    
    
				data[y][x - 1] = '*';
				set.add(y+","+(x - 1));
			}
			
			if(x < data[0].length - 1 && data[y][x + 1] == '.') {
    
    
				data[y][x + 1] = '*';
				set.add(y+","+(x + 1));
 			}
		}
		if(set.isEmpty())
			return -1;
		int r = maze(data,set,goal);
		if(r < 0)
			return r;
		return r + 1;
	}
	public static void main(String[] args) {
    
    
		Scanner sc = new Scanner(System.in);
		int n = Integer.parseInt(sc.nextLine());
		int m = Integer.parseInt(sc.nextLine());
		
		char[][] data = new char[n][];
		for(int i = 0;i<data.length;i++) {
    
    
			data[i] = sc.nextLine().trim().toCharArray();
		}
		
		Set set = new HashSet();
		set.add("0,0");
		System.out.println(maze(data,set,n-1 + "," + (m-1)));
	}
}

分酒问题(韩信走马分油)

有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

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class LiquorDistribution {
    
    
	static Set dispose(String liquor) {
    
    
		int[] bottle = {
    
    9,7,4,2};//瓶子的容积
		int[] data = new int[4];
		String[] ss = liquor.split(" ");
		for(int i = 0;i<4;i++)
			data[i] = Integer.parseInt(ss[i]);
		
		Set set = new HashSet();
		for(int i = 0;i<4;i++) {
    
    //以i所在的瓶子为酒
			for(int j = 0;j<4;j++) {
    
    //以j所在瓶子为目标,将i向j倒入
				if(i == j)
					continue;
				if(data[i] == 0)
					continue;
				if(data[j] == bottle[j])
					continue;
				
				int sum = data[i] + data[j];
				int Vi = (sum <= bottle[j]) ? 0 : (sum - bottle[j]);//i所在的酒的瓶子
				int Vj = (sum <= bottle[j]) ? sum : bottle[j];//j所在的目标的的瓶子
				
				String result = "";
				for(int k = 0;k<4;k++) {
    
    
					if(k == i)
						result += Vi + " ";
					else if(k == j)
						result += Vj + " ";
					else
						result += data[k] + " ";
				}
				
				set.add(result.trim());
			}
		}
		return set;
	}
	static int distribute(Set history,Set begin,String end) {
    
    
		if(begin.contains(end))
			return 0;
		Set set = new HashSet();
		
		for(Object obj:begin) {
    
    
			Set temp = dispose(obj.toString());
			set.addAll(temp);
		}
		set.removeAll(history);
		if(set.isEmpty())
			return -1;
		history.addAll(set);
		int r = distribute(history,set,end);
		if(r < 0)
			return r;
		return r + 1;
	}
	
	public static void main(String[] args) {
    
    
		Scanner sc = new Scanner(System.in);
		
		Set begin = new HashSet();
		begin.add("9 0 0 0");
		Set history = new HashSet();
		history.addAll(begin);
		System.out.println(distribute(history,begin,sc.nextLine().trim()));
	}
}

青蛙交换问题

【问题描述】
X星球的流行宠物是青蛙,一般有两种颜色:白色和黑色。
X星球的居民喜欢把它们放在一排茶杯里,这样可以观察它们跳来跳去。
如下图,有一排杯子,左边的一个是空着的,右边的杯子,每个里边有一只青蛙。
*WWWBBB
其中,W字母表示白色青蛙,B表示黑色青蛙,*表示空杯子。
X星的青蛙很有些癖好,它们只做3个动作之一:

  1. 跳到相邻的空杯子里。
  2. 隔着1只其它的青蛙(随便什么颜色)跳到空杯子里。
  3. 隔着2只其它的青蛙(随便什么颜色)跳到空杯子里。

对于上图的局面,只要1步,就可跳成下图局面:
WWW · BBB
本题的任务就是已知初始局面,询问至少需要几步,才能跳成另一个目标局面。
输入为2行,2个串,表示初始局面和目标局面。
输出要求为一个整数,表示至少需要多少步的青蛙跳。
例如:
输入:
· WWBB
WWBB ·
则程序应该输出:
2
再例如,
输入:
WWW · BBB
BBB · WWW
则程序应该输出:
10
我们约定,输入的串的长度不超过15

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class frogJump {
    
    
	static void move(char[] arr,int i,int step,Set frogs) {
    
    
		if(arr[i] == '*')
			return;
		int moved = i + step;
		if(moved < 0 || moved >= arr.length)
			return;
		if(arr[moved] != '*')
			return;
		
		arr[moved] = arr[i];
		arr[i] = '*';
		frogs.add(new String(arr));
		arr[i] = arr[moved];
		arr[moved] = '*';
	}
	static Set Frog(String frog) {
    
    
		Set s = new HashSet();
		char[] arr = frog.toCharArray();
		for(int i = 0;i<arr.length;i++) {
    
    
			move(arr,i,-1,s);
			move(arr,i,-2,s);
			move(arr,i,-3,s);
			move(arr,i,1,s);
			move(arr,i,2,s);
			move(arr,i,3,s);
		}
		return s;
	}
	static int Jump(Set begin,Set history,String goal) {
    
    
		if(begin.contains(goal))
			return 0;
		Set set = new HashSet();
		for(Object obj:begin) {
    
    
			Set temp = Frog(obj.toString());
			set.addAll(temp);
		}
		set.removeAll(history);
		if(set.isEmpty())
			return -1;
		history.addAll(set);
		int r = Jump(set,history,goal);
		if(r < 0)
			return r;
		return r+1;
	}
	public static void main(String[] args) {
    
    
		Scanner sc = new Scanner(System.in);
		
		Set begin = new HashSet();
		begin.add(sc.nextLine().trim());
		Set history = new HashSet();
		history.addAll(begin);
		System.out.println(Jump(begin,history,sc.nextLine().trim()));
	}
}

猜你喜欢

转载自blog.csdn.net/qq_40893595/article/details/107656578