数据结构书上有3种方式存图这样的结构,总结下什么情况用什么。
1, 当图中点的个数不是很大时(5000以下),可以用邻接矩阵来存图。邻接矩阵实际就是一个2维数组map【i】【j】所以空间复杂度占N^2。比如 顶点i 到 顶点j 有一条有向边,(在这里定义i顶点为头,j顶点为尾)存入map【i】【j】中,代表 i 点到 j 点的边权为数组里的值,如果没有边权,则另开一个一维数组用来存点权,map数组只用来存是否有 i 到 j 这条边。如果是无向边时,则需要在map【j】【i】里存入。
特性: 这种方式存入查询很快并且可以直接找到2点之间的联系,但是缺点也很多, 当点大,但是边少时浪费内存,点太大时存不下等问题。
2, 当点的个数太大,数组无法储存时,可以用链式前向星和邻接表来储存。先说第一种前向星,开一个结构体,存入边的头,尾及边权(如果没有边权还是另开一维数组存点权),然后把它们通过边权或头排序,这种结构用的不多,因为缺点很多,比如查询两点之间联系很慢,好处你们自己根据题来看吧。现在就来讲讲用的最多的另外一种方式,邻接表和链式前向星,其实后者和前者类似,所以只讲邻接表。
邻接表首先需要开一个first数组,next数组和edge数组,用来存边的头尾和边权,当然数组只需要开边的个数大小就行了。first数组赋初值为-1,first【i】的值指的是以顶点i为头的一条边在edge数组的下标数值,next数组下标为first【i】的值就是以i 点为头的下条不同边.
举个栗子: 顶点5-->顶点3和5-->9这两条边.
首先edge【1】记录第一条边的头为5,尾为3和边权pi,edge【2】同样。。。
之后来看记录第一条边时数组的变化,first【5】=1,next【1】=-1。first【5】=1 表示通过first数组找到 以顶点5为头的一条边信息存在edge数组下标为1的数组里。....next【1】=-1,表示以i为头的下一条边信息存在edge数组下标为-1的数组内,没有-1下标,所以表示没有以i 顶点为头的边了(这也正是为什么first数组初始化为-1的原因)。
之后当记录5->9这条边时,first【5】的值由1变成2,next【2】=1,表示以顶点5为头的一条边信息存在edge数组下标为2的数组内,然后查询下条边时,发现next【2】=1,表示下条以5为头的边的记录存在了edge数组下标为1的数组内,然后再找下条边时,next【1】=-1,表示没有下条边了。之后每当出现以5为头的边时,first【5】的值就会变成这条边在edge数组的记录下标,之后通过改变next【first【5】】的值来连接之前储存的边,就像链表的头插过程。
其实就是通过first数组来找到以i为头记录的第一条边在edge数组哪个位置储存,并通过next数组来找到以i为头的下一条边在edge数组哪里储存。
特性:邻接表这个结构优点就是可以很快查询到一个顶点作为头时与之相连的边有哪些。缺点就是无法直接查询到2点之间的联系。
关于这个写法书上一种,我在网上看到有另外一种更简单明了的。
附上代码,书上和我写的可能不太一样,但是思路是一样的:
for(int i=1;i<=m;i++){
scanf("%d%d%d",&tou,&wei,&quanzhi);
edge[i].tou=tou;
edge[i].wei=wei;
edge[i].quanzhi=quanzhi;
next[i]=first[tou];
first[tou]=i;
}
还有一种是网上看到的:
vector<int> map[max];
for(int i=1;i<=m;i++){
scanf("%d%d%d",&tou,&wei,&quanzhi);
edge[i].tou=tou;
edge[i].wei=wei;
edge[i].quanzhi=quanzhi;
map[tou].push_back(i);
}
还有,上面都是是有向边的存法,如果是无向边,edge需要开两倍,反着再存一次就行了,加油。