图和BFS算法

您好(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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值