每日一省之————无向图(无向非赋权图)

22 篇文章 0 订阅

已经好久没有复习数据结构了,今天复习的是无向图。由于本人确定自己写的注释还是蛮认真的,所以就直接贴代码了。接下来张贴的几段代码或者说几个类都是循序渐进的。只要你还记得图的定义(具体是无向图),应该都不难理解。好了,废话不多说,直接看代码吧。

1 无向图数据结构的构建(图的表示)



/**
 * 一个可以用来表示无向图的类
 */
public class Graph {

    private static final String NEWLINE = System.getProperty("line.separator");
    private final int V;
    private int E;
    private List<Integer>[] adj;

    /**
     * 构造
     * 
     * @param V
     */
    public Graph(int V) {
        if (V <= 0) {
            throw new IllegalArgumentException("顶点数必须是一个正整数");
        }
        this.V = V;
        this.E = 0;
        adj = (List<Integer>[]) new ArrayList[V];
        for (int v = 0; v < V; v++) {
            adj[v] = new ArrayList<Integer>();
        }

    }

    /**
     * 根据另外一个无向图构造新图
     * 
     * @param G
     */
    public Graph(Graph G) {
        this(G.V());
        this.E = G.E();
        for (int v = 0; v < G.V(); v++) {
            Stack<Integer> reverse = new Stack<Integer>();
            for (int w : G.adj[v]) {
                reverse.push(w);
            }
            for (int w : reverse) {
                adj[v].add(w);
            }
        }
    }

    /**
     * 返回顶点数
     * 
     * @return
     */
    public int V() {
        return V;
    }

    /**
     * 返回边数
     * 
     * @return
     */
    public int E() {
        return E;
    }

    /**
     * 向无向图中添加一条边
     * 
     * @param v
     * @param w
     */
    public void addEdge(int v, int w) {
        validateVertex(v);
        validateVertex(w);
        E++;
        adj[v].add(w);
        adj[w].add(v);
    }

    /**
     * 返回与顶点v直接相连的其他顶点
     * 
     * @param v
     * @return
     */
    public Iterable<Integer> adj(int v) {
        validateVertex(v);
        return adj[v];
    }

    /**
     * 返回顶点v的度数,也即与顶点v直接相连的顶点的个数
     * 
     * @param v
     * @return
     */
    public int degree(int v) {
        validateVertex(v);
        return adj[v].size();
    }

    private void validateVertex(int v) {
        if (v < 0 || v >= V)
            throw new IndexOutOfBoundsException("顶点 " + v + "不在 0 和 " + (V - 1) + "之间");
    }

    @Override
    public String toString() {
        StringBuffer s = new StringBuffer();
        s.append(V + " 个顶点, " + E + " 条边 " + NEWLINE);
        for (int v = 0; v < V; v++) {
            s.append(v + ": ");
            for (int w : adj[v]) {
                s.append(w + " ");
            }
            s.append(NEWLINE);
        }
        return s.toString();
    }

    /**
     * @param args
     */
    public static void main(String[] args) {


//               0——————6
//              /| \    |         7———8
//             / |  \   |         |
//            /  1   2  |         |
//           /          |         9 —————10
//          5-----------4        |  \
//           \         /         |   \
//            \       /          11——12
//             \     /
//              \   /
//                3

        Graph graph = new Graph(13);
        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(0, 5);
        graph.addEdge(0, 6);
        graph.addEdge(3, 4);
        graph.addEdge(3, 5);
        graph.addEdge(4, 5);
        graph.addEdge(4, 6);
        graph.addEdge(7, 8);
        graph.addEdge(7, 9);
        graph.addEdge(9, 10);
        graph.addEdge(9, 11);
        graph.addEdge(9, 12);
        graph.addEdge(11, 12);

        System.out.println(graph);
    }

}

2 无向图的深度优先搜索



/**
 * 该类可以用于无向图的深度优先搜索
 */
public class DFS {

     private boolean[] visited;    //visited[v] = true表示顶点v被dfs方法访问过了,也可以表示存在s~v的路径
     private int count;    

     /**
      * 构造
      * @param G
      * @param s
      */
     public DFS(Graph G, int s) {
         visited = new boolean[G.V()];
         dfs(G, s);
        }

