数据结构:图的邻接表

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

这是我的作业。。。。希望对各位有#include <stdio.h> #include <malloc.h> #include <stdlib.h> #define SIZE (xsize*ysize+1) //一系列全局变量便于传递参数 int *location,*way, xsize,ysize,firstx,firsty, noworder; int getnum (void);//取数函数,取数成功返回1,否则返回-1 int init (void); //初始化函数,申请数组空间,以及初始化数组, //申请成功返回1,否则返回-1 int play (void); //下棋函数,下棋成功返回1,否则返回-1 int back (void); //悔棋函数,悔棋成功返回1,否则返回-1 void print (void);//输出函数,顺序输出马踩的棋盘一维坐标 //////////////////////////// void main () { int canget,caninit,canplay,canback; do canget=getnum(); while(canget==-1); caninit=init(); if(caninit==-1) exit (0);//终止程序运行 for (;noworder<SIZE-1;) { if(way[location[noworder]]>0 && way[location[noworder]]<=8) { canplay=play(); if(canplay==-1) way[location[noworder]]++;//当前方法不可行,改变方法 } else { canback=back(); if(canback==-1) { printf("不可能遍历整个棋盘!\n"); getchar();getchar(); exit (0);//当程序不能再悔棋时终止程序运行 } else way[location[noworder]]++; //当前方法不可行,改变方法 } } if(noworder==SIZE-1)//已经遍历整个棋盘 print(); getchar();getchar(); } //////////////////////////// int getnum() { printf("输入棋盘规格(假定无0点)和入口坐标:\n"); printf("输入棋盘规格xsize="); scanf("%d",&xsize); printf("输入棋盘规格ysize="); scanf("%d",&ysize); printf("输入入口坐标x="); scanf("%d",&firstx); printf("输入入口坐标y="); scanf("%d",&firsty); if (firstx>xsize || firsty>ysize || firstx<=0 || firsty<=0 || xsize <3 || ysize<3) { printf("输入有误,重新输入:\n\n\a"); return -1; } else return 1; } //////////////////////////// int init (void) { location=(int *)malloc(sizeof(int)*SIZE); way=(int *)malloc(sizeof(int)*SIZE); if(location==NULL || way==NULL) { printf("系统申请内存空间失败!程序执行终止!\a"); return -1; } for(int i=0;i<SIZE;i++)//初始化数组 { way[i]=0; location[i]=0; } noworder=1; location[1]=(firsty-1)*xsize+firstx; way[location[1]]=1; return 1; } //////////////////////////// void print(void) { printf("\n\n可以遍历,顺序如下:\n\n"); for (int i=1;i<SIZE;i++) { printf("%3d-->",location[i]); printf("OK\n"); } } //////////////////////////// int play() { int x,y,nextlocation; //一维坐标值à二维坐标值 x=location[noworder] % xsize; if(x==0) x=xsize; y=(location[noworder]-x)/xsize+1; switch (way[location[noworder]]) { case 1 : x+=2;y-=1;break; case 2 : x+=2;y+=1;break; case 3 : x+=1;y+=2;break; case 4 : x-=1;y+=2;break; case 5 : x-=2;y+=1;break; case 6 : x-=2;y-=1;break; case 7 : x-=1;y-=2;break; case 8 : x+=1;y-=2;break; } nextlocation = xsize*(y-1)+x; if (x>xsize || y>ysize || x<=0 || y<=0 || way[nextlocation]!=0)//越界或重复 return -1; else//下棋 { noworder++; location[noworder] = nextlocation; way[location[noworder]]=1; return 1; } } //////////////////////////// int back (void) { if(noworder==1)//不能再悔棋,不能遍历 return -1; else { way[location[noworder]]=0;//注意不能搞错语句顺序 location[noworder]=0; noworder--; return 1; } }用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值