数据结构之图(Graph)

图的概述

什么是图

map_overview
如图就是一张图,其实之前介绍的树、链表都可以看做一个简单的图。

图描述的是一种多对多的关系,由顶点(vertex)和连接顶点间的边(edge)组成。每个顶点可以有零个或多个前驱、也可以有零个或多个后继。
注:图可以没有边,但至少有一个顶点。

因此图可以表示成G=(V,E)。V即顶点的集合、E为边的集合。

G = (V,E)。 其中,VE={<v,w>|v,w\(\in\)V且P(v,w)}。
<v,w>是从v到w的一条边,有方向的,与<w,v>不一样的。P(v,w)是定义在<v,w>上的权值信息(即赋予的有意义的数值或信息)。

图的定义及术语

几种常见的图
  • 无向图:图中的边没有方向。
  • 有向图:图中的边存在方向。
  • 完全图:图中任意两个顶点都有边连接。
  • 无权图:边上没有赋值,即不带权值。
  • 有权图:边上附有权值。
顶点与边的数目关系

假使图中有n个顶点,e条边。
若有e=n(n-1)/2条无向边,则是无向完全图
若有e=n(n-1)条有向边,则是有向完全图
若e<nlogn则称作稀疏图,否则为稠密图

图的一些概念
  • 邻接点:若顶点v和w之间存在边,则v、w互为邻接点。
  • 度:与顶点v关联的边的数目,即是度。
  • 出度/入度:对于有向图,<v,w>是v指向w的一条边,那么该边是v的出边,w的入边。
    出度/入度即是顶点的出边/入边数目。
    所以顶点的度=出度+入度。
  • 路径长度:从v到w之间存在一条路径,路径上边的数目即是路径的长度。
  • 简单路径:即路径中顶点不重复的路径。
  • 连通图:若G(图)中,任意两个顶点之间都有路径连通,则G称为连通图。
  • 强连通图:对于有向图中,任意两点之间都有一条有向路径。则称这个有向图为强连通图。
  • 连通分量:非连通图,其中的极大连通子图称为连通分量。这里的极大是指子图中包含的顶点个数极大。
  • 生成树:假使一个连通图,n个顶点e条边,若n-1条边与n个顶点构成的极小连通子图,则这个极小连通子图称为该连通图的生成树。如下面的连通图,红色部分即以D为根的生成树。
  • 生成森林:非连通图,每个连通分量的生成树 共同构成该非连通图的生成森林。
    map_tree

图的存储

邻接矩阵

如下图,右边的就是左边图的邻接矩阵表示。即记录每个顶点与其他所有点是否存在边,存在则标1,否则为0。
map_array
标志信息也可以根据实际情况表示,如权值等。
无向图该矩阵是对称的,有向图则不一定。

邻接表

如下图所示,每个顶点指向了一个链表,链表里记录了所有存在边的顶点在数组中的位置。
map_array_link

图的遍历

map_search
图的遍历都以这个有向图为例进行说明的。

从图中某个顶点V0出发,访问V0,然后依次以v0相连的顶点为出发点向后访问。依次类推,直到所有与V0顶点相连的所有结点都被访问到。 如果图中仍有未访问的顶点,则该图为非连通图,在图中未访问的顶点选择一个顶点为起点,并重复上述过程,直到访问完图中的所有顶点为止。
下面分别是使用邻接矩阵和邻接表表示的图 实现的深度优先的demo,可以大致看看了解下。
邻接矩阵表示的图
注意,邻接矩阵表示 使用的非递归方法实现,通过栈来实现的

import java.util.ArrayList;
import java.util.Stack;

//深度优先:使用邻接矩阵存储方式
public class DFSArrayTest {
    static ArrayList<Node> nodes = new ArrayList<>();
    static class Node {
        char data;//数据
        boolean visited;//是否访问过标志

        Node(char data) {
            this.data = data;
        }
    }

    //查找某顶点在邻接矩阵中的邻接点
    public ArrayList<Node> findNeighbours(int adjacency_matrix[][], Node x) {
        int nodeIndex = -1;

        ArrayList<Node> neighbours = new ArrayList<>();
        for (int i = 0; i < nodes.size(); i++) {
            if (nodes.get(i).equals(x)) {
                nodeIndex = i;
                break;
            }
        }

        if (nodeIndex != -1) {
            for (int j = 0; j < adjacency_matrix[nodeIndex].length; j++) {
                if (adjacency_matrix[nodeIndex][j] == 1) {
                    neighbours.add(nodes.get(j));
                }
            }
        }
        return neighbours;
    }

    //使用栈实现深度优先
    public void dfsUseStack(int adjacency_matrix[][], Node node) {
        Stack<Node> stack = new Stack<>();
        stack.add(node);
        
        while (!stack.isEmpty()) {
            Node element = stack.pop();
            if (!element.visited) {
                System.out.print(element.data + " ");
                element.visited = true;
            }

            ArrayList<Node> neighbours = findNeighbours(adjacency_matrix, element);
            for (int i = 0; i < neighbours.size(); i++) {
                Node n = neighbours.get(i);
                if (n != null && !n.visited) {
                    stack.add(n);
                }
            }
        }
    }