     /**
      * 递归实现无向图的深度优先搜索
      * @param G
      * @param v
      */
     private void dfs(Graph G, int v) {
            count++;
            visited[v] = true;
            for (int w : G.adj(v)) {
                if (!visited[w]) {
                    dfs(G, w);
                }
            }
        }

     /**
      * 用于判断顶点v是否被访问过了,或者用于判断起始顶点s和顶点v之间是否连通
      * @param v
      * @return
      */
     public boolean visited(int v) {
            return visited[v];
        }

     /**
      * 返回与起始顶点相连通的顶点数
      * @return
      */
     public int count() {
            return count;
        }


    //         0——————6
    //        /| \    |         7———8
    //       / |  \   |         |
    //      /  1   2  |         |
    //     /          |         9 —————10
    //    5-----------4        |  \
    //     \         /         |   \
    //      \       /          11——12
    //       \     /
    //        \   /
    //          3
     public static void main(String[] args) {

            Graph graph = new Graph(13);
            graph.addEdge(0, 1);
            graph.addEdge(0, 2);
            graph.addEdge(0, 5);
            graph.addEdge(0, 6);
            graph.addEdge(3, 4);
            graph.addEdge(3, 5);
            graph.addEdge(4, 5);
            graph.addEdge(4, 6);
            graph.addEdge(7, 8);
            graph.addEdge(7, 9);
            graph.addEdge(9, 10);
            graph.addEdge(9, 11);
            graph.addEdge(9, 12);
            graph.addEdge(11, 12);

            DFS ds = new DFS(graph, 0);
            for (int v = 0; v < graph.V(); v++) {
                if (ds.visited(v)) {
                    System.out.print(v + " ");
                }
            }

            System.out.println();

            if (ds.count() != graph.V()){
                System.out.print("该无向图不是联通图");
            } 
            else {
                System.out.print("该无向图是联通图");
            }                         
        }

    }

3 使用深度优先搜索查找图的路径



/**
 * 该类用于计算无向图中从起始顶点s到某个特定顶点之间是存在路径使其相连
 *
 */
public class DFPaths {

    private boolean[] visited;    // marked[v] = true 表示存在s-v的路径
    private int[] edgeTo;         // edgeTo[v] = 从s-v的路径上的最后一个顶点
    private final int s;          //起始顶点 或源


    public DFPaths(Graph G, int s) {
        this.s = s;
        edgeTo = new int[G.V()];
        visited = new boolean[G.V()];
        dfs(G, s);
    }

    private void dfs(Graph G, int v) {
        visited[v] = true;
        for (int w : G.adj(v)) {
            if (!visited[w]) {
                edgeTo[w] = v;
                dfs(G, w);
            }
        }
    }

    /**
     * 用于判断s到v之间是否存在一条路径使其相连
     * @param v
     * @return
     */
    public boolean hasPathTo(int v) {
        return visited[v];
    }

    /**
     * 返回s到v之间的路径,不存在这样的路径则返回null
     * @param v
     * @return
     */
    public Iterable<Integer> pathTo(int v) {
        if (!hasPathTo(v)) {
            return null;
        }
        Stack<Integer> path = new Stack<Integer>();
        for (int x = v; x != s; x = edgeTo[x]) {
            path.push(x);  
        }
        path.push(s);
        return path;
    }

    //         0——————6
    //        /| \    |         7———8
    //       / |  \   |         |
    //      /  1   2  |         |
    //     /          |         9 —————10
    //    5-----------4        |  \
    //     \         /         |   \
    //      \       /          11——12
    //       \     /
    //        \   /
    //          3
    public static void main(String[] args) {
        Graph graph = new Graph(13);
        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(0, 5);
        graph.addEdge(0, 6);
        graph.addEdge(3, 4);
        graph.addEdge(3, 5);
        graph.addEdge(4, 5);
        graph.addEdge(4, 6);
        graph.addEdge(7, 8);
        graph.addEdge(7, 9);
        graph.addEdge(9, 10);
        graph.addEdge(9, 11);
        graph.addEdge(9, 12);
        graph.addEdge(11, 12);
        int s = 0;
        DFPaths paths = new DFPaths(graph, s);

        for (int v = 0; v < graph.V(); v++) {
            if (paths.hasPathTo(v)) {
                System.out.printf("%d -> %d:  ", s, v);
                for (int x : paths.pathTo(v)) {
                    if (x == s) {
                        System.out.print(x);
                    } else {
                        System.out.print("-" + x);
                    }
                }
                System.out.println();
            }

            else {
                System.out.printf("顶点%d与顶点 %d之间不连通\n", s, v);
            }

        }
    }
}

