普通的数据结构教科书对邻接表的实现中规中矩,耶鲁大学教授James Aspnes的代码显得非常油菜花,而且运用了C语言的一个称为bug的特性,不检查数组越界。
选几小段分析一下:
struct successors{
int d; /* number of successors */
int len; /* number of slots in array */
char is_sorted; /* true if list is already sorted */
int list [1]; /* actual list of successors */
};
struct graph{
int n; /* number of vertices */
int m; /* number of edges */
struct successors *alist[1];
};
/* 由于VC不支持结构体的嵌套定义,我将struct successors从struct graph中抽取了出来 */
/* create a new graph with n vertices labeled 0..n-1 and no edges */
Graph graph_create(int n){
Graph g;
int i;
g = (Graph) malloc(sizeof(struct graph) + sizeof(struct successors *) * (n-1));
assert(g);
g->n = n;
g->m = 0;
for(i = 0; i < n; i++){
g->alist[i] = (struct successors *) malloc(sizeof(struct successors));
assert(g->alist[i]);
g->alist[i]->d = 0;
g->alist[i]->len = 1;
g->alist[i]->is_sorted = 1;
}
return g;
}
struct successors *alist[1] 是一个数组,该数组的类型是指向struct successors的指针,数组的元素只有一个。
看到这里,也许你和我一样,心中有个隐隐约约的疑问,为什么只有一个元素???别着急,往下看。
在graph_create函数调用时,一切都明白了,原来上面定义的邻接表只能容纳一个顶点的指针(很重要),运行到malloc函数时,补齐了剩下的n-1个顶点的指针。在接下来的for循环中,将这些指针指向了新开辟的内存块。之所以这样写能工作,得益于
1. malloc开辟出逻辑连续的内存块。
2. C语言对数组越界不做检查。
再来看看添加边的函数
/* add an edge to an existing graph */
void grpah_add_edge(Graph g, int u, int v){
assert(u >= 0);
assert(v >= 0);
assert(u < g->n);
assert(v < g->n);
/* do we need to grow the list? */
while(g->alist[u]->d >= g->alist[u]->len){
g->alist[u]->len *= 2;
g->alist[u] =
(struct successors *)realloc(g->alist[u], sizeof(struct successors) + sizeof(int) * (g->alist[u]->len -1));
}
/* now add the new sink */
g->alist[u]->list[g->alist[u]->d++] = v;
g->alist[u]->is_sorted = 0;
g->m++;
}
这段代码同样利用了C语言不检查数组越界的特性。最终,该图在内存中的结构如下所示。

Successor下面的数字代表该顶点与哪些顶点相连接。
完整的源代码在这里:
http://www.cs.yale.edu/homes/aspnes/pinewiki/C(2f)Graphs.html

408

被折叠的 条评论
为什么被折叠?