    public static void clearVisitedFlags() {
        for (int i = 0; i < nodes.size(); i++) {
            nodes.get(i).visited = false;
        }
    }

    public static void main(String arg[]) {
        Node nodeA = new Node('A');
        Node nodeB = new Node('B');
        Node nodeC = new Node('C');
        Node nodeD = new Node('D');
        Node nodeE = new Node('E');
        int adjacency_matrix[][] = { 
                //A  B  C  D  E
                { 0, 1, 0, 0, 1 }, // A
                { 0, 0, 0, 1, 0 }, // B
                { 1, 0, 0, 0, 0 }, // C
                { 0, 0, 1, 0, 0 }, // D
                { 0, 0, 0, 1, 0 }  // E
        };
		
        nodes.add(nodeA);nodes.add(nodeB);nodes.add(nodeC);nodes.add(nodeD);nodes.add(nodeE);
		
        DFSArrayTest dfsArrayTest = new DFSArrayTest();
        System.out.println("DFSArray(以C为开始顶点):");
        dfsArrayTest.dfsUseStack(adjacency_matrix, nodeC);
        System.out.println();
        clearVisitedFlags();
        System.out.println("DFSArray(以A为开始顶点):");
        dfsArrayTest.dfsUseStack(adjacency_matrix, nodeA);
    }
}

结果如下:

DFSArray(以C为开始顶点):
C A E D B 
DFSArray(以A为开始顶点):
A E D C B 

邻接表 表示的图
注意,邻接表表示 使用的是递归方法实现

import java.util.ArrayList;
import java.util.LinkedList;

//深度优先:使用邻接表存储方式
class DFSLinkedTest {

    static class Node {
        char data;//数据
        boolean visited;//是否访问过的标志
        LinkedList<Node> neighbours;//所有邻接点

        Node(char data) {
            this.data = data;
            this.neighbours = new LinkedList<>();
        }
    }

    //深度遍历,node为开始顶点
    private void dfsUtil(Node node) {
        node.visited = true;
        System.out.print(node.data + "  ");
        for (int i = 0; i < node.neighbours.size(); i++) {
            Node neighbourNode = node.neighbours.get(i);
            if (neighbourNode != null && !neighbourNode.visited) {
                dfsUtil(neighbourNode);
            }
        }
    }
	
    //恢复所有顶点标志到未访问
    private void clearVisitedFlags(ArrayList<Node> nodes) {
        for (int i = 0; i < nodes.size(); i++) {
            nodes.get(i).visited = false;
        }
    }
    
    public static void main(String args[]) {
        DFSLinkedTest dfsLinkedTest = new DFSLinkedTest();
        ArrayList<Node> nodes = new ArrayList<>();
        Node nodeA = new Node('A');
        Node nodeB = new Node('B');
        Node nodeC = new Node('C');
        Node nodeD = new Node('D');
        Node nodeE = new Node('E');
        nodeA.neighbours.add(nodeB);
        nodeA.neighbours.add(nodeE);
        nodeB.neighbours.add(nodeD);
        nodeC.neighbours.add(nodeA);
        nodeD.neighbours.add(nodeC);
        nodeE.neighbours.add(nodeD);
        
        nodes.add(nodeA);nodes.add(nodeB);nodes.add(nodeC);nodes.add(nodeD);nodes.add(nodeE);
        dfsLinkedTest.clearVisitedFlags(nodes);
        System.out.println("DFSLinked(以C为开始顶点):");
        dfsLinkedTest.dfsUtil(nodeC);
        System.out.println();
        dfsLinkedTest.clearVisitedFlags(nodes);
        System.out.println("DFSLinked(以A为开始顶点):");
        dfsLinkedTest.dfsUtil(nodeA);
    }
}

结果如下:

DFSLinked(以C为开始顶点):
C  A  B  D  E  
DFSLinked(以A为开始顶点):
A  B  D  C  E  

从图中的某个顶点V0开始,访问V0,然后依次访问V0的所有未访问的邻接点,然后以访问这些顶点的顺序访问它们的邻接点,直到所有顶点在都被访问到。如果图中仍有未访问的顶点,则该图为非连通图,在图中未访问的顶点选择一个顶点为起点,并重复上述过程,直到访问完图中的所有顶点为止。
同样 下面分别是使用邻接矩阵和邻接表表示的图 实现的广度优先的demo,可以大致看看了解下。
邻接矩阵表示的图

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

//广度优先:使用邻接矩阵存储方式
public class BFSArrayTest {

    private Queue<Node> queue;
    static ArrayList<Node> nodes = new ArrayList<Node>();
    
    //顶点定义
    static class Node {
        char data;
        boolean visited;
        
        Node(char data) {
            this.data = data;
        }
    }

    public BFSArrayTest() {
        queue = new LinkedList<Node>();
    }