4 使用广度优先搜索查找图的路径(最短路径)



/**
 *该类用于计算无向图中从构造函数得到的起点s到与其他所有顶点的最短路径。由于是无向图,边不带权重,所以实际计算出来的
 *最短路径(从起点s到顶点v的顶点总数越小,路径越短)与添加边到图中的顺序是有一定关系的。或者说所得到的最短路径不唯一。
 *比如本类main方法所示的无向图中,路径"4-> 6-> 0"与路径 "4-> 5-> 0"都是0到4的最短路径,但计算只是给出了
 *前一条路径。不过本算法的意思在于,找不到一条0到4的路径比上述提到的两条路径还短了。
 */
public class BFPaths {

     private boolean[] visited;  
     private int[] edgeTo;      
     private final int s;

     /**
      * 构造
      * @param G
      * @param s
      */
     public BFPaths(Graph G, int s) {
            visited = new boolean[G.V()];
            edgeTo = new int[G.V()];
            this.s = s;
            bfs(G, s);
        }

     /**
      * 广度优先搜索计算最短路径
      * @param G
      * @param s
      */
     private void bfs(Graph G, int s) {
         Queue<Integer> queue = new LinkedList<Integer>();
         visited[s] = true;
         queue.add(s);
         while(!queue.isEmpty()) {
             int v = queue.remove();
             for(int w : G.adj(v)) {
                 if(!visited[w]) {
                     edgeTo[w] = v;
                     visited[w] = true;
                     queue.add(w);
                 }
             }
         }
     }

     /**
      * @param v
      * @return
      */
     public boolean hasPathTo(int v) {
            return visited[v];
        }

       /**
         * 返回s到v之间的路径,不存在这样的路径则返回null
         * @param v
         * @return
         */
     public Iterable<Integer> pathTo(int v) {
            if (!hasPathTo(v)) {
                return null;
            }
            Stack<Integer> path = new Stack<Integer>();
            for (int x = v; x != s; x = edgeTo[x]) {
                path.push(x);  
            }
            path.push(s);
            return path;
        }


        //         0——————6
        //        /| \    |         7———8
        //       / |  \   |         |
        //      /  1   2  |         |
        //     /          |         9 —————10
        //    5-----------4        |  \
        //     \         /         |   \
        //      \       /          11——12
        //       \     /
        //        \   /
        //          3
     public static void main(String[] args) {

         Graph graph = new Graph(13);
            graph.addEdge(0, 1);
            graph.addEdge(0, 2);
            graph.addEdge(0, 5);
            graph.addEdge(0, 6);
            graph.addEdge(3, 4);
            graph.addEdge(3, 5);
            graph.addEdge(4, 5);
            graph.addEdge(4, 6);
            graph.addEdge(7, 8);
            graph.addEdge(7, 9);
            graph.addEdge(9, 10);
            graph.addEdge(9, 11);
            graph.addEdge(9, 12);
            graph.addEdge(11, 12);
            int s = 0;
            BFPaths paths = new BFPaths(graph, s);

            for (int v = 0; v < graph.V(); v++) {
                if (paths.hasPathTo(v)) {
                    System.out.printf("%d -> %d:  ", v, s);
                    for (int x : paths.pathTo(v)) {
                        if (x == v) {
                            System.out.print(x);
                        } else {
                            System.out.print("-> " + x);
                        }
                    }
                    System.out.println();
                }

                else {
                    System.out.printf("顶点%d与顶点 %d之间不连通\n", v, s);
                }

            }


     }

}

5 利用深度优先搜索计算图的连通分量

