邻接矩阵(数组表示法)
基本思想: 用一个一维数组存储图中顶点的信息,用一个二维数组(称为邻接矩阵)存储图中各顶点之间的邻接关系。
无向图的邻接矩阵
无向图的邻接矩阵的特点:主对角线为0且一定是对称矩阵。
如何求顶点i的度:邻接矩阵的第i行(或第i列)非零元素的个数。
如何判断顶点 i 和 j 之间是否存在边:测试邻接矩阵中相应位置的元素arc[i][j]是否为1。
如何求顶点 i 的所有邻接点:将数组中第 i 行元素扫描一遍,若arc[i][j]为1,则顶点 j 为顶点 i 的邻接点。
有向图的邻接矩阵
有向图的邻接矩阵一定不对称吗:不一定,例如有向完全图。
如何求顶点 i 的出度:邻接矩阵的第 i 行元素之和。
如何求顶点 i 的入度:邻接矩阵的第 i 列元素之和。
如何判断从顶点 i 到顶点 j 是否存在边:测试邻接矩阵中相应位置的元素arc[i][j]是否为1。
邻接矩阵存储无向图的类
const int MaxSize=10;
template<class T>
class Mgraph{
public:
MGraph(T a[ ], int n, int e );
~MGraph( )
void DFSTraverse(int v);
void BFSTraverse(int v);
……
private:
T vertex[MaxSize];
int arc[MaxSize][MaxSize];
int vertexNum, arcNum;
};
邻接矩阵中图的基本操作——构造函数
MGraph(T a[ ], int n, int e );
1、确定图的顶点个数和边的个数;
2、输入顶点信息存储在一维数组vertex中;
3、初始化邻接矩阵;
4、依次输入每条边存储在邻接矩阵arc中;
4.1 输入边依附的两个顶点的序号i, j;
4.2 将邻接矩阵的第i行第j列的元素值置为1;
4.3 将邻接矩阵的第j行第i列的元素值置为1;
template<class T>
MGraph::MGraph(T a[ ], int n, int e) {
vertexNum=n; arcNum=e;
for (i=0; i<vertexNum; i++)
vertex[i]=a[i];
for (i=0; i<vertexNum; i++)
for (j=0; j<vertexNum; j++)
arc[i][j]=0;
for (k=0; k<arcNum; k++) {
cin>>i>>j;
arc[i][j]=1;
arc[j][i]=1;
}
}
邻接矩阵中图的基本操作——深度优先遍历
⑴ 访问顶点v;
⑵ 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;
⑶ 重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。
递归定义
int visited[MaxSize];
template<class T>
void MGraph::DFSTraverse(int v){
cout<<vertex[v]; visited [v]=1;
for (j=0; j<vertexNum; j++)
if (arc[v][j]==1 && visited[j]==0)
DFSTraverse( j );
}
邻接矩阵中图的基本操作——广度优先遍历
⑴ 访问顶点v;
⑵ 依次访问v的各个未被访问的邻接点v1, v2, …, vk;
⑶ 分别从v1,v2,…,vk出发依次访问它们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问。直至图中所有与顶点v有路径相通的顶点都被访问到。
int visited[MaxSize];
template<class T>
void MGraph::BFSTraverse(int v){
front=rear=-1;
int Q[MaxSize];
cout<<vertex[v];
visited[v]=1;
Q[++rear]=v;
while (front!=rear){
v=Q[++front];
for (j=0; j<vertexNum; j++)
if (arc[v][j]==1 && visited[j]==0 ){
cout<<vertex[j];
visited[j]=1;
Q[++rear]=j;
}
}
}
邻接矩阵上的其他操作
增加一个顶点:在存储顶点的一维数组中插入该顶点的信息,在邻接矩阵中插入一行、一列。
删除一个顶点:在存储顶点的一维数组中删除该顶点的信息,在邻接矩阵中删除一行、一列。
增加一条边:修改相应的矩阵元素的值。
删除一条边:修改相应的矩阵元素的值。
邻接表
假设图G有n个顶点e条边,则存储该图需要O(n2) 。
基本思想: 对于图的每个顶点vi,将所有邻接于vi的顶点链成一个单链表,称为顶点vi的边表(对于有向图则称为出边表),所有边表的头指针和存储顶点信息的一维数组构成了顶点表。
邻接表有两种结点结构:顶点表结点和边表结点。
定义邻接表的结点
struct ArcNode{
int adjvex;
ArcNode *next;
};
template<class T>
struct VertexNode{
T vertex;
ArcNode *firstedge;
};
无向图的邻接表
边表中的结点表示什么:每个结点对应图中的一条边, 邻接表的空间复杂度为O(n+e)。
如何求顶点 i 的度:顶点i的边表中结点的个数。
如何判断顶点 i 和顶点 j 之间是否存在边:测试顶点 i 的边表中是否存在终点为 j 的结点。
有向图的邻接表(出边表)
如何求顶点 i 的出度:顶点 i 的出边表中结点的个数。
如何求顶点 i 的入度:各顶点的出边表中以顶点 i 为 终点的结点个数。
如何求顶点 i 的所有邻接点:遍历顶点 i 的边表,该边表中的所有终点都是顶点 i 的邻接点。
有向图的逆邻接表(入边表)
为方便的计算有向图的顶点的入度,可以构造逆邻接表。 在逆邻接表中,边表中存储的是以顶点vi为弧头的弧。
邻接表存储有向图的类
const int MaxSize=10;
template<class T>
class ALGraph {
public:
ALGraph(T a[ ], int n, int e);
~ALGraph;
void DFSTraverse(int v);
void BFSTraverse(int v);
………
private:
VertexNode adjlist[MaxSize];
int vertexNum, arcNum;
};
邻接表中图的基本操作——构造函数
1、确定图的顶点个数和边的个数;
2、输入顶点信息,初始化该顶点的边表;
3、依次输入边的信息并存储在边表中;
3.1 输入边所依附的两个顶点的序号i和j;
3.2 生成邻接点序号为j的边表结点s;
3.3 将结点s插入到第i个边表的头部;
template<class T>
ALGraph::ALGraph(T a[ ], int n, int e) {
vertexNum=n;
arcNum=e;
for (i=0; i<vertexNum; i++)
{
adjlist[i].vertex=a[i];
adjlist[i].firstedge=NULL;
}
for (k=0; k<arcNum; k++)
{
cin>>i>>j;
s=new ArcNode;
s->adjvex=j;
s->next=adjlist[i].firstedge;
adjlist[i].firstedge=s;
}
}
邻接表中图的基本操作——深度优先遍历
template<class T>
void ALGraph::DFSTraverse(int v){
cout<<adjlist[v].vertex;
visited[v]=1;
p=adjlist[v].firstedge;
while (p!=NULL){
j=p->adjvex;
if (visited[j]==0)
DFSTraverse(j);
p=p->next;
}
}
邻接表中图的基本操作——广度优先遍历
template<class T>
void ALGraph::BFSTraverse(int v){
front=rear=-1;
cout<<adjlist[v].vertex;
visited[v]=1;
Q[++rear]=v;
while (front!=rear){
v=Q[++front];
p=adjlist[v].firstedge;
while (p!=NULL){
j= p->adjvex;
if (visited[j]==0){
cout<<adjlist[j].vertex;
visited[j]=1;
Q[++rear]=j;
}
p=p->next;
}
}
}
十字链表:有向图的链式存储结构
本质是将在有向图的邻接表和逆邻接表中两次出现的同一条弧用一个结点表示 。
邻接多重表 :无向图的存储结构
边集数组:利用两个一维数组,一个数组存储顶点信息,另外一个数组存储边及其权。数组分量包含三个域:边所依附的两个顶点,权值。各边在数组中的次序可以任意。
空间复杂性:O(n+e)
寻找一条边:O(e)
适用性:对边依次进行处理的操作。(加边法求最小代价生成树)
图的存储结构的比较——邻接矩阵和邻接表
邻接矩阵:
空间性能:O(n2)
时间性能:O(n2)
适用范围:稠密图
邻接表:
空间性能:O(n+e)
时间性能:O(n+e)
适用范围:稀疏图