Graph Representation

Graph Interface

Graph是Undirected的(15122定义),也就是说对于vertex A和B,BA和AB是一条edge。我们将graph表示成一个data structure,其中vertex定义为无符号整数。那么一个基本的graph interface可以如下所示:

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
typedef unsigned int vertex;
typedef struct graph_header *graph_t;

graph_t graph_new(unsigned int numvert);
//@ensures \result != NULL;

void graph_free(graph_t G);
//@requires G != NULL;

unsigned int graph_size(graph_t G);
//@requires G != NULL;

bool graph_hasedge(graph_t G, vertex v, vertex w);
//@requires G != NULL;
//@requires v < graph_size(G) && w < graph_size(G);

void graph_addedge(graph_t G, vertex v, vertex w);
//@requires G != NULL;
//@requires v < graph_size(G) && w < graph_size(G);
//@requires v != w && !graph_hasedge(G, v, w);

typedef struct neighbor_header *neighbors_t;

neightbors_t graph_get_neighbors(graph_t G, vertex v);
//@requires G != NULL && v < graph_size(G);
//@ensures \result != NULL;

bool graph_hasmore_neighbors(neighbors_t nbors);
//@requires nbors != NULL;

vertex graph_next_neighbor(neighbors_t nbors);
//@requires nbors != NULL;
//@requires graph_hasmore_neighbors(nbors);

void graph_free_neighbors(neighbors_t nbors);
//@requires nbors != NULL;

我们通过graph_hasmore_neighbors和graph_next_neighbors来检查或者获取某个vertex的相邻vertex。我们可以通过多种数据结构实现上面的interface,一个有e条边的graph可以被链表或者edge的数组来表示。在链表实现中,添加一个edge的时间复杂度是O(1),因为可以直接在链表前面接上。但是检查某一个edge是否存在需要O(e)的复杂度因为要遍历整个graph。获取某个节点的相邻节点同样需要O(e)的复杂度(Worst case)。使用哈希表和自平衡二分树也可以实现graph,但是这里只涉及到使用adjacency matrices和adjacency lists的graph实现。

Adjacency Matrices

我们可以使用一个二维数组储存两个节点之间是否存在edge,这样的表示方法在这里被称为Adjacency Matrices。假设我们有节点B(=1)和节点D(=3),我们检查两个节点之间是否存在edge的方法就是看二维数组的row 1,column 3。在一个不区分方向的图中,数组的右上半边是左下的镜像。因为edge的关系是对称的。因为我们不允许一个node使用edge链接自己,所以这个matrix的对角线是空的。

Adjacency Matrices的实现需要很多空间。对于一个有v个节点的graph我们需要O(v2)O(v^2)的复杂度来分配内存。但是,使用改方法的好处是添加edge和检查edge的操作都是O(1)复杂度。

假如一个graph有0到v(v1)2\frac{v(v-1)}{2}个节点数v,并且多数edge存在,那么总edge数和v2v^2成比例关系,我们说这样的graph是dense的。对于dense graph,O(e)=O(v2)O(e) = O(v^2),因此adjacency matrices是表示其的较好办法。因为存储其的空间并不比linked list多,但是操作更快。

Adjacency Lists

我们将dense graph的对立称作sparse graph。Adjacency lists适合作为sparse graph的实现。在这个实现当中,我们有一个类似于哈希表的一维数组,每一个vertex都在数组中有一个位置,并且数组中的每一个元素包含所有其他和该vertex相连的链表。

这个实现需要O(v + e)的空间来存储有v个vertex和e个edge的graph。也可以被写作O(max(v, e))。添加一个edge需要的时间是constant,但是lookup某一个edge需要的时间是O(min(v, e)),因为min(v-1, e)是每一个adjacency list的最大长度。在adjacency list实现里面寻找一个neighbor的时间是O(1)。

Implementation

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
typedef struct adjlist_node adjlist
struct adjlist_node{
vertex vert;
adjlist *next;
};

typedef struct graph_header graph;
struct graph_header{
unsigned int size;
adjlist **adj;
};

bool is_vertex(graph *G, vertex v){
REQUIRES(G != NULL);
return v < G->size;
}

bool is_in(adjlist *p, vertex v){
while(p != NULL){
if(p->vert == v) return true;
p = p->next;
}
return false;
}

bool is_graph(graph *G){
if(G == NULL) return false;
if(G->adj == NULL) return false;
for(unsigned int i = 0; i < G->size; i++){
if(!is_acyclic(G->adj[i])) return false;
for(adjlist *p = G->adj[i]; p != NULL; p = p->next){
if(p->vert == i || !(is_vertex(G, p->vert))) return false;
if(!is_in(G->adj[p->vert], i)) return false;
if(is_in(p->next, p->vert)) return false;
}
}
return true;
}

graph *graph_new(unsigned int size){
graph *G = xmalloc(sizeof(graph));
G->size = size;
G->adj = xcalloc(size, sizeof(adjlist*));
ENSURES(is_graph(G));
return G;
}

bool graph_hasedge(graph *G, vertex v, vertex w){
REQUIRES(is_graph(G) && is_vertex(G, v) && is_vertex(G, w));
for(adjlist *L = G->adj[v]; L != NULL; L = L->next){
if(L->vert = w) return true;
}
return false;
}

void graph_addedge(graph *G, vertex v, vertex w){
REQUIRES(is_graph(G) && is_vertex(G, v) && is_vertex(G, w));
REQUIRES(v != w && !graph_hasedge(G, v, w));

adjlist *L;
L = xmalloc(sizeof(adjlist));
L->vert = w;
L->next = G->adj[v];
G->adj[v] = L;

L = xmalloc(sizeof(adjlist));
L->vert = v;
L->next = G->adj[w];
G->adj[w] = L;

ENSURES(is_graph(G));
ENSURES(graph_hasedge(G, v, w));
}

struct neighbor_header{
adjlist *next_neighbor;
}
typedef struct neighbor_header neighbors;

neighbors *graph_get_neighbors(graph *G, vertex v){
REQUIRES(is_graph(G) && is_vertex(G, v));
neighbors *nbors = xmalloc(sizeof(neighbors));
nbors->next_neighbor = G->adj[v];
ENSURES(is_neighbors(nbors));
return nbors;
}

bool graph_hasmore_neighbors(neighbors *nbors){
REQUIRES(is_neighbors(nbors));
return nbors->next_neighbor != NULL;
}

vertex graph_next_neighbor(neighbors *nbors){
REQUIRES(is_neighbors(nbors));
REQUIRES(graph_hasmore_neighbors(nbors));

vertex v = nbors->next_neighbor->vert;
nbors->next_neighbor = nbors->next_neighbor->next;
return v;
}

void graph_free_neighbors(neighbors *nbors){
free(nbors);
}