“`java

/**
*该类用于计算无向图中的连通分量, CC也即Connected Component的缩写
*/
public class CC {

 private boolean[] visited;   //visited[v]= true则表示顶点v被访问过了
    private int[] id;         // id[v]的值表示包含顶点v的那个连通分量的id
    private int[] size;       // size[id]表示第id个连通分量所包含的顶点数
    private int count;        // 连通分量总数

    public CC(Graph G) {
        visited = new boolean[G.V()];
        id = new int[G.V()];
        size = new int[G.V()];
        for (int v = 0; v < G.V(); v++) {
            if (!visited[v]) {
                dfs(G, v);
                count++;
            }
        }
    }

    private void dfs(Graph G, int v) {
        visited[v] = true;
        id[v] = count;
        size[count]++;
        for (int w : G.adj(v)) {
            if (!visited[w]) {
                dfs(G, w);
            }
        }
    }

    /**
     * 返回标识包含顶点v的那个连通分量的id
     * @param v
     * @return
     */
    public int id(int v) {
        return id[v];
    }
    /**
     * 返回顶点v所在的那个连通分量的大小
     * @param v
     * @return
     */
    public int size(int v) {
        return size[id[v]];
    }

    /**
     * 返回无向图中的连通分量总数
     * @return
     */
    public int count() {
        return count;
    }

    /**
     * 返回顶点v和顶点w是否位于同一个连通分量
     * @param v
     * @param w
     * @return
     */
    public boolean connected(int v, int w) {
        return id(v) == id(w);
    }

    public void printConnection(int v, int w) {
        if(id(v) == id(w)) {
            System.out.println("顶点" + v + "与顶点" + w + "连通");
        } else {
            System.out.println("顶点" + v + "与顶点" + w + "不连通");
        }
    }

    //         0---------6
    //        /| \       |                        7———8
    //       / |  \      |                        |
    //      /  1   2     |                        |
    //     /             |                       9 —————10
    //    5----------4                           |  \
    //     \         /                           |   \
    //      \       /                           11——12
    //       \     /
    //        \   /
    //          3
 public static void main(String[] args) {

     Graph graph = new Graph(13);
        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(0, 5);
        graph.addEdge(0, 6);
        graph.addEdge(3, 4);
        graph.addEdge(3, 5);
        graph.addEdge(4, 5);
        graph.addEdge(4, 6);
        graph.addEdge(7, 8);
        graph.addEdge(7, 9);
        graph.addEdge(9, 10);
        graph.addEdge(9, 11);
        graph.addEdge(9, 12);
        graph.addEdge(11, 12);
        int s = 0;
        CC cc = new CC(graph);

        System.out.println("该无向图的连通分量总数是: " + cc.count());
        cc.printConnection(0, 3);
        cc.printConnection(12, 3);
 }

}
“`

6 利用深度优先搜索判断图中是否含有环



/**
 * 该类用于计算一个无向图是否包含了环,如果包含的话,找出这个环
 * 
 * @author lhever
 *
 */
public class Cycle {
    private boolean[] visited;
    private int[] edgeTo;
    private Stack<Integer> cycle;

    /**
     * 构造
     * @param G
     */
    public Cycle(Graph G) {
        if (hasSelfLoop(G)) {
            return;
        }
        if (hasParallelEdges(G)) {
            return;
        }
        visited = new boolean[G.V()];
        edgeTo = new int[G.V()];
        for (int v = 0; v < G.V(); v++) {
            if (!visited[v]) {
                dfs(G, -1, v);
            }
        }
    }

