图的存储

图的存储一共有四种方法:

1.邻接矩阵:这种方法简单易懂,但是容易造成内存的浪费。

       最简单的存图方式,一般就是开辟一个二维数组,比如一共有n个点组成的图,就开个edge[n][n],edge[1][2] = 3,就表示点1到点2的权值为3,但是有一些到不了的点,比如4到达不了5,但是仍有edge[4][5]这个数据,我们可以把它初始化为一个标记数,代表无法到达。太简单,代码就不放了;

2.邻接表:改善了矩阵所带来的内存浪费问题,但是操作不便。

邻接表的处理办法是这样。

1)  图中顶点用一个一维数组存储,当然也可以用单链表来存储,不过用数组可以较容易的读取顶点信息,更加方便。另外,对于顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息。

2)  图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储,无向图称为顶点vi的边表,有向图则称为以vi为弧尾的出边表。

简单来讲就是定义一个头节点结构体数组,存储节点信息与链表信息,大大减少了内存的占用。

代码:

typedef struct node{
	int data;
	int value;
	struct node *prev;
	struct node *next;
}Node;

typedef struct graph{
	Elemtype data;
	struct node *first;
	struct node *last;
}Graph;

Graph *Create_graph(char *s){
	int len = strlen(s);
	Graph *q = (Graph *)malloc(len * sizeof(*q));
	for(int i = 0 ; i < len ; i++){
		q[i].data = s[i];
		q[i].first = q[i].last = NULL;
	}
	char start,end;
	int w;
	while(1){
		scanf("%c%c %d",&start,&end,&w);
		getchar();
		if(start == '.' && end == '.'){
			break;
		}
		Insert(q,s,start,end,w);
	}
	return q;
}

void Insert(Graph *q,char *s,char start,char end,int w){
	int x = find(s,start);
	int y = find(s,end);
	if(x != -1 && y != -1){
		Node *p = (Node *)malloc(sizeof(*p));
		p->data = y;
		p->next = p->prev = NULL;
		p->value = w;
		if(!q[x].first){
			q[x].first = q[x].last = p;
		}
		else{
			q[x].last->next = p;
			p->prev = q[x].last;
			q[x].last = p;
		}
	}
	return ;
}

3.十字链表:用于有向图的存储,同时具有矩阵的形式与链表的简洁。

对于有向图来说,邻接表是有缺陷的,关心了出度问题,想了解入度就必须要遍历整个图才能知道,反之,逆邻接表解决了入度的情况。

把邻接表与逆邻接表结合起来,即有向图的一种存储方法十字链表(Orthogonal   List)。

我们重新定义顶点表结构

firstin表示入边表头指针,指向该顶点的入边表中第一个结点;

firstout表示出边表头指针,指向该顶点的出边表中第一个结点;

重新定义了边表结点结构

其中

tailvex是指弧起点在顶点的下标,

headvex是指弧终点在顶点表中的下标,

headlink是指入边表指针域,指向终点相同的一下条边

taillink是指出边表指针域,指向起点相同的下一条边。

如果是网,还可以再增加一个weight域来存储权值。

 

如图对于这样的一个有向图,构建如下的十字链表

 

对于十字链表的一些认识:

1.表头结点即顶点结点,与邻接表一样是顺序存储。

2.对于每个顶点结点之后是与之相关联的弧结点(该弧结点中存有弧头、弧尾),而邻接表则是一些与顶点结点相连接的点。

3.从每个顶点结点开始有两条链表,一条是以该顶点结点为弧头的链表,一条是以该顶点结点为弧尾的链表。

4.对于其中的每一条链表必然是从顶点结点开始,直到与之相关的弧结点链域headlink和taillink为空是结束,构成一条完整的链表。

代码:

typedef struct node{
	int start,end,value;
	struct node *next_in;
	struct node *next_out;
}Node;

typedef struct head{
	char name;
	struct node *first_in;
	struct node *first_out;
}Head; 

int find_vex(Head *q,char c,int len){
	for(int i = 0 ; i < len ; i++){
		if(q[i].name == c){
			return i;
		}
	}
	return -1;
}

void print(Head *q,int vex){
	Node *t1 = q[vex].first_out;
	Node *t2 = q[vex].first_in;
	printf("%c的出度为:",q[vex].name);
	while(t1){
		printf("%c %c %d     ",q[t1->start].name,q[t1->end].name,t1->value);
		t1 = t1->next_out;
	}
	printf("\n");
	printf("%c的入度为:",q[vex].name);
	while(t2){
		printf("%c %c %d     ",q[t2->start].name,q[t2->end].name,t2->value);
		t2 = t2->next_in;
	}
	printf("\n");
	printf("\n");
	return ;
}

Head *Create_list(char *s){
	int len = strlen(s);
	Head *q = (Head *)malloc(len * sizeof(*q));
	for(int i = 0 ; i < len ; i++){
		q[i].first_in = q[i].first_out = NULL;
		q[i].name = s[i];
	}
	char start,end;
	int value;
	while(scanf("%c%c %d",&start,&end,&value)){
		getchar();
		if(start == '.' && end == '.'){
			break;
		}
		int x = find_vex(q,start,len);
		int y = find_vex(q,end,len);
		if(x != -1 && y != -1){
			Insert_edge(q,value,x,y);
		}
	}
	return q;
}

