1.邻接矩阵
用一个n×n的二维数组来存储,例如node[i][j],代表的是从点i到点j的距离为node[i][j],缺点是无法存储重边的情况,比如从i点到j点有两条路可以走,一条路的长度为3,一条路的长度为5
用一个n×n的二维数组来存储,例如node[i][j],代表的是从点i到点j的距离为node[i][j],缺点是无法存储重边的情况,比如从i点到j点有两条路可以走,一条路的长度为3,一条路的长度为5
代码实现
int n,m,node;
scanf("%d%d",&n,&m);//n点的个数,m边的条数
//建立城市之前的关系图,自身到自身距离为0,两个城市无法到达则为INF
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i==j)
city[i][j]=0;
else city[i][j]=INF;
}
int x,y,r;
//输入题目给出的两个城市之间的距离
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&r);
city[x][y]=r;
}
2.向前星
读入每条边的信息,将边存放在数组中,把数组中的边按照起点顺序排序
优点是应对点非常多的情况,可以存储重边,但是不能直接判断任意来两个顶点之间是否有边,而且排序要浪费一些时间
代码实现
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<map>
#include<iomanip>
#define INF 99999999
#define MAX 20
using namespace std;
//向前星
int head[MAX];//用来记录存储起点为i的第一条边的位置 ,也就是说在排好序以后,该起点的第一条边在这个边的数组中的位置
//利用head数组也是为了以后查询方便,可以查询从某一顶点出发的所有的边
struct Node{
int from;//边的起点
int to;//边的终点
int w;//边的权值
};
Node edge[MAX];//建立一个边的集合的数组
bool cmp(Node a, Node b)//比较函数
{
if(a.from==b.from && a.to==b.to) return a.w<b.w;//如果两条边的起点和终点相同,则按照权值从小到大排序
if(a.from==b.from) return a.to<b.to;//如果两条边的起点相同,但是终点不相同,按照重点的大小从小到大排序
return a.from<b.from;//如果两条边的起点不相同,则按照起点的大小,从小到大排序
}
int main()
{
int n,m;//n代表点的个数,m代表边的条数
cin>>n>>m;
for(int i=0;i<m;i++)
cin>>edge[i].from>>edge[i].to>>edge[i].w;
sort(edge,edge+m,cmp); //按上面的排列规则,把边按顺序排好
memset(head,-1,sizeof(head));
head[edge[0].from]=0;//第一条边的位置为0
for(int i=1;i<m;i++)
if(edge[i].from!=edge[i-1].from) head[edge[i].from]=i;//如果该起点与上一条边的起点不相同,则是一个新的点
//所以该新的起点的第一条变的的位置就要记录下来
//遍历代码
for(int i=1;i<=n;i++)
{
for(k=head[i];edge[k].from==i && k<m;k++)//k为顶点为i的第一条边的位置,如果该条边的起点不是i,或者该条边的
{ //位置超出了最大位置,这些边的位置最大为m-1,因为是从0开始算起的
cout<<edge[k].from<<" "<<edge[k].to<<" "<<edge[k].w<<endl;
}
}
return 0;
}
/*测试数据
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
程序运行效果
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
*/
3.邻接表
邻接表就是以每一个节点为起点,分别建立一个单链表,用来链接与它直接相邻的点,通过邻接表可以访问与每个节点直接相连的边与顶点
(1)利用vector动态实现
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<map>
#include<iomanip>
#define INF 99999999
#define maxn 20
#include<vector>
using namespace std;
struct node{
int to;//终点
int w;//权值
};
vector<node> Map[maxn];
int main()
{
int n,m;
node t;
cin>>n>>m;
for(int i=0;i<m;i++)
{
int from;//form为邻接表的头 ,也就是邻接表的边的起点
cin>>from>>t.to>>t.w;
Map[from].push_back(t);
}
cout<<endl;
//遍历每一个点的邻接表
for(int i=1;i<=n;i++)
{
for(vector<node>::iterator k=Map[i].begin();k!=Map[i].end();k++)//遍历邻接表的每一条边
{
t=*k;
cout<<i<<" "<<t.to<<" "<<t.w<<endl;
}
}
return 0;
}
/*测试数据
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
程序运行效果
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
*/
(2)利用数组静态实现(链式向前星)
链式向前星的建图效率非常高,读入结束,建图就结束,时间复杂度为O(m),空间上使用了两个数组,空间复杂度为
O(m+n)
链式向前星的优点在于除了必要的边信息的存储空间外,只需要非常少的额外空间即可实现,代码和原理都比较简单,可以存储重边,还可以应付点和边的数量很大的情况,与动态建表相比没有内存管理,更安全,因该说除了不能直接用起点和终点确定是否有边以外,链式向前星几乎是完美的(以上语句摘自《图论及应用ACM-ICPC程序设计系列》)
用邻接表来存储图的结构,构图的时间复杂度为O(m),遍历每一条边的时间复杂度也为O(m)
如果一个图是稀疏图的话,m要远小于n,因此稀疏图用邻接表来存储要比邻接矩阵要好得多
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<map>
#include<iomanip>
#define INF 0x3f3f3f3f
#define maxn 20
#include<vector>
using namespace std;
struct node{
int to;
int w;
int next;
};
int main()
{
int n,m,from;//form代表邻接表的第一个点,也就是起点
node Edge[maxn];//node类型的存放边的数组
int head[maxn];//用来存放第i个点所对应的第一条边在Edge数组里面的位置
cin>>n>>m;
memset(head,-1,sizeof(head));
//相当于链表的前插,就是每次插入一条边都是插入到该链表的第一位置,比如链表一开始为1->2->3,后来插入了4,就变成了
//4->1->2->3
for(int i=0;i<m;i++)
{
cin>>from>>Edge[i].to>>Edge[i].w;
Edge[i].next=head[from];
head[from]=i;
}
cout<<endl;
//遍历每一个点的邻接表
for(int i=1;i<=n;i++)
{
for(int j=head[i];j!=-1;j=Edge[j].next)//-1代表该邻接表已经没有下一条边了,该点的邻接表遍历完成
cout<<i<<" "<<Edge[j].to<<" "<<Edge[j].w<<endl;
}
return 0;
}
/*测试数据
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
程序运行效果
1 3 12
1 2 1
2 4 3
2 3 9
3 5 5
4 6 15
4 5 13
4 3 4
5 6 4
*/