您好(Java)爱好者,
在本文中,我想向您介绍一些有关图形以及如何使用图形的知识
使用BFS(宽度优先搜索)算法搜索图形。
我将解决以下问题,并希望回答以下问题:
•什么是图?
•图形如何表示为ADT?
•我们如何使用BFS算法搜索/浏览图形?
什么是图?
图是由一组顶点和一个集合组成的(数据)结构
该组对的顶点对。 这些对表示顶点如何
该图相互连接。
以好莱坞的电影业为例:有些电影是
由演员制作的影片(为简单起见,我省略了女演员,导演,制片人,
等,这当然也是电影的一部分!)。 电影和演员是
该图的顶点以及演员与电影的关系是
称为边缘。
这是一个小图的示例,其中罗马数字是电影,而罗马数字是
小写字母字符是参与者:
这样的图称为非加权无向图。 无向意味着
是从演员到电影的关系(边缘),并且相同的关系可以追溯到过去。
认为它是一条双向道路,您可以双向行驶。 不加权意味着
从一个顶点到另一个顶点不会“花费”任何东西。
加权双向(无向和有向)图的一个很好的例子是
街道网络。 交叉点(和死角)是顶点和道路
连接这些顶点的是边。 它是双向的,因为有
两路和单路街道,之所以加权,是因为
顶点。
好吧,这一切都很好,我听说您在想,但是…
图如何表示为ADT?
如上一段所述:图形中的顶点是唯一的。
每个顶点“指向”与其连接的顶点列表。 所以,
java.util.Map接口是处理此类问题的理想结构。 对于那些
不知道它是什么的人,java.util.Map是标准中的ADT
JDK拥有一个唯一键,并将一个值映射到该键。 详情请
请参阅API文档:
http://java.sun.com/j2se/1.5.0/docs/.../util/Map.html为了存储边缘的集合,我们只需使用java.util.List。 这种
图的表示形式称为邻接表。
在Graph类中我们还需要什么? 首先,我们需要构建
以下功能:
•我们需要能够添加和连接顶点;
•一种解析保存我们图形数据的文本文件的方法;
•可能重写toString()方法以检查图形是否正确构建;
并且,对于下一部分中的BFS算法,我们将需要能够:
•获取特定顶点的边缘;
•检查某个顶点是否连接到另一个顶点。
到目前为止,这是Graph类的实现:
import java.io.*;
import java.util.*;
public class Graph {
private Map<String, List<String>> adjacencyList;
/**
* Instatiates the 'adjacencyList' and then parse the data file.
*/
public Graph(String fileName) throws FileNotFoundException {
adjacencyList = new HashMap<String, List<String>>();
parseDataFile(fileName);
}
/**
* This is an undirected graph, so we connect 'vertexA' to 'vertexB'
* and the other way around.
*/
public void addConnection(String vertexA, String vertexB) {
connect(vertexA, vertexB);
connect(vertexB, vertexA);
}
/**
* A private helper-method to connect 'vertexA' to 'vertexB'.
* If 'vertexA' alreay exists in our 'adjacencyList', get it's
* edges-list and add 'vertexB' to it. If it doesn't exist,
* create a new ArrayList, add 'vertexB' to it and put it all
* in our 'adjacencyList'.
*/
private void connect(String vertexA, String vertexB) {
List<String> edges;
if(adjacencyList.containsKey(vertexA)) {
edges = adjacencyList.get(vertexA);
edges.add(vertexB);
} else {
edges = new ArrayList<String>();
edges.add(vertexB);
this.adjacencyList.put(vertexA, edges);
}
}
/**
* Returns true iff 'vertexA' poits to to 'vertexB'.
* Note that since this is an undirected graph, we do not
* need to check the other way around, the case if 'vertexB'
* is points to 'vertexA'.
*/
public boolean isConnectedTo(String vertexA, String vertexB) {
List<String> edges = getEdges(vertexA);
return edges.contains(vertexB);
}
/**
* Returns all the edges of a certain vertex, or throws an
* exception if the vertex doesn't exist in this graph.
*/
public List<String> getEdges(String vertex) {
List<String> edges = adjacencyList.get(vertex);
if(edges == null) {
throw new RuntimeException(vertex+" not present in the graph.");
}
return edges;
}
/**
* Reads a text file with the graph-data. The text file contains
* N-blocks of lines where each block starts with the movie followed
* by N-lines of text representing the actors and ending with an
* empty line.
*/
private void parseDataFile(String fileName) throws FileNotFoundException {
Scanner file = new Scanner(new File(fileName));
while(file.hasNextLine()) {
String movie = file.nextLine().trim();
while(file.hasNextLine()) {
String actor = file.nextLine().trim();
if(actor.length() == 0) break;
addConnection(movie, actor);
}
}
}
/**
* A Sting representation if this Graph.
*/
public String toString() {
StringBuilder builder = new StringBuilder();
Iterator<String> vertices = adjacencyList.keySet().iterator();
while(vertices.hasNext()) {
String vertex = vertices.next();
List<String> edges = adjacencyList.get(vertex);
builder.append(vertex);
builder.append(" --> ");
builder.append(edges);
builder.append('\n');
}
return builder.toString();
}
/**
* main
*/
public static void main(String[] args) {
try {
Graph graph = new Graph("data.txt");
System.out.println(graph);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
这是我发布图像的图形的data.txt文件的内容:
I
a
b
c
d
e
II
c
h
q
r
III
h
o
p
IV
e
f
g
V
c
g
j
VI
k
m
n
o
如您所见,如果您运行此代码,则图已正确构建。
所以,
我们准备在图表上进行一些蛮力搜索。
我们如何使用BFS算法搜索/浏览图形?
首先:什么是BFS? BFS是一种蛮简单的方法
从给定的顶点开始遍历图形。 它将图分为
几个级别。 BFS的起点称为0级,它是直接的
边缘级别1,边缘级别2的边缘,依此类推。 你可以比较
当您在水池中扔石头时,它会出现:起始顶点是
石头碰到水,冲击的涟漪正在“发现”
未知领域。 因此,该算法可用于查找最短路径
在两个顶点之间。 在这种情况下,最短路径是指从
一个顶点到另一个顶点,同时遍历最少数量的边。
请注意,我说“在这种情况下”是因为在加权图的情况下,
最短路径不一定是边缘最少的路径:一条直接道路
在两个长度为10英里的顶点之间,比两个具有
当然是4英里长。
假设要找到顶点g和g之间的最短路径
。 如您所见,它们之间有两条可能的路径:
g -> IV -> e -> I -> c -> II -> h -> III -> o -> VI -> n
和
g -> V -> c -> II -> h -> III -> o -> VI -> n
针说,我们有兴趣找到第二条路径,即
最短的。
为了使事情井然有序,我制作了一个单独的类,该类从
文本文件,并找到两个顶点之间的最短路径。
发生的第一件事是在BFS遍历期间,一个“容器”是
由新发现的顶点创建,其中第一个顶点(起点
点)位于索引0(级别0)上,边缘位于索引1(级别1)上,依此类推。
因此,在发现我们的最终顶点之后,该“容器”将如下所示:
level 0 = g
level 1 = IV, V
level 2 = e, f, c, j
level 3 = I, II
level 4 = a, b, d, h, q, r
level 5 = III
level 6 = o, p
level 7 = VI
level 8 = n
之后,我们从终点(“ n”)向起点追溯
(“ g”)使用Graph类中的areConnected(String,String)方法
查看是否连接了两个顶点。 这样我们就以最短的时间结束
从“ g”到“ n”的路径。
import java.io.*;
import java.util.*;
public class BFSAlgorithm {
private Graph graph;
/**
* Constructor.
*/
public BFSAlgorithm(Graph g) {
graph = g;
}
/**
* 1 - Create a stack to store all the vertices of our path on.
* 2 - First push the 'end' vertex on our stack.
* 3 - Now loop from the highest level back to the first level and
* a. loop through each level and
* b. check each vertex in that level if it's connected to the
* vertex on the top of our stack, if we find a match, push that
* match on the stack and break out of the loop.
* 4 - Now we only need to reverse the collection (stack) before returning
* the path in the "correct" order (from start to finish).
*
* Here's an example ASCII drawing of backtracking from the end vertex "n"
* to the starting vertex "g". The arrows, <-, denote the path that was
* found.
*
* level: 0 1 2 3 4 5 6 7 8
* ---------------------------------------------------------
* g <-+ IV e I a +- III <- o <- VI <- n
* +- V <-+ f +- II <-+ b | p
* +- c <-+ | d |
* j +- h <-+
* q
* r
*/
private List<String> backTrack(List<List<String>> container, String end) {
Stack<String> path = new Stack<String>(); // 1
path.push(end); // 2
for(int i = container.size()-1; i >= 0; i--) { // 3
List<String> level = container.get(i);
String last = path.peek();
for(String s : level) { // a
if(graph.areConnected(last, s)) { // b
path.push(s);
break;
}
}
}
Collections.reverse(path); // 4
return path;
}
/**
* 1 - Get the level from the 'container' which was added last.
* 2 - Create a new level to store (possible) unexplored verices in.
* 3 - Loop through each of the vertices from the last added level, and
* a. get the neighboring vertices connected to each vertex,
* b. loop through each connecting vertex and if the connecting vertex
* has not yet been visited,
* c. only then add it to the newly created level-list and mark it as
* visited in our set.
* 4 - We don't need to search any further if we stumble upon the 'end'
* vertex. In that case, just "return" from this method.
* 5 - Only make the recursive call if we have found vertices which have
* not been explored yet.
*/
private void bfs(List<List<String>> container,
String end, Set<String> visited) {
List<String> lastLevel = container.get(container.size()-1); // 1
List<String> newLevel = new ArrayList<String>(); // 2
for(String vertex : lastLevel) { // 3
List<String> edges = graph.getEdges(vertex); // a
for(String edge : edges) { // b
if(!visited.contains(edge)) { // c
visited.add(edge);
newLevel.add(edge);
}
if(edge.equals(end)) return; // 4
}
}
if(newLevel.size() > 0) { // 5
container.add(newLevel);
bfs(container, end, visited);
}
}
/**
* 1 - Create an empty 'container' to store all the levels from the
* 'start'-vertex in. A level is also a list with one or more vertices.
* 2 - The first level only holds the 'start' vertex, which is added first,
* this is the 'init' list.
* 3 - The 'start' vertex is also stored in a Set which keeps track of all
* the vertices we have encountered so that we don't traverse vertices
* twice (or more).
* 4 - Once we initialized the steps 1-3, we can call the actual BFS-
* algorithm.
* 5 - Once the BFS-algorithm is done, we call the backTrack(...) method
* to find the shortest path between 'end' and 'start' between the
* explored levels of the graph.
*/
public List<String> getShortestPath(String start, String end) {
List<List<String>> container = new ArrayList<List<String>>(); // 1
List<String> init = new ArrayList<String>(); // 2
init.add(start);
container.add(init);
Set<String> visited = new HashSet<String>(); // 3
visited.add(start);
bfs(container, end, visited); // 4
return backTrack(container, end); // 5
}
/**
* Main method:
* 1 - Create a Graph.
* 2 - Get a shortest path between two vertices.
* 3 - Print the shortest path.
*/
public static void main(String[] args) throws FileNotFoundException {
Graph graph = new Graph("data.txt"); // 1
BFSAlgorithm bfsAlgorithm = new BFSAlgorithm(graph);
List<String> shortestPath =
bfsAlgorithm.getShortestPath("g", "n"); // 2
for(int i = 0; i < shortestPath.size(); i++) {
System.out.print(shortestPath.get(i)); // 3
System.out.print(i < shortestPath.size()-1 ? "\n -> " : "\n");
}
}
}
好的,这比我预想的要多。
我希望我不会觉得无聊
你太多了 对于那些想尝试更多的人:我做了一个小
从IMDB的数据文件中选择电影和演员(+/- 70000个顶点)*
您可以尝试一下,看看演员罗恩·杰里米(Ron Jeremy)和
凯文·培根(Kevin Bacon)已连接(他们甚至已连接?)。 您可以在这里找到该文件:
http://www.iruimte.nl/graph/imdb.txt ,其解析方式与上面的文本文件。
问候,
巴特(prometheuzz)
*所有数据文件都可以在这里找到:
ftp://ftp.sunet.se/pub/tv+movies/imdb/From: https://bytes.com/topic/java/insights/665333-graphs-bfs-algorithm