    //查找某顶点在邻接矩阵中的邻接点
    public ArrayList<Node> findNeighbours(int adjacency_matrix[][], Node x) {
        int nodeIndex = -1;
        
        ArrayList<Node> neighbours = new ArrayList<Node>();
        for (int i = 0; i < nodes.size(); i++) {
            if (nodes.get(i).equals(x)) {
                nodeIndex = i;
                break;
            }
        }
        
        if (nodeIndex != -1) {
            for (int j = 0; j < adjacency_matrix[nodeIndex].length; j++) {
                if (adjacency_matrix[nodeIndex][j] == 1) {
                    neighbours.add(nodes.get(j));
                }
            }
        }
        return neighbours;
    }

    public void bfsUtil(int adjacency_matrix[][], Node node) {
        queue.add(node);
        node.visited = true;
        while (!queue.isEmpty()) {
            Node element = queue.remove();
            System.out.print(element.data + " ");
            ArrayList<Node> neighbours = findNeighbours(adjacency_matrix, element);
            for (int i = 0; i < neighbours.size(); i++) {
                Node n = neighbours.get(i);
                if (n != null && !n.visited) {
                    queue.add(n);
                    n.visited = true;
                }
            }
        }
    }
	
    public void clearVisitedFlags() {
        for (int i = 0; i < nodes.size(); i++) {
            nodes.get(i).visited = false;
        }
    }

    public static void main(String arg[]) {
        Node nodeA = new Node('A');
        Node nodeB = new Node('B');
        Node nodeC = new Node('C');
        Node nodeD = new Node('D');
        Node nodeE = new Node('E');
        int adjacency_matrix[][] = { 
                //A  B  C  D  E
                { 0, 1, 0, 0, 1 }, // A
                { 0, 0, 0, 1, 0 }, // B
                { 1, 0, 0, 0, 0 }, // C
                { 0, 0, 1, 0, 0 }, // D
                { 0, 0, 0, 1, 0 }  // E
        };

        nodes.add(nodeA);nodes.add(nodeB);nodes.add(nodeC);nodes.add(nodeD);nodes.add(nodeE);
		
        BFSArrayTest bfsArrayTest = new BFSArrayTest();
        System.out.println("BFSArray(以C为开始顶点):");
        bfsArrayTest.bfsUtil(adjacency_matrix, nodeC);
        System.out.println();
        bfsArrayTest.clearVisitedFlags();
        System.out.println("BFSArray(以A为开始顶点):");
        bfsArrayTest.bfsUtil(adjacency_matrix, nodeA);
    }
}

结果如下:

BFSArray(以C为开始顶点):
C A B E D 
BFSArray(以A为开始顶点):
A B E D C 

邻接表表示的图

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

//广度优先:使用邻接表存储方式
public class BFSLinkedTest {
    private Queue<Node> queue;
    static ArrayList<Node> nodes = new ArrayList<Node>();

    static class Node {
        char data;
        boolean visited;
        LinkedList<Node> neighbours;
        
        Node(char data) {
            this.data = data;
            this.neighbours = new LinkedList<>();
        }
    }

    public BFSLinkedTest() {
        queue = new LinkedList<Node>();
    }

    public void bfsUtil(Node node) {
        queue.add(node);
        node.visited = true;
        while (!queue.isEmpty()) {
            Node element = queue.remove();
            System.out.print(element.data + "  ");
            for (int i = 0; i < element.neighbours.size(); i++) {
                Node n = element.neighbours.get(i);
                if (n != null && !n.visited) {
                    queue.add(n);
                    n.visited = true;
                }
            }
        }
    }
	
    private void clearVisitedFlags(ArrayList<Node> nodes) {
        for (int i = 0; i < nodes.size(); i++) {
            nodes.get(i).visited = false;
        }
    }

    public static void main(String arg[]) {
        ArrayList<Node> nodes = new ArrayList<>();
        Node nodeA = new Node('A');
        Node nodeB = new Node('B');
        Node nodeC = new Node('C');
        Node nodeD = new Node('D');
        Node nodeE = new Node('E');
        nodeA.neighbours.add(nodeB);
        nodeA.neighbours.add(nodeE);
        nodeB.neighbours.add(nodeD);
        nodeC.neighbours.add(nodeA);
        nodeD.neighbours.add(nodeC);
        nodeE.neighbours.add(nodeD);
		
        BFSLinkedTest bfsLinkedTest = new BFSLinkedTest();
        nodes.add(nodeA);nodes.add(nodeB);nodes.add(nodeC);nodes.add(nodeD);nodes.add(nodeE);
        bfsLinkedTest.clearVisitedFlags(nodes);
        System.out.println("BFSLinked(以C为开始顶点):");
        bfsLinkedTest.bfsUtil(nodeC);
        System.out.println();
        bfsLinkedTest.clearVisitedFlags(nodes);
        System.out.println("BFSLinked(以A为开始顶点):");
        bfsLinkedTest.bfsUtil(nodeA);
    }
}

结果如下:

BFSLinked(以C为开始顶点):
C  A  B  E  D  
BFSLinked(以A为开始顶点):
A  B  E  D  C  
posted @ 2020-06-24 23:02  流浪_归家  阅读(728)  评论(0编辑  收藏  举报