数据结构-图(定义、存储结构、遍历、求最短路径、拓扑排序)

图是很有用的数据结构,在解决最短路径、工程规划时有很重要的作用。

一、图的定义

1.1图的定义

数据结构-图(定义、存储结构、遍历、求最短路径、拓扑排序)_第1张图片
image.png

图是由顶点的有穷非空集合和顶点指点的边的集合组成,通常表示尾:G(V,E),其中,G表示一个图,V是图G中的顶点的集合,E是图G中边的集合。
下面三个数据结构都可以称为图


数据结构-图(定义、存储结构、遍历、求最短路径、拓扑排序)_第2张图片
image.png

1.2有向图、无向图和网

无向图:图中所有的边都没有方向。
完全无向图:图中任意两个顶点之间都存在边。含有n个顶点的完全无向图的边的总数是n(n-1)/2。
有向图:图中所有边都有方向。假设顶点A到顶点B之前存在有向边,从A指向B,则边可以用有序偶来表示,A为弧尾,B为弧头。有向边也称为弧。
完全有向图:图中任意两点都有方向相反的两条有向边。含有n个顶点的完全有向图的边的总是是n
(n-1)。
网:有些图的边具有与它相关的数字,这种与图的边相关的数字叫做权。这些权可以表示从一个顶点到另外一个顶点的距离或耗费。这种带权的图通常称为网。

度、入度、出度

度:对于无向图来说,一个顶点的度就是与该顶点相关联的边的数量。有n个顶点的无向图的度等于边数的两倍。
入度:对于有向图来说,一个顶点的入度是弧头是该顶点的弧的数量。
出度:对于有向图来说,一个顶点的出度是弧尾是该顶点的弧的数量。

1.3路径、环

路径:图G=(V,E)中从顶点A到顶点D的路径是一个顶点序列。路径的长度是路径上边的数量。
环:第一个顶点到最后一个顶点相同的路径称为回路或环。

1.4连通图

在无向图中,如果顶点A到顶点B有路径,则称A和B是联通的。如果图中任意两个顶点都是联通的,则该图称为连通图。
在有向图中,对于每一对顶点A,B,从A到B和从B到A都存在路径,则称该图是强联通图。

二、图的存储结构

我们可以在纸上把图画出来,但是图在程序中应该怎样表示?

2.1 邻接矩阵

邻接矩阵存储方法是用一个一维数组和一个二维数组来表示图。一维数组存储图中顶点的信息,二维数组存储图的边或弧的信息。


数据结构-图(定义、存储结构、遍历、求最短路径、拓扑排序)_第3张图片
无向图的邻接矩阵
数据结构-图(定义、存储结构、遍历、求最短路径、拓扑排序)_第4张图片
有向图的邻接矩阵

上图中,假如我们要找以弧尾为顶点V0的弧,就看二维数组的第一行(V0 - V4)。加入我们要以弧头为V0的弧,就看第一列(V1 - V0,V2 - V0)。

2.2 邻接表

与HashMap的存储结构有些类似。用一个一维数组存储结点,每个结点的邻接点形成一个链表。


数据结构-图(定义、存储结构、遍历、求最短路径、拓扑排序)_第5张图片
无向图的邻接表

数据结构-图(定义、存储结构、遍历、求最短路径、拓扑排序)_第6张图片
有向图的邻接表

三、图的遍历

有时候我们需要从图的某一顶点出发,以某种方式遍历图的每个顶点,并且每个顶点只会被访问一次。

3.1深度优先遍历

以深度优先的方法来遍历图。主要用到深度优先搜索和回溯。
以邻接矩阵表示的图的深度优先搜索的伪代码

/**
 * 邻接矩阵表示的图的深度优先搜索
 * @author TinyMonster
 *
 */