    /**
     * 计算无向图中是否含有自环
     * 
     * @param G
     * @return
     */
    private boolean hasSelfLoop(Graph G) {
        for (int v = 0; v < G.V(); v++) {
            for (int w : G.adj(v)) {
                if (v == w) {
                    cycle = new Stack<Integer>();
                    cycle.push(v);
                    cycle.push(v);
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 计算无向图是否含有平行边
     * 
     * @param G
     * @return
     */
    private boolean hasParallelEdges(Graph G) {
        visited = new boolean[G.V()];

        for (int v = 0; v < G.V(); v++) {

            for (int w : G.adj(v)) {
                if (visited[w]) {
                    cycle = new Stack<Integer>();
                    cycle.push(v);
                    cycle.push(w);
                    cycle.push(v);
                    return true;
                }
                visited[w] = true;
            }
            // reset so marked[v] = false for all v
            for (int w : G.adj(v)) {
                visited[w] = false;
            }
        }
        return false;
    }

    /**
     * 如果含有环的话返回true
     * @return
     */
    public boolean hasCycle() {
        return cycle != null;
    }

    /**
     * 返回环
     * 
     * @return
     */
    public Iterable<Integer> cycle() {
        return cycle;
    }

    private void dfs(Graph G, int u, int v) {
        visited[v] = true;
        for (int w : G.adj(v)) {

            /**
             * 只要找到一个环就不在计算
             */
            if (cycle != null)
                return;

            if (!visited[w]) {
                edgeTo[w] = v;
                dfs(G, v, w);
            } else if (w != u) {
                cycle = new Stack<Integer>();
                for (int x = v; x != w; x = edgeTo[x]) {
                    cycle.push(x);
                }
                cycle.push(w);
                cycle.push(v);
            }
        }
    }

    //         0------6
    //        /| \    |         7———8
    //       / |  \   |         |
    //      /  1   2  |         |
    //     /          |         9 —————10
    //    5-----------4        |  \
    //     \         /         |   \
    //      \       /          11——12
    //       \     /
    //        \   /
    //          3
    public static void main(String[] args) {
        Graph graph = new Graph(13);
        graph.addEdge(0, 1);
        graph.addEdge(0, 2);
        graph.addEdge(0, 5);
        graph.addEdge(0, 6);
        graph.addEdge(3, 4);
        graph.addEdge(3, 5);
        graph.addEdge(4, 5);
        graph.addEdge(4, 6);
        graph.addEdge(7, 8);
        graph.addEdge(7, 9);
        graph.addEdge(9, 10);
        graph.addEdge(9, 11);
        graph.addEdge(9, 12);
        graph.addEdge(11, 12);

        Cycle finder = new Cycle(graph);
        if (finder.hasCycle()) {
            System.out.println("该无向图至少含有一个环,比如: ");
            for (int v : finder.cycle()) {
                System.out.print(v + " ");
            }
            System.out.println();
        } else {
            System.out.println("该无向图中无环");
        }
    }

}

7 将字符串表示节点名的无向图转换为上述用数字表示节点名称的无向图


/**
 * 许多实际问题可以抽象为无向图进行解决,但往往实际问题抽象为图后,其顶点名一般是字符串,该类用于将字符串
 * 类型的顶点名与数字命名的顶点进行关联,以便于我们用图的一般方法进行解决问题。
 */
public class SymbolGraph {

    private Map<String, Integer> st; // 根据字符串名称找到索引
    private String[] keys; // 根据索引找到对应名称
    private Graph G; // 无向图

    /**
     * List<List<String>>中的每一个List<String>,List<String>.get(0)表示某一个顶点,其余元素都表示与
     * 该顶点直接通过一条边相连的临接顶点。
     * @param lists
     */
    public SymbolGraph(List<List<String>> lists) {
        st = new HashMap<String, Integer>();

        for (List<String> list : lists) {
            for (String key : list) {
                if (!st.containsKey(key)) {
                    st.put(key, st.size());
                }
            }

        }

        System.out.println("将字符串类型的顶点名与数字类型的顶点名建立关联完毕");

        // 建立反向索引,以便根据顶点名可以获取对应的数字顶点名
        keys = new String[st.size()];
        for (String name : st.keySet()) {
            keys[st.get(name)] = name;
        }

        G = new Graph(st.size());
        for (List<String> list : lists) {
            int v = st.get(list.get(0));
            for (int i = 1; i < list.size(); i++) {
                int w = st.get(list.get(i));
                G.addEdge(v, w);
            }
        }

    }

    public boolean contains(String s) {
        return st.containsKey(s);
    }

    public int index(String s) {
        return st.get(s);
    }

    public String name(int v) {
        return keys[v];
    }

    public Graph G() {
        return G;
    }


            //      (A) 0---------1(B)
            //         /| \       |         
            //        / |  \      |         
            //       /  3   2(C)  |        
            //      /  (D)        |        
            //  (E) 4-------------5(F)        
            //       \         /         
            //        \       /         
            //         \     /
            //          \   /
            //          6(G)

    public static void main(String... args) {


        List<List<String>> lists = new ArrayList<List<String>>();
        List<String> li01 = new ArrayList<String>();
        li01.add("A");
        li01.add("B");
        li01.add("C");
        li01.add("D");
        li01.add("E");

        List<String> li02 = new ArrayList<String>();
        li02.add("B");
        li02.add("F");

        List<String> li03 = new ArrayList<String>();
        li03.add("E");
        li03.add("F");
        li03.add("G");

        List<String> li04 = new ArrayList<String>();
        li04.add("F");
        li04.add("G");

        lists.add(li01);
        lists.add(li02);
        lists.add(li03);
        lists.add(li04);

        SymbolGraph sg = new SymbolGraph(lists);
        Graph graph = sg.G();
        System.out.println(graph);



    }

}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值