图的基本概念和存储结构
回顾:
图的基本概念
- 图的定义和术语
图:G=(V,E)
V:顶点(数据元素)的有穷非空集合
E:边的有穷集合
无向图——每条边都是无方向的
有向图——每条边都是有方向的
完全图——任意两个点都有一条边相连
无向完全图——n个顶点,有
n
(
n
−
1
)
/
2
n(n-1)/2
n(n−1)/2条边
有向完全图——n个顶点,有
n
(
n
−
1
)
n(n-1)
n(n−1)条边
稀疏图——有很少边的弧的图(e<nlogn)
稠密图——有较多边或弧的图
网——边/弧带权的图
邻接——有边/弧相连的两个顶点之间的关系。存在
(
v
i
,
v
j
)
(v_i,v_j)
(vi,vj),则称
v
i
v_i
vi和
v
j
v_j
vj互为邻接点;存在
<
v
i
,
v
j
>
<v_i,v_j>
<vi,vj>,则称
v
i
v_i
vi邻接到
v
j
v_j
vj,
v
j
v_j
vj邻接于
v
i
v_i
vi.
关联(依附)——边/弧与顶点之间的关系。存在
(
v
i
,
v
j
)
/
<
v
i
,
v
j
>
(v_i,v_j)/<v_i,v_j>
(vi,vj)/<vi,vj>,则称该边/弧关联于
v
i
v_i
vi和
v
j
v_j
vj.
顶点的度——与该点相关联的边的数目,记为TD(v)
在有向图中,顶点的度等于该顶点的入度与出度之和。
顶点v的入度是以v为终点的有向边的条数,记作ID(v)
顶点v的出度是以v为始点的有向边的条数,记作OD(v)
路径——接续的边构成的顶点序列
路径长度——路径上边或弧的数目/权值之和。
回路(环)——第一个顶点和最后一个顶点相同的路径
简单路径——除路径起点和终点可以相同外,其余顶点均不相同的路径
简单回路(简单环)——除路径起点和终点相同外,其余顶点均不相同的路径。
连通图(强连通图)——在无(有)向图G=(V,{E})中,若对任何两个顶点v,u都存在从v到u的路径,则称G是连通图(强连通图)。
权与网——图中边或所具有的相关数称为权。表明从一个顶点到另一个顶点的距离或耗费。带权的图称为网。
子图——设有两个G=(V,{E})、G1=(V1,{E1}),若V1
⊆
\subseteq
⊆V,E1
⊆
\subseteq
⊆E,则称G1是G的子图。
连通分量(强联通分量)
- 无向图G的极大连通子图称为G的连通分量。
极大连通子图意思是:该子图是G连通子图,将G的任何不在该子图中的顶点加入,子图不在连通。 - 有向图G的极大强连通子图称为G的强连通分量
极大强连通子图意思是:该子图是G的强连通子图,将D的任何不在该子图中的顶点加入,子图不在是强连通的
极小连通子图——该子图是G的连通子图,在该子图中删除任何一条边的子图不在连通。
生成树——包含无向图G所有顶点的极小连通子图。
生成森林——对非连通图,由各个连通分量的生成树的集合。
图的存储结构
图的逻辑关系——多对多
图没有顺序存储结构,但可以借助二维数组来表示元素间的关系——邻接矩阵
链式存储结构——多重链表(邻接表、邻接多重表、十字链表)
邻接矩阵的存储方法
- 建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间的关系)。
设图A=(V,E)有n个顶点,则顶点表Vexs[n]
i | 0 | 1 | 2 | … | n-1 |
---|---|---|---|---|---|
Vexs[i] | v 1 v_1 v1 | v 2 v_2 v2 | v 3 v_3 v3 | … | V n V_n Vn |
图的邻接矩阵是一个二维数组A.arcs[n][n],定义为:
A . a r c s [ i ] [ j ] = { 1 , 如 果 < i , j > ∈ E 或 者 ( i , j ) ∈ E 0 , 否 则 A.arcs[i][j] = \begin{cases} 1, & 如果 < i,j> \in E 或者 (i,j) \in E \\ 0, & 否则 \\ \end{cases} A.arcs[i][j]={1,0,如果<i,j>∈E或者(i,j)∈E否则
- 例子:
无向图的邻接矩阵:
有向图的邻接矩阵
网的邻接矩阵
A . a r c s [ i ] [ j ] = { W i , j , 如 果 < i , j > 或 者 ( i , j ) ∈ V R ∞ , 无 边 ( 弧 ) A.arcs[i][j] = \begin{cases} W_{i,j}, & 如果 <i,j> 或者 (i,j) \in VR \\ \infty, & 无边(弧) \\ \end{cases} A.arcs[i][j]={Wi,j,∞,如果<i,j>或者(i,j)∈VR无边(弧)
- 邻接矩阵的存储表示——用两个数组分别存储顶点表和邻接矩阵
#define MaxInt 32767 //表示极大值
#define MVNum 100 //最大顶点数
typedef char VerTexType; //顶点的数据类型定义
typedef int ArcType; //权值数据类型定义
typedef struct {
VerTexType vexs[MVNum]; //顶点表
ArcType arc[MVNum][MVNum]; //邻接矩阵
int vexnum,arcnum; //图的当前点数和边数
}AMGraph;
- 采用邻接矩阵表示法创建无向网
无 向 网 { 无 向 图 有 向 图 有 向 网 无向网 \begin{cases} 无向图 \\ 有向图 \\ 有向网 \\ \end{cases} 无向网⎩⎪⎨⎪⎧无向图有向图有向网
算法思想:
1.输入总顶点数和总边数。
2.依次输入点的信息存入顶点表中。——vexs[i]
3.初始化邻接矩阵,使每个权值初始化为极大值。
4.构造邻接矩阵。
//在图中查找顶点
int LocateVex(AMGraph G,VertexType u){
//在图中查找顶点u,存在则返回顶点表中的下标;否则返回-1
int i;
for(i=0;i<G.vexnum;++i)
if(u==G.vexs[i]) return i;
return -1;
}
//采用邻接矩阵表示法,创建无向网
Status CreateUDN(AMGraph &G){
cin>>G.vexnum>>G.arcnum; //输入总顶点数,总边数
for(int i=0;i<G.vexnum;i++)cin>>G.vexs[i]; //依次输入点的信息
for(int i=0;i<G.vexnum;i++) // 初始化邻接矩阵
for(int j=0;j<G.arcnum;j++)
G.arcs[i][j]=MaxInt; //边的权值均置为最大值
for(int k=0;k<G.arcnum;++k){
cin>>v1>>v2>>w; //输入一条边所依附的顶点以及边的权值
i=LocateVex(G,v1);
j=LocateVex(G,v2); //确定v1和v2在G中的位置
G.arcs[i][j]=w; //边<v1,v2>的权值置为w
G.arcs[j][i]=G.arcs[i][j]; //置<v1,v2>的对称边<v2,v1>的权值为w
}
return OK;
} //CreateUDN
-
邻接矩阵的优缺点
-
优点:
1.直观、简单、好理解
2.方便检查任意一对顶点间是否存在边
3.方便找任一顶点的所有“邻接点”(有边直接相连的顶点)
4.方便计算任一顶点的“度”(从该点发出的边数为“出度”,指向该点的边数为入度)- 无向图:对应行(或列)非0元素的个数树“出度”
- 有向图:对应行非0元素的个数是“出度”;对应列非0元素的个数是“入度”
-
缺点:
1.不便于增加和删除顶点
2.浪费空间——存稀疏图时有大量的无效元素
3.浪费时间——统计稀疏图中一共有多少条边
邻接表的存储方法
- 邻接表的表示方法(链式)
- 顶点——按编号顺序将1顶点数据存储在一维数组中;
- 关联同一顶点的边(以顶点为尾的弧):
用线性链表存储
- 无向图的邻接表
- 特点
1.邻接表不唯一
2.若无向图中有n个顶点、e条边,则其邻接表需n个头结点和2e个表结点。适宜存储稀疏图
3.无向图中顶点 v i v_i vi的度为第 i i i个单链表中的结点数
- 有向图的邻接表
- 特点
1.顶点 v i v_i vi的出度为第 i i i个单链表中的结点个数
2.顶点 v i v_i vi的入度为整个单链表中邻接点域值时 i − 1 i-1 i−1的结点个数
邻接表:——存储出度边
找出度易,找入度难
逆邻接表:——储存入度边
找入度易,找出度难
-
图的邻接表存储表示:
-
图的结点类型定义
-
顶点结点结构
∣ d a t a ∣ f i r s t a r c ∣ |data|firstarc| ∣data∣firstarc∣
typedef struct VNode{
VerTexType data; //顶点信息
ArcNode *firstarc; //指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum]; //AdjList表示邻接表类型
- 弧(边)结点结构
∣ a d j v e x ∣ n e x t a r c ∣ i n f o ∣ |adjvex|nextarc|info| ∣adjvex∣nextarc∣info∣
#define MVNum 100 //最大顶点个数
typedef struct ArcNode{ //边结点
int adjvex; //该边所指向的顶点的位置
struct ArcNode *nextarc; //指向下一条边的指针
OtherInfo info; //和边相关的信息
}ArcNode;
- 图的结构定义
∣ 顶 点 ∣ 弧 ( 边 ) ∣ |顶点|弧(边)| ∣顶点∣弧(边)∣
typedef struct {
AdjList vertices; // vertices--vertex的复数
int vexnum,arcnum; // 图的当前顶点数和弧度数
}ALGraph;
-
采用邻接表表示法创建无向网
-
算法思想
1.输入总顶点数和总边数
2.建立顶点表
依次输入点的信息存入顶点表中,使每个表头结点的指针域初始化为NULL
3.创建邻接表- 依次输入每条边依附的两个顶点;
- 确定两个顶点的序号i和j,建立边结点;
- 将此边结点分别插入到 v i v_i vi和 v j v_j vj对应的两个边链表的头部
//采用邻接表表示法,创建无向图
Status CreateUDG(ALGraph &G){
cin>>G.vexnum>>G.arcnum; //输入总顶点数、总边数
for(int i=0;i<G.vexnum;++i){ //输入各点,构造表头结点表
cin>>G.vexnum[i].data; //输入顶点数
G.vertices[i].firstarc=NULL; //初始化表头结点的指针域
}
for(int k=0;k<G.arcnum;++k){ //输入各边,构造邻接表
cin>>v1>>v2; // 输入一条边依附的两个顶点
i=LocateVex(G,v1);
j=LocateVex(G,v2);
//出度边
p1=new ArcNode; //生成一个新结点*p1
p1->adjvex=j; //邻接点序号为j
p1->nextarc=G.vertices[i].firstarc; //
G.vertices[i].firstarc=p1; //将新结点*p1插入顶点vi的边表头部
//入度边
p2=new ArcNode; //生成另一个对称的新的边结点*p
p2->adjvex=i; //邻接点序号为i
p2->nextarc=G.vertices[j].firstarc;
G.vertices[j].fistarc=p2; //将新结点*p2插入顶点vj的边表头部
}
return OK;
}
- 邻接表特点
1.方便找任意顶点的所有“邻接点”
2.节约稀疏图的空间
3.对于无向图方便计算出度和入度,无向图分情况讨论(邻接表:方便计算出度;逆邻接表方便计算入度)
4.不方便检查任意一对顶点间是否存在边
邻接矩阵和邻接表的关系
-
联系——邻接表中每个链表对应于邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数。
-
区别:
1.对于任一确定的无向图,邻接矩阵是唯一确定的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)。
2.邻接矩阵的空间复杂度为 O ( n 2 ) O(n^2) O(n2);而邻接表的空间复杂度为 O ( n + e ) O(n+e) O(n+e)。 -
用途——邻接矩阵多用于稠密图;而邻接表多用于稀疏图。
十字链表
邻接表和逆邻接表的结合
例子:
邻接多重表
无向图只存储一条边
例子: