数据结构:图的邻接表

普通的数据结构教科书对邻接表的实现中规中矩,耶鲁大学教授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语言不检查数组越界的特性。最终,该图在内存中的结构如下所示。

 
数据结构:图的邻接表_第1张图片

 

Successor下面的数字代表该顶点与哪些顶点相连接。
 

 

完整的源代码在这里:

http://www.cs.yale.edu/homes/aspnes/pinewiki/C(2f)Graphs.html

你可能感兴趣的:(数据结构,C语言)