目录

图(数据结构)

graph

在计算机科学中,图(英语:graph)是一种抽象数据类型,用于实现数学中图论的无向图和有向图的概念。

图的数据结构包含一个有限(可能是可变的)的集合作为节点集合,以及一个无序对(对应无向图)或有序对(对应有向图)的集合作为边(有向图中也称作弧)的集合。节点可以是图结构的一部分,也可以是用整数下标或引用表示的外部实体。

图的数据结构还可能包含和每条边相关联的数值(edge value),例如一个标号或一个数值(即权重,weight;表示花费、容量、长度等)。

基本概念

图是由顶点和边组成的:(可以无边,但至少包含一个顶点)

  • 一组顶点:通常用 V(vertex) 表示顶点集合
  • 一组边:通常用 E(edge) 表示边的集合

图可以分为有向图和无向图,在图中:

  • (v, w) 表示无向边,即 v 和 w 是互通的
  • <v, w> 表示有向边,该边始于 v,终于 w

图可以分为有权图和无权图:

  • 有权图:每条边具有一定的权重(weight),通常是一个数字
  • 无权图:每条边均没有权重,也可以理解为权为 1

图又可以分为连通图和非连通图:

  • 连通图:所有的点都有路径相连
  • 非连通图:存在某两个点没有路径相连

图中的顶点有度的概念:

  • 度(Degree):所有与它连接点的个数之和
  • 入度(Indegree):存在于有向图中,所有接入该点的边数之和
  • 出度(Outdegree):存在于有向图中,所有接出该点的边数之和

有向网,存在2个节点不同方向权不同的有向图。

操作

图数据结构G支持的基本操作通常包括:

  • adjacent(G, x, y):查看是否存在从节点x到y的边;
  • neighbors(G, x):列出所有从x出发的边的另一个顶点y;
  • add_vertex(G, x):如果不存在,将节点x添加进图;
  • remove_vertex(G, x):如果存在,从图中移除节点x;
  • add_edge(G, x, y):如果不存在,添加一条从节点x到y的边;
  • remove_edge(G, x, y):如果存在,从图中移除从节点x到y的边;
  • get_vertex_value(G, x):返回节点x上的值;
  • set_vertex_value(G, x, v):将节点x上的值赋为v。

如果该数据结构支持和边关联的数值,则通常也支持下列操作:

  • get_edge_value(G, x, y):返回边(x, y)上的值;
  • set_edge_value(G, x, y, v):将边(x, y)上的值赋为v。

图的常见数据结构

邻接表 adjacency list

节点存储为记录或对象,且为每个节点创建一个列表。这些列表可以按节点存储其余的信息;例如,若每条边也是一个对象,则将边存储到边起点的列表上,并将边的终点存储在边这个的对象本身。

无权图

1
2
3
4
// 数组表示顶点
char[] vexs;
// graph[i][j] 表示节点i有连接的点集合
int[][] graph;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class Node {
    int n;
    String name;
    boolean visited;
}
// 
private HashMap<Node, LinkedList<Node>> adjacencyMap;
private boolean directed;

public void depthFirstSearch(Node node) {
    node.visit();
    System.out.print(node.name + " ");

    LinkedList<Node> allNeighbors = adjacencyMap.get(node);
    if (allNeighbors == null)
        return;

    for (Node neighbor : allNeighbors) {
        if (!neighbor.isVisited())
            depthFirstSearch(neighbor);
    }
}

public class ListUDG {
    private static int INF = Integer.MAX_VALUE;

    // 邻接表中表对应的链表的顶点
    private class ENode {
        int ivex;       // 该边所指向的顶点的位置
        int weight;     // 该边的权
        ENode nextEdge; // 指向下一条弧的指针
    }

    // 邻接表中表的顶点
    private class VNode {
        char data;          // 顶点信息
        ENode firstEdge;    // 指向第一条依附该顶点的弧
    };

    private int mEdgNum;    // 边的数量
    private VNode[] mVexs;  // 顶点数组

    /*
     * 深度优先搜索遍历图的递归实现
     */
    private void DFS(int i, boolean[] visited) {
        ENode node;

        visited[i] = true;
        System.out.printf("%c ", mVexs[i].data);
        node = mVexs[i].firstEdge;
        while (node != null) {
            if (!visited[node.ivex])
                DFS(node.ivex, visited);
            node = node.nextEdge;
        }
    }

    /*
     * 深度优先搜索遍历图
     */
    public void DFS() {
        boolean[] visited = new boolean[mVexs.length];       // 顶点访问标记

        // 初始化所有顶点都没有被访问
        for (int i = 0; i < mVexs.length; i++)
            visited[i] = false;

        System.out.printf("DFS: ");
        for (int i = 0; i < mVexs.length; i++) {
            if (!visited[i])
                DFS(i, visited);
        }
        System.out.printf("\n");
    }
}

邻接矩阵 adjacency matrix

一个二维矩阵,其中行与列分别表示边的起点和终点。顶点上的值存储在外部。矩阵中可以存储边的值。

1
2
3
4
5
6
7
public class MatrixUDG {

    private int edgeNum;        // 边的数量
    private char[] vexs;       // 顶点集合
    private int[][] matrix;    // 邻接矩阵
    private static final int INF = Integer.MAX_VALUE;   // 最大值
}

关联矩阵(英语:incidence matrix)

一个二维矩阵,行表示顶点,列表示边。矩阵中的数值用于标识顶点和边的关系(是起点、是终点、不在这条边上等)。

邻接表在稀疏图(英语:sparse graph)上比较有效率。邻接矩阵则常在图比较稠密的时候使用,判断标准一般为边的数量$|E|$接近于节点的数量的平方$|V|^2$;邻接矩阵也在查找两节点邻接情况较为频繁时使用。

其它表示和存储图的数据结构还包括链式前向星、十字链表、邻接多重表(英语:adjacency multilist)等。

图的遍历

图的遍历问题分为四类:

  • 遍历完所有的边而不能有重复,即所谓“欧拉路径问题”(又名一笔画问题);
  • 遍历完所有的顶点而没有重复,即所谓“哈密顿路径问题”。
  • 遍历完所有的边而可以有重复,即所谓“邮递员问题”;
  • 遍历完所有的顶点而可以重复,即所谓“旅行推销员问题”。

对于第一和第三类问题已经得到了完满的解决,而第二和第四类问题则只得到了部分解决。

第一类问题就是研究所谓的欧拉图的性质,而第二类问题则是研究所谓的哈密顿图的性质。

算法

图的遍历方法有

  • 深度优先搜索法
  • 广度(宽度)优先搜索法。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int graph[][] = {{1,2,3},{4,5,6}}
int[][] graph = new int[2][3];
// 第二维的长度可以动态申请
int [][] graph3 = new int[5][];
for (int i = 0; i< graph3.length; i++){
  graph3[i] = new int[i+1];
  for (int j=0; j< graph3[i].length; j++){
    graph3[i][j] = i+j;
  }
}

附件