图通常以一个二元组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 100typedef char VexTypetypedef 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或1typedef 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; //邻接矩阵储置1else{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 5a b c da ba db cb dc 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 7a b c d ea ba ca eb cc dc ed 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^1int 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 51 2 51 4 32 3 82 4 123 4 9*/
