普通的数据结构教科书对邻接表的实现中规中矩,耶鲁大学教授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