void Insert_edge(Head *q,int w,int x,int y){
	Node *p = (Node *)malloc(sizeof(*p));
	p->next_in = p->next_out = NULL;
	p->start = x;
	p->end = y;
	p->value = w;
	if(q[x].first_out == NULL){
		q[x].first_out = p;
	}
	else{
		Node *t1 = q[x].first_out;
		while(t1->next_out){
			t1 = t1->next_out;
		}
		t1->next_out = p;
	}
	if(q[y].first_in == NULL){
		q[y].first_in = p;
	}
	else{
		Node *t2 = q[y].first_in;
		while(t2->next_in){
			t2 = t2->next_in;
		}
		t2->next_in = p;
	}
	return ;
}

void Del_edge(Head *q,int w,int x,int y){
	Node *t1 = q[x].first_out;
	Node *t2 = q[y].first_in;
	Node *t = t1;
	while(t1){
		if(t1->start == x && t1->end == y && w == t1->value){
			if(t == t1){
				q[x].first_out = NULL;
				break;
			}
			t->next_out = t1->next_out;
			t1->next_out = NULL;
			break;
		}
		t = t1;
		t1 = t1->next_out;
	}
	t = t2;
	while(t2){
		if(t2->start == x && t2->end == y && w == t2->value){
			if(t == t2){
				q[y].first_in = NULL;
				free(t2);
				break;
			}
			t->next_in = t2->next_in;
			t2->next_in = NULL;
			free(t2);
			break;
		}
		t = t2;
		t2 = t2->next_in;
	}
	return ;
}

十字链表的好处就是因为把邻接表和逆邻接表整合在一起,这样既容易找到vi为尾的弧,也容易打到以vi为头的弧,因而容易求得顶点的出度和入度。而且它除了结构复杂一点燃上,其实创建图的算法的时间复杂度与邻接表相同,因此,在有向图的应用中,十字链表是非常好的数据结构模型。

4.临接多重表:用于存储无向图,其实个人觉得跟十字链表差不多,都是每个结点都存储一条边及两个顶点,同样存储方便。

对于无向图的边操作,如下图要删除(v0,v2)这条边要做

 

需要对邻接表结构中右边表的阴影两个结点进行删除操作,显然比较烦琐

因此,我们也仿照十字链表的方式对边表结构进行一些改造,也许就可避免刚才的问题。

重新定义边表结构:

 

其中mark是标志域,用于标记该条边是否访问过。ivex和jvex是与某条边依附的两个顶点在顶点表中的下标。ilink指向依附顶点ivex的下条边,jlink指向依附顶点的下一条边。这就是邻接多重表结构

 对于顶点结构不变,也是用一个结点表示,由存储与该结点信息相关的域date和指示第一条依附于该结点的边的域firstedge构成。

 

我们来看结构示意图的绘制过程,理解了它是如何连线的,也就是理解邻接多重表构造原理了。

 

 

 

对于邻接多重表的一些认识:

1.表头结点即顶点结点,与邻接表一样是顺序存储。

2.对于每个顶点结点之后是与之相关联的边结点(与该顶点结点相连的边),而邻接表则是一些与顶点结点相连接的点。

3.从每个顶点结点开始有一条链表,这条链表将所有与该顶点相连的边都连接了起来。

4.邻接多重表中边结点的个数就是无向图中边的数量,又因为无向图中的边必然连接两个顶点,所以便边结点结构中的ilink和jlink会连接两个不同的链表。

代码:

typedef char Elemtype;

typedef struct node{
	int mark;
	int start,end;
	struct node *slink;
	struct node *elink;
	int value;
}Node;

typedef struct Head{
	Elemtype data;
	struct node *first;
}Head;

int find_vex(Head *q,char c,int len){
	for(int i =  0 ; i < len ; i++){
		if(q[i].data == c){
			return i;
		}
	}
	return -1;
}

Head *Create_list(char *o){
	int len = strlen(o);
	Head *q = (Head *)malloc(len * sizeof(*q));
	for(int i = 0 ; i < len ; i++){
		q[i].data = o[i];
		q[i].first = NULL;
	}
	char s,e;
	int w;
	while(scanf("%c%c %d",&s,&e,&w)){
		getchar();
		if(s == '.' && e == '.'){
			break;
		}
		int x = find_vex(q,s,len);
		int y = find_vex(q,e,len);
		if(x != -1 && y!= -1){
			Insert_edge(q,x,y,w);
		}
	}
	return q;
}

void Insert_edge(Head *q,int s,int e,int w){
	Node *p = (Node *)malloc(sizeof(*p));
	p->elink = p->slink = NULL;
	p->start = s;
	p->end = e;
	p->mark = 0;
	p->value = w;
	if(!q[s].first){
		q[s].first = p;
	}
	else{
		Node *t = q[s].first;
		while(1){
			if(t->start == s && t->slink){
				t = t->slink;
			}
			else if(t->end == s && t->elink){
				t = t->elink;
			}
			else break;
		}
		if(t->start == s){
			t->slink = p;
		}
		else{
			t->elink = p;
		}
	}
	if(!q[e].first){
		q[e].first = p;
	}
	else{
		Node *t = q[e].first;
		while(1){
			if(t->start == e && t->slink){
				t = t->slink;
			}
			else if(t->end == e && t->elink){
				t = t->elink;
			}
			else break;
		}
		if(t->start == e){
			t->slink = p;
		}
		else{
			t->elink = p;
		}
	}
	return ;
}

void print(Head *q,int vex){
	Node *t = q[vex].first;
	printf("与%c有关的边:",q[vex].data);
	while(t){
		printf("%c%c %d,",q[t->start].data,q[t->end].data,t->value);
		if(t->start == vex){
			t = t->slink;
		}
		else{
			t = t->elink;
		}
	}
	printf("\n");
	return ;
}


  

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值