图通常以一个二元组G=
若图G中的每条边是没有方向的,则称为无向图;若图G中的每条边都是有方向的,则称为有向图。在无向图中,每条边都是由两个节点组成的无序对,例如节点V1和V3之间的边,记作(v1,v3)或(v3,v1)。在有向图中,有向边又称为弧,每条弧都是由两个节点组成的有序对,例如从节点v1到节点v3的弧,记作
节点的度指与该节点相关联的边数,记作TD(v)。
握手定理
所有节点的度数之和等于边数的两倍,即,其中n为节点数,e为边数。
若在计算度数之和时,每计算一度就画一条小短线,则可看出每条边都被计算了两次,如下图所示
在有向图中,节点的度又被称为入度和出度。节点v的入度是以节点v为终点的有向边的数量,记作ID(v),即进来的边数,节点v的出度是以节点v为始点的有向边的数量,记作OD(v),即发出的边数。节点v的度等于入度加上出度。所有节点的入度之和等于出度之和,又因为所有节点的度数之和等于边的2倍,因此
图的存储
图的结构比较复杂,任何两个点之间都可能有关系。图的存储分为顺序存储和链式存储。顺序存储包括邻接矩阵和边集数组,链式存储包括邻接表,链式前向量,十字链表和邻接多重表。
1.邻接矩阵
邻接矩阵一般采用一个一维数组存储图中节点的信息。采用一个二维数组存储图中节点的邻接关系。
表示方法
1.无向图的邻接矩阵
在无向图中,若从节点vi到节点vj有边,则邻接矩阵M[i][j]==M[j][i]=1,否则M[i][j]=0.
特点:
(1):无向图的邻接矩阵是对称矩阵,且是唯一的。
(2):第i行或者第j行非零元素的个数正好是第i个节点的度。
2.有向图的邻接矩阵
有向图中,若从vi到vj有边,则邻接矩阵M[i][j]=1,否则M[i][j]=0.
特点:
(1):有向图的邻接矩阵不一定对称
(2):第i行非零元素个数正好是第i个元素的出度,第i列非零元素的个数正好是第i+1个节点的入度。
3.网的邻接矩阵
网是带权图,需要存储边的权值,即(见视频)
其中wij表示边上的权值,当i=j时,wii也可被设置为零。
邻接矩阵的数据结构定义
#define MaxVnum 100
typedef char VexType
typedef int EgdeType
Typedef struct{
VexType Vex[MaxVnum];
EdgeType Edge[MaxVnum][MaxVnum];
int vexnum,edgenum;
}AMGraph;
邻接矩阵的存储方法
算法步骤:
- 输入节点数和边数
- 依次输入节点信息,并存储至节点数组Vex[]中
- 初始化邻接矩阵,若为图,则初始化为0,若为网,初始化为无穷
- 依次输入每条边依附的两个节点,若为网,还需要输入该边的权值。
代码实现如下
#include<iostream>//创建无向图的邻接矩阵
using namespace std;
#define MaxVnum 100 //顶点数最大值
typedef char VexType; //顶点的数据类型,根据需要定义
typedef int EdgeType; //边上权值的数据类型,若不带权值的图,则为0或1
typedef struct{
VexType Vex[MaxVnum];
EdgeType Edge[MaxVnum][MaxVnum];
int vexnum,edgenum; //顶点数,边数
}AMGraph;
int locatevex(AMGraph G,VexType x){
for(int i=0;i<G.vexnum;i++)//查找顶点信息的下标
if(x==G.Vex[i])
return i;
return -1;//没找到
}
void CreateAMGraph(AMGraph &G){
int i,j;
VexType u,v;
cout<<"请输入顶点数:"<<endl;
cin>>G.vexnum;
cout<<"请输入边数:"<<endl;
cin>>G.edgenum;
cout<<"请输入顶点信息:"<<endl;
for(int i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
cin>>G.Vex[i];
for(int i=0;i<G.vexnum;i++)//初始化邻接矩阵所有值为0,如果是网,则初始化邻接矩阵为无穷大
for(int j=0;j<G.vexnum;j++)
G.Edge[i][j]=0;
cout<<"请输入每条边依附的两个顶点:"<<endl;
while(G.edgenum--){
cin>>u>>v;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
G.Edge[i][j]=G.Edge[j][i]=1; //邻接矩阵储置1
else{
cout << "输入顶点信息错!请重新输入!"<<endl;
G.edgenum++;//本次输入不算
}
}
}
void print(AMGraph G){//输出邻接矩阵
cout<<"图的邻接矩阵为:"<<endl;
for(int i=0;i<G.vexnum;i++){
for(int j=0;j<G.vexnum;j++)
cout<<G.Edge[i][j]<<"\t";
cout<<endl;
}
}
int main(){
AMGraph G;
CreateAMGraph(G);
print(G);
return 0;
}
/*
4 5
a b c d
a b
a d
b c
b d
c d
*/
邻接矩阵的优缺点:
1.优点是可以快速判断两节点是否有边,方便计算各节点的度,时间复杂度低。
2.缺点是不便于增删节点,不便于访问所有邻接点,空间复杂度高。
边集数组
边集数组通过数组存储每条边的起点和终点,若为网,则增加一个权值域。网的边集数组数据结构定义如下:
struct Edge{
int u;
int v;
int w;
}e[N*N];
采用边集数组计算节点的度或者查找边时,要遍历整个边集数组,时间复杂度为O(e)。除非特殊需要,很少使用边集数组,比如求解最小生成树的kruskal算法时需要按照权值对边进行排序,使用边集数组更方便。
1.邻接表
无向图的邻接表,一个节点的所有邻接点构成一个单链表。
特点:若无向图有n个节点,e条边,则节点表有n个节点,邻接点表有2e个节点。节点的度为该节点后面单链表的节点数。
有向图的邻接表
特点:若有向图有n个节点,e条边,则节点表有n个节点,邻接点表有e个节点。节点的出度为该节点后面单链表中的节点数。
有向图的逆邻接表
特点:若有向图有n个节点,e条边,则节点表有n个节点,邻接点表有e个节点。节点的入度为该节点后面单链表中的节点数。
邻接表数据结构定义
typedef struct VexNode{
VexType data;
AdjNode *first;
}VexNode;
typedef struct{
VexNode Vex[MaxVnum];
int vexnum,edgenum;
}ALGraph;
邻接表存储方法
算法步骤:
- 输入节点数和边数
- 依次输入节点信息,并存储至节点数组Vex[]的data域中,将first域置空
- 依次输入每条边依附的两个节点,若为网,还需要输入该边的权值
实现代码如下
#include<iostream>//创建有向图的邻接表
using namespace std;
const int MaxVnum=100;//顶点数最大值
typedef char VexType;//顶点的数据类型为字符型
typedef struct AdjNode{ //定义邻接点类型
int v; //邻接点下标
struct AdjNode *next; //指向下一个邻接点
}AdjNode;
typedef struct VexNode{ //定义顶点类型
VexType data; // VexType为顶点的数据类型,根据需要定义
AdjNode *first; //指向第一个邻接点
}VexNode;
typedef struct{//定义邻接表类型
VexNode Vex[MaxVnum];
int vexnum,edgenum; //顶点数,边数
}ALGraph;
int locatevex(ALGraph G,VexType x){
for(int i=0;i<G.vexnum;i++)//查找顶点信息的下标
if(x==G.Vex[i].data)
return i;
return -1;//没找到
}
void insertedge(ALGraph &G,int i,int j){//插入一条边
AdjNode *s;
s=new AdjNode;
s->v=j;
s->next=G.Vex[i].first;
G.Vex[i].first=s;
}
void printg(ALGraph G){//输出邻接表
cout<<"----------邻接表如下:----------"<<endl;
for(int i=0;i<G.vexnum;i++){
AdjNode *t=G.Vex[i].first;
cout<<G.Vex[i].data<<": ";
while(t!=NULL){
cout<<"["<<t->v<<"]\t";
t=t->next;
}
cout<<endl;
}
}
void CreateALGraph(ALGraph &G){//创建有向图邻接表
int i,j;
VexType u,v;
cout<<"请输入顶点数和边数:"<<endl;
cin>>G.vexnum>>G.edgenum;
cout<<"请输入顶点信息:"<<endl;
for(i=0;i<G.vexnum;i++)//输入顶点信息,存入顶点信息数组
cin>>G.Vex[i].data;
for(i=0;i<G.vexnum;i++)
G.Vex[i].first=NULL;
cout<<"请依次输入每条边的两个顶点u,v"<<endl;
while(G.edgenum--){
cin>>u>>v;
i=locatevex(G,u);//查找顶点u的存储下标
j=locatevex(G,v);//查找顶点v的存储下标
if(i!=-1&&j!=-1)
insertedge(G,i,j);
else{
cout<<"输入顶点信息错!请重新输入!"<<endl;
G.edgenum++;//本次输入不算
}
}
}
int main(){
ALGraph G;
CreateALGraph(G);//创建有向图邻接表
printg(G);//输出邻接表
return 0;
}
/*
5 7
a b c d e
a b
a c
a e
b c
c d
c e
d e
*/
邻接表优缺点
1.优点是便于增删节点,便于访问所有邻接点,空间复杂度低
2.缺点是不便于判断两个节点间是否有边,不便于计算个节点的度。
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=100000+5;
int maxx[maxn],head[maxn];
int n,m,x,y,w,cnt;
struct Edge{
int to,w,next;
}e[maxn];
void add(int u,int v,int w){
e[cnt].to=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt++;
}
void printg(){
cout<<"----------链式前向星如下:----------"<<endl;
for(int v=1;v<=n;v++){
cout<<v<<": ";
for(int i=head[v];~i;i=e[i].next){ //i=-1 I^1
int v1=e[i].to,w1=e[i].w;
cout<<"["<<v1<<" "<<w1<<"]\t";
}
cout<<endl;
}
}
int main(){
cin>>n>>m;
memset(head,-1,sizeof(head));
cnt=0;
for(int i=1;i<=m;i++){
cin>>x>>y>>w;
add(x,y,w);//添加边
add(y,x,w);//添加反向边
}
printg();
return 0;
}
/*
4 5
1 2 5
1 4 3
2 3 8
2 4 12
3 4 9
*/