图的存储一共有四种方法:
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 ;
}