标签:
英语中,有些单词可以出现在其他单词后面。例如“Love”可以出现在“I”之后,“You”可以出现在“Love”之后,因此它们能构成“I Love You”这句话。
现在给你一些单词间的关系,你能计算出最多能有几个单词组合在一起构成一句话吗?
输入包含多组数据。
每组数据的第一行包含一个正整数n (1≤n≤10000)。
紧接着n行单词关系,每行包含两个单词A和B,表示单词B能出现在A后面。单词长度不超过32个字符,并且只有字母组成。
不存在循环的依赖关系。
对应每组数据,输出最多有几个单词能构成一个句子。
1
hello world
3
I love
love you
love me
2
3
假设某一组数据有n条,第i条记录为(
假设有输入记录:(A,B)、(B,C)、(C,D)、(B,D)、(E,F)、(F,G)、(C,E)、(I,B)、(A,F)。根据输入的先后顺序构造一个有向图,有向图的构造如图2-1所示。
图2-1 根据输入构造有向无环图
根据输入的添加过程,可以知道构造有向无环图的过程。假设G是有向图,V是图G的顶点集合。对于某一个输入序列
如果
如果只有
如果只有
如果
对所有的输入序列
对有向无环图G的每一个起始顶点进行深度优先遍历,最长的路径的顶点数就是所求的单词个数。图1中(A,B,C,E,F,G) (I,B,C,E,F,G)和就是最长的路径,顶点数为6,所以单词个数为6。
深度优先遍历非常耗时,所以可以在步骤一的过程中记录以顶点V为结束点的最长有向线段的顶点数。当图构建完成后,可以对图的所有顶点遍历一次,找出V对应用的最大值就可以了。
import java.util.*;
/**
* 解法一会生产超时
* Author: 王俊超
* Time: 2016-05-10 10:58
* CSDN: http://blog.csdn.net/derrantcm
* Github: https://github.com/Wang-Jun-Chao
* Declaration: All Rights Reserved !!!
*/
public class Main {
/**
* 有向图
*/
private static class G {
// 顶点集合,通过顶点的名称来找顶点。
private final Map<String, V> VERTEX = new HashMap<>();
// 有向无环图的起始顶点,通过顶点的名称来找起始顶点。
private final Map<String, V> STARTING = new HashMap<>();
}
/**
* 图的顶点对象,使用图的邻接表表示
*/
private static class V {
// 顶点的名称
private String n;
// 邻接点
private final Set<V> ADJ = new HashSet<>();
V(String n) {
this.n = n;
}
}
public static void main(String[] args) {
// Scanner scanner = new Scanner(System.in);
Scanner scanner = new Scanner(Main.class.getClassLoader().getResourceAsStream("data2.txt"));
while (scanner.hasNext()) {
// 创建一个图对象
G g = new G();
int n = scanner.nextInt();
for (int i = 0; i < n; i++) {
String a = scanner.next();
String b = scanner.next();
addEdge(g, a, b);
}
System.out.println(getLongestPathLength(g));
}
scanner.close();
}
/**
* 求图g最长路径的长度
* TIP: 这是一个非常耗时的方法
*
* @param g 图
* @return 最长路径的长度
*/
private static int getLongestPathLength(G g) {
if (g == null || g.VERTEX.isEmpty()) {
return 0;
}
int[] r = {0};
int[] t = {0};
Collection<V> vs = g.STARTING.values();
for (V v : vs) {
t[0] = 0;
findLongestPathLength(v, t, r);
}
return r[0];
}
/**
* 找以v顶点开始的最长路径的长度
*
* @param v 顶点
* @param curr 从最开始到当前处理的顶点的上一个顶点,一个有curr个顶点
* @param result 长度为1的数组,用于记录结果,记录最长路径的顶点数
*/
private static void findLongestPathLength(V v, int[] curr, int[] result) {
curr[0]++;
if (result[0] < curr[0]) {
result[0] = curr[0];
}
Collection<V> vs = v.ADJ;
// 处理领接点
for (V t : vs) {
findLongestPathLength(t, curr, result);
}
// 现场还原
curr[0]--;
}
/**
* 向图g中添加边(a, b);
*
* @param g 图
* @param a 边的起始点
* @param b 边的终点
*/
private static void addEdge(G g, String a, String b) {
// 判断两个顶点是否都在图中
boolean ca = g.VERTEX.containsKey(a);
boolean cb = g.VERTEX.containsKey(b);
// 两个顶点都已经存在了
if (ca && cb) {
// 将b设置为a的邻接点
g.VERTEX.get(a).ADJ.add(g.VERTEX.get(b));
}
// 顶点a已经存在,b不存在
else if (ca && !cb) {
V bv = new V(b);
// 将顶点b放到顶点集合中
g.VERTEX.put(b, bv);
// 将b设置为a的邻接点
g.VERTEX.get(a).ADJ.add(bv);
}
// 顶点a不存存,b存在
else if (!ca && cb) {
V av = new V(a);
// 将顶点a放到顶点集合中
g.VERTEX.put(a, av);
// 将b设置为a的邻接点
av.ADJ.add(g.VERTEX.get(b));
// 如果b起始顶点,加入(a, b)边之后,b就不是起始顶点了
if (g.STARTING.containsKey(b)) {
g.STARTING.remove(b);
}
// a是新的起始顶点
g.STARTING.put(a, av);
}
// 两个顶点都不在图中
else {
// 构造两个顶点
V av = new V(a);
V bv = new V(b);
// 将b设置为a的邻接点
av.ADJ.add(bv);
// 将顶点a、b放到顶点集合中
g.VERTEX.put(a, av);
g.VERTEX.put(b, bv);
// a为起始顶点
g.STARTING.put(a, av);
}
}
}
import java.util.*;
/**
* 解法二
* Author: 王俊超
* Time: 2016-05-10 22:01
* CSDN: http://blog.csdn.net/derrantcm
* Github: https://github.com/Wang-Jun-Chao
* Declaration: All Rights Reserved !!!
*/
public class Main2 {
public static void main(String[] args) {
// Scanner scanner = new Scanner(System.in);
Scanner scanner = new Scanner(Main.class.getClassLoader().getResourceAsStream("data2.txt"));
while (scanner.hasNext()) {
int row = scanner.nextInt();
// 顶点集合,同是记录顶点为终点的最长有向线段的顶点数
// key(=String)为起始顶点,value(=Integer)为以key结点的最长有向线段的顶点数,当只有一个顶点时value为1
Map<String, Integer> vertex = new HashMap<>();
// 图
// 记录以key(=String)开为起始顶点的有向边,value(List<String>)邻接顶点集合
Map<String, List<String>> graph = new HashMap<>();
for (int i = 0; i < row; i++) {
// 输入两个单词,同时也表示两个顶表示的有向边
String a = scanner.next();
String b = scanner.next();
// 如果是新的顶点,就加入到顶点集合中
if (!vertex.containsKey(a)) {
vertex.put(a, 1);
}
if (!vertex.containsKey(b)) {
vertex.put(b, 1);
}
// 获取顶点a的有邻接顶点集合,如果集合不存就创建
List<String> list = graph.get(a);
if (list == null) {
list = new ArrayList<>();
graph.put(a, list);
}
// 添加a的邻接顶点b
list.add(b);
visitAll(a, b, vertex, graph);
}
int max = 0;
for (Integer val : vertex.values()) {
if (val > max) {
max = val;
}
}
System.out.println(max);
}
}
/**
* 更新以b为终点的最长有向线段的顶点数,其中(a, b)表示新添加的有向线段
*
* @param a 顶点
* @param b 顶点
* @param vertex 顶点集合
* @param graph 有向图
*/
private static void visitAll(String a, String b, Map<String, Integer> vertex, Map<String, List<String>> graph) {
// 以b为终点的最长线段包含的顶点数
int val = vertex.get(b);
// 原先以a为终点的最长线段包含的顶点数,再加上1,表示从包含(a, b),
// 以b为终点的最长线段包含的顶点数
int t = vertex.get(a) + 1;
// 记录以b为终点的最长有向线段的顶点数
if (val < t) {
vertex.put(b, t);
// 接在b后面的顶点都要进行更新
List<String> list = graph.get(b);
if (list != null) {
for (String s : list) {
visitAll(b, s, vertex, graph);
}
}
}
// 以b为终点的最长有向线段的顶点数没有发生变化,就不需要再进行处理
}
}
import java.util.*;
/**
* 解法三:
* 因为解法一(Main)会生产超时,现在对他进行改进
* Author: 王俊超
* Time: 2016-05-10 10:58
* CSDN: http://blog.csdn.net/derrantcm
* Github: https://github.com/Wang-Jun-Chao
* Declaration: All Rights Reserved !!!
*/
public class Main3 {
/**
* 有向图
*/
private static class G {
// 顶点集合,通过顶点的名称来找顶点。
private final Map<String, V> VERTEX = new HashMap<>();
}
/**
* 图的顶点对象,使用图的邻接表表示
*/
private static class V {
// 顶点的名称
private String n;
// 以当前顶点为终点的最长有向线段的顶点数,只有一个顶点时为1
private int v;
// 邻接点
private final Set<V> ADJ = new HashSet<>();
V(String n, int v) {
this.n = n;
this.v = v;
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Scanner scanner = new Scanner(Main3.class.getClassLoader().getResourceAsStream("data2.txt"));
// 创建一个图对象,可以重复使用
G g = new G();
while (scanner.hasNext()) {
// 清空内容
g.VERTEX.clear();
int n = scanner.nextInt();
for (int i = 0; i < n; i++) {
String a = scanner.next();
String b = scanner.next();
addEdge(g, a, b);
}
System.out.println(getLongestPathLength(g));
}
scanner.close();
}
/**
* 求图g最长路径的长度
*
* @param g 图
* @return 最长路径的长度
*/
private static int getLongestPathLength(G g) {
if (g == null || g.VERTEX.isEmpty()) {
return 0;
}
int max = 0;
Collection<V> vs = g.VERTEX.values();
for (V v : vs) {
if (max < v.v) {
max = v.v;
}
}
return max;
}
/**
* 向图g中添加边(a, b);
*
* @param g 图
* @param a 边的起始点
* @param b 边的终点
*/
private static void addEdge(G g, String a, String b) {
// // 判断两个顶点是否都在图中
// boolean ca = g.VERTEX.containsKey(a);
// boolean cb = g.VERTEX.containsKey(b);
V av = g.VERTEX.get(a);
V bv = g.VERTEX.get(b);
if (av == null) {
av = new V(a, 1);
// 将顶点a放到顶点集合中
g.VERTEX.put(a, av);
}
if (bv == null) {
bv = new V(b, 1);
// 将顶点b放到顶点集合中
g.VERTEX.put(b, bv);
}
// 将b设置为a的邻接点
g.VERTEX.get(a).ADJ.add(g.VERTEX.get(b));
update(g.VERTEX.get(a), g.VERTEX.get(b), g);
}
/**
* 更新结束顶点的长度记数
*
* @param a 顶点
* @param b 顶点
* @param g 图
*/
private static void update(V a, V b, G g) {
// 原先以a为终点的最长线段包含的顶点数,再加上1,表示从包含(a, b),
// 以b为终点的最长线段包含的顶点数
int lenA = a.v + 1;
// 以b为终点的最长线段包含的顶点数
int lenB = b.v;
if (lenA > lenB) {
b.v = lenA;
Set<V> vs = b.ADJ;
for (V v : vs) {
update(b, v, g);
}
}
}
}
因为markddow不好编辑,因此将文档的图片上传以供阅读。Pdf和Word文档可以在Github上进行【下载>>>】。
标签:
原文地址:http://blog.csdn.net/derrantcm/article/details/51643744