目录
邻接矩阵
邻接矩阵是表示顶点之间的关系的矩阵。
邻接矩阵的存储方式:
一个二维数组存储图中顶点之间的邻接关系。(如果顶点中的信息有多个,可以用一个一维数组存储图中顶点的信息)
在无向图当中:
如果vi到vj有边,则邻接矩阵M[i][j]=M[j][i]=1,否则M[i][j]=M[j][i]=0。
所以无向图的邻接矩阵关于对角线对称。
顶点的度:该顶点对应的那一行或者那一列的和即为它的度(几个1)。
在有向图当中:
如果vi到vj有边,则邻接矩阵M[i][j]=1,否则M[i][j]=0。(有多少条边就有多少个1)
顶点的出度:该顶点所对应的那一行的和。
顶点的入度:该顶点所对应的那一列的和。
顶点的度:顶点的出度+入度。
在带权图当中:
如果vi到vj有边,则邻接矩阵M[i][j]=wij(权重),否则M[i][j]=无穷大。
邻接矩阵的优点在于求顶点的度和判断两个顶点之间有没有边非常方便。缺点在于找某个顶点的所有邻接点必须要访问一行 或/和 一列,时间复杂度为O(N);不便于增删顶点;空间复杂度高。
const int maxn=10005;
int n,m;//结点数,边数
int E[maxn][maxn];//邻接矩阵
void Init_E_wuxiang(){
//E初始化默认为0
int i,a,b;
for(i=0;i<m;i++){
cin>>a>>b;
E[a][b]=E[b][a]=1;
}
}
void Init_E_youxiang(){
//E初始化默认为0
int i,a,b;
for(i=0;i<m;i++){
cin>>a>>b;
E[a][b]=1;
}
}
void Init_E_daiquanyouxiang(){
int i,j,a,b,w;
//E初始化默认为无穷大
for(i=0;i<n;i++){
for(j=0;j<n;j++){
E[i][j]=0x3f3f3f;
}
}
for(i=0;i<m;i++){
cin>>a>>b>>w;
E[a][b]=w;
}
}
边集数组
通过数组存储每条边的起点和终点。(如果是网则增加一个权值域)
struct Edge{
int u,v,w;
}e[N*N];
每个顶点可以引出N-1条边,有向图:N*(N-1),无向图:(N*(N-1))/ 2。
优点:可以对边进行排序(例如按权值);方便对边进行处理。
缺点:不便于判断两点之间是否有边(遍历);不便于访问所有邻接点(遍历);不便于计算各个顶点的度(邻接点-->遍历)。
邻接表
邻接表是图的一种链式存储方法。邻接表包含两部分:顶点和邻接点。
顶点:包括顶点信息和指向第一个邻接点的指针。
邻接点:包括邻接点的存储下标和指向下一个邻接点的指针。(不直接存邻接点,节省空间)
顶点vi的所有邻接点构成一个单链表。
其实邻接点也可以存放在vector当中。
在无向图当中:
顶点的度:该顶点对应的单链表的大小。
邻接点的个数等于边数*2。
在有向图当中:
只关注它的发出边。
顶点的出度:该顶点对应的单链表的大小。
入度就不好找了,需要遍历所有的邻接点。
const int Max=10005;
//邻接点类型
typedef struct AdjNode{
int v;//邻接点下标
struct AdjNode* next;//指向下一个邻接点
}AdjNode;
//定义顶点类型
typedef struct VexNode{
int data;//顶点数据
AdjNode* first;//指向第一个邻接点
}VexNode;
typedef struct{
VexNode vex[Max];//结点表
int vexnum,edgenum;//结点数,边数(在只有一个图的情况下可以不用定义这个结构体)
}ALGraph;
创建有向图的邻接表过程:
采用头插法,因为尾插法我们还需要用一个指针去记录尾部 。(最后单链表的元素顺序不唯一)
优点:便于增删结点;便于访问所有邻接点;空间复杂度低。
缺点:不便于判断两顶点之间是否有边;不便于计算有向图的顶点的入度。
#include<bits/stdc++.h>
using namespace std;
const int Max=10005;
//邻接点类型
typedef struct AdjNode{
int v;//邻接点下标
struct AdjNode* next;//指向下一个邻接点
}AdjNode;
//定义顶点类型
typedef struct VexNode{
char data;//顶点数据
AdjNode* first;//指向第一个邻接点
}VexNode;
int n,m;//顶点数,边数
VexNode vex[Max];
//插入一条边
void adde(int u,int v){
AdjNode* a=new AdjNode();
a->v=v;
a->next=vex[u].first;
vex[u].first=a;
}
//创建有向图邻接表
void creatAL(){
char x,y;
int i;
cin>>n>>m;
for(i=0;i<n;i++){
vex[i].data=(char)((int)'a'+i);
vex[i].first=nullptr;
}
for(i=0;i<m;i++){
cin>>x>>y;
adde(x-'a',y-'a');
}
}
void print(){
int i,j;
for(i=0;i<n;i++){
cout<<vex[i].data<<':';
AdjNode* a=vex[i].first;
while(a!=nullptr){
cout<<vex[a->v].data<<" ";
a=a->next;
}
cout<<endl;
}
}
int main(){
creatAL();
print();
return 0;
}
vector存储图
const int Max=10005;
vector<int>E[Max];//每个结点定义一个vector,存储其邻接点
int n,m;
void creatVec_wuxiang(){
int u,v,i;
cin>>n>>m;
for(i=0;i<m;i++){
cin>>u>>v;
E[u].push_back(v);
E[v].push_back(u);
}
}
结点为字符串类型:
unordered_map<string,int>mp;
void CreatVec(){
string s1,s2;
int k=0,i;
cin>>n>>m;
for(i=0;i<m;i++){
cin>>s1>>s2;
if(mp.find(s1)==mp.end()){
mp[s1]=k++;
}
if(mp.find(s2)==mp.end()){
mp[s2]=k++;
}
E[mp[s1]].push_back(mp[s2]);
}
}
链式前向星
链式前向星是一种静态链表存储,用边集数组和邻接表相结合,可以快速访问一个顶点的所有邻接点,一条边和它的反向边,在算法竞赛中广泛应用。
链式前向星存储包括两种结构:
边集数组:edge[ ],edge[i]表示第i条边。
(无向图的边集数组大小=边数*2)
struct node{
//to表示边的终点 (邻接点)的下标,next表示下一条邻接边的下标,w表示边的权值
int to,next,w;
}edge[maxe];
//边数maxe一般设置maxn*maxn,题目有要求除外
头结点数组:head[ ],head[i]存以i为起点的第一条边的下标(在edge[ ]中的下标)
int head[maxn];
插入同样选择头插法,没有下一条边就填-1(存的是下标!不是指针)
所以一开始head数组全部初始化为-1。
const int maxn=105;
struct node{
int to,w,next;
}edge[maxn*maxn];
int head[maxn];
int k=0;//代表edge数组的下标
//初始化
void init(){
memset(head,-1,sizeof(head));
k=0;
}
//添加一条边
void add(int u,int v,int w){
edge[k].to=v;
edge[k].w=w;
edge[k].next=head[u];
head[u]=k++;
}
如果是有向图,每输入一条边,执行一次add(u,v,w)即可;如果是无向图,执行add(u,v,w)和add(v,u,w)。
链式前向星的特性:
1、和邻接表一样,因为采用头插法进行链接,所以边输入顺序不同,创建的链式前向星也不同。
2、对于无向图,每输入一条边,需要添加两条边,互为反向边。每条边可以通过与1异或运算得到其反向边,也就是说如果一条边的下标为i,则其反向边为i^1。
3、链式前向星具有边集数组和邻接表的功能,属于静态链表,不需要频繁创建结点,应用十分灵活。