public class DFSAdjMat {
    boolean[] visited;
    public void DFSTraverse(int[][] graph) {
        visited = new boolean[graph.length];  //初始化访问数组,记录每个结点的访问情况
        for(int i=0;i

以邻接表表示的图的深度优先搜索的伪代码

/**
 * 邻接表表示的图的深度优先搜索
 * @author TinyMonster
 *
 */
public class DFSAdjList {
    boolean[] visited;
    public void DFSAdjListTraverse(Node[] AdjList) {
        visited = new boolean[AdjList.length];   //初始化访问数组,记录每个结点的访问情况
        for(int i = 0;i

3.2 图的广度优先搜索

图的广度优先搜索使用了队列这一数据结构
邻接矩阵表示的图的深度优先搜索

/**
 * 邻接矩阵表示的图的广度优先搜索
 * @author TinyMonster
 *
 */
public class BFSAdjMat {
    boolean[] visited;
    public void BFSTraverse(int[][] graph) {
        visited = new boolean[graph.length];   //初始化访问数组,记录每个结点的访问情况
        for(int i=0;i queue = new LinkedList();   //使用队列来进行广度优先搜索
        queue.offer(i);
        while(!queue.isEmpty()) {
            int cur = queue.poll();
            visited[cur] = true;
            System.out.println(cur);
            for(int j=1;j

邻接表表示的图的广度优先搜索

/**
 * 邻接表表示的图的广度优先搜索
 * @author TinyMonster
 *
 */
public class BFSAdjList {
    boolean[] visited;
    public void BFSTraverse(Node[] AdjList) {
        visited = new boolean[AdjList.length];   //初始化访问数组,记录每个结点的访问情况
        for(int i=0;i queue = new LinkedList();   //使用队列来进行广度优先搜索
        queue.offer(i);
        while(!queue.isEmpty()) {
            int j = queue.poll();
            Node cur = AdjList[j];
            visited[j] = true;                              //打印数据
            System.out.println(j);
            Node next = cur.next;
            while(next!=null) {                             //遍历该结点的所有邻接结点,压入队列中
                queue.offer(next.data);
                next = next.next;
            }
        }
    }
    
    private class Node{
        int data;
        Node next;
    }
}

四、图的最小生成树

加入你是电信的实施工程师,需要为一个镇的九个村庄假设通信网络做设计,村庄位置大致如下图所示。其中每个结点表示一个村庄,边上的数据表示路径长短。我们要设计一个最短的路径。这就需要最小生成树的知识。
我们知道一个图的生成树是一个极小的连接子图,它含有图中的全部顶点,但是只有足以构成一颗树的n-1条边。
我们把构造连通网的最小代价生成树称为最小生成树。


数据结构-图(定义、存储结构、遍历、求最短路径、拓扑排序)_第7张图片

求一个图的最小生成树可以用以下方法。

普里姆算法

该算法主要一个数组来保存每个结点在路径中的前一个结点,使用另外一个数组来保存到达每个结点的最短距离。通过广度优先搜索,不断扩大搜索范围,不断更新两个数组,最终完成最小生成树的搜索。
一个图和它的邻接矩阵如下图所示。邻接矩阵中,∞表示两个结点之间没有边。


数据结构-图(定义、存储结构、遍历、求最短路径、拓扑排序)_第8张图片
image.png

我们定义两个数组adjvex和lowcost。其中adjvex表示每个结点在最小生成树中路径的上一个结点。lowcost表示每个结点在最小生成树中上一个结点到达该结点的最短路径。


数据结构-图(定义、存储结构、遍历、求最短路径、拓扑排序)_第9张图片
image.png

伪代码
/**
 * 图的最小生成树
 * @author TinyMonster
 *
 */
public class MiniSpanTreePrim {
    public void MiniSpanTreePrim(int[][] graph) {
        int min;
        int[] adjvex = new int[graph.length];
        int[] lowcost = new int[graph.length];
        for(int i=1;i"+nextNode);  //打印路径
            for(int j=1;j

五、图中两个结点的最短路径

生活中我们经常会需要找到从一个点到另外一个点的最短距离,这时候可以用图的最短路径来解决。

5.1迪杰斯特拉算法

与普里姆算法类似,通过不断搜索周围结点,找到最短路径。

public class ShortestPath {
    public void ShortestPath(int[][] graph,int v0,int[] Patharc,int[] ShortPathValue) {
        boolean[] finish = new boolean[graph.length];
        int min = Integer.MIN_VALUE;
        int next = v0;
        for(int i=0;i

六、拓扑排序

在生活中,完成一个事情可以分为若干个步骤,但是这些步骤之前有依赖关系。比如要拍摄一部电影,可以分为完成据本,确定演员,进行拍摄。首先要在完成据本和确定演员之后才能进行拍摄。
在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们称为AOV网。
拓扑排序的定义:设G=(V,E)是一个具有n个顶点的有序列向图,V中顶点序列v1,v2...vn,满足若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必定在顶点vj之前。则我们称这样的顶点序列为一个拓扑排序。
一个AOV网的拓扑排序可能不止一条。所谓拓扑排序,其实就是对一个有向图构造拓扑排序的过程。构造时会有两个结果。如果拓扑序列包含了AOV网的所有结点,那么这个AOV网中不存在环,否则,这个AOV网存在环。

6.1拓扑排序算法

对AOV网进行拓扑排序的基本思路是:从AOV网中选择一个入度为0的顶点输出,然后删去此顶点,并删除以此顶点为尾的弧,继续重复此操作,知道输出全部顶点或者AOV网中不存在入度尾0的顶点为止。
在拓扑排序中需要删除顶点,我们用邻接表的存储结构更加方便。

/**
 * AOV网的拓扑排序
 * @author TinyMonster
 *
 */
public class TopoSort {
    public boolean TopoSort(EdgeNode[] head) {
        Stack stack = new Stack();  //存储入度等于0的结点
        EdgeNode cur = null; //当前顶点结点
        Node next = null;   //当前边结点
        int count = 0; //拓扑序列中结点数量
        for(int i=0;i0) {
                    head[next.adjvex].in --;
                    if(head[next.adjvex].in == 0) {
                        stack.push(head[next.adjvex]);
                    }
                }
            }
        }
        return count == head.length;     //如果拓扑序列元素数量等于AOV网结点总数,该AOV网可以进行拓扑排序,AOV网中没有环
    }
    
    private class EdgeNode{   //邻接表的顶点
        int in;//入度
        int data;//数据
        Node next;
    }
    
    private class Node{    //连接表的边结点
        int adjvex; //该点对应的顶点的下标
        Node next;
    }
}

你可能感兴趣的:(数据结构-图(定义、存储结构、遍历、求最短路径、拓扑排序))