文章目录
图的定义和术语
图
:
G
r
a
p
h
=
(
V
,
E
)
Graph=(V,E)
Graph=(V,E)
V:顶点(数据元素)的有穷非空集合;
E:边的有穷集合。
在图G中,如果代表边的顶点对是无序的,则称G为无向图。用圆括号序偶表示无向边。如果表示边的顶点对是有序的,则称G为有向图。用尖括号序偶表示有向边。
端点和邻接点
无向图:若存在一条边(i,j),则顶点i和顶点j为端点,它们互为邻接点。
有向图:若存在一条边<i,j>,则顶点i为起始端点(简称为起点),顶点j为终止端点(简称终点),它们互为邻接点。
关联(依附)
存在
(
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。
顶点v的度TD(v)
、入度ID(v)
和出度OD(v)
无向图:以顶点 i 为端点的边数称为该顶点的度。
有向图:以顶点i为终点的入边的数目,称为该顶点的入度。以顶点i为始点的出边的数目,称为该顶点的出度。一个顶点的入度与出度的和为该顶点的度。
完全图
无向图:每两个顶点之间都存在着一条边,称为完全无向图, 包含有n(n-1)/2条边。
有向图:每两个顶点之间都存在着方向相反的两条边,称为完全有向图,包含有n(n-1)条边。
子图
设有两个图G=(V,E)和G’=(V’,E’),若V’是V的子集,且E’是E的子集,则称G’是G的子图。
路径长度
是指一条路径上经过的边的数目。
若一条路径上除开始点和结束点可以相同外,其余顶点均不相同,则称此路径为简单路径
。
若一条路径上的开始点与结束点为同一个顶点,则此路径被称为回路或环
。开始点与结束点相同的简单路径被称为简单回路或简单环
。
连通、连通图和连通分量(无向图)
连通:若从顶点i到顶点j有路径,则称顶点i和j是连通的。
连通图:若图中任意两个顶点都连通,则称为连通图,否则称为非连通图。
连通分量:无向图G中的极大连通子图称为G的连通分量。
极大连通子图:该子图是 G 连通子图,将G 的任何不在该子图中的顶点加入,子图不再连通。
强连通图和强连通分量(有向图)
连通:若从顶点i到顶点j有路径,则称从顶点i到j是连通的。
强连通图:若图G中的任意两个顶点i和j都连通,即从顶点i到j和从顶点j到i都存在路径,则称图G是强连通图。
图的存储结构
邻接矩阵表示法
设G=(V,E)是具有n(n>0)个顶点的图,顶点的编号依次为0~n-1。
G的邻接矩阵A是n阶方阵,定义为:
A
[
i
]
[
j
]
=
{
1
,
如果
<
i
,
j
>
∈
E
或者
(
i
,
j
)
∈
E
0
,
否则
A [i][j]=\left\{\begin{array}{ll}1, & \text { 如果 }<i, j>\in E \text { 或者 }(i, j) \in E \\ 0, & \text { 否则 }\end{array}\right.
A[i][j]={1,0, 如果 <i,j>∈E 或者 (i,j)∈E 否则
分析1:无向图的邻接矩阵
是对称的;
分析2:顶点i 的度=第 i 行 (列) 中1 的个数;
特别:完全图的邻接矩阵中,对角元素为0,其余1。
有向图的邻接矩阵
,
第i行含义:以结点vi为尾的弧(即顶点i的出边);
第i列含义:以结点vi为头的弧(即顶点i的入边)。
分析1:有向图的邻接矩阵不一定是对称的。
分析2:顶点i的出度 = 第i行元素之和
顶点i的入度 = 第i列元素之和
顶点i的度 = 第i行元素之和+第i列元素之和
邻接表表示法
对图中每个顶点i建立一个单链表,将顶点i的所有邻接点链起来。有向图的邻接表可以只存出边或只存入边。
邻接矩阵与邻接表表示法的关系
- 联系:邻接表中每个链表对应于邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数。
- 区别
① 对于任一确定的无向图,邻接矩阵是唯一的(行列号与顶点编号一致),但邻接表不唯一(链接次序与顶点编号无关)。
② 邻接矩阵的空间复杂度为O(n2),而邻接表的空间复杂度为O(n+e)。 - 用途:邻接矩阵多用于稠密图;而邻接表多用于稀疏图
图的遍历
深度优先搜索( DFS ——Depth First Search)
深度优先遍历过程:
- 从图中某个初始顶点v出发,首先访问初始顶点v。
- 依次从所有与顶点v相邻且没被访问过的顶点w出发进行深度优先搜索。
基于邻接矩阵的DFS算法
void DFS(MGraph G, int v)
{ // 以顶点v为起点深度优先遍历图G,图G采用邻接矩阵存储
cout<<v; visited[v] = 1; //访问第v个顶点
for(int w = 0; w< G.vexnum; w++) //依次检查邻接矩阵v所在的行
if((G.arcs[v][w]!=0) && (!visited[w])) //w是v的邻接点,如果w未访问,则递归调用DFS
DFS(G, w);
}
基于邻接表的DFS算法的实现
void DFS(ALGraph G, int v)
{ //以顶点v为起点深度优先遍历图G,图G为邻接表类型
cout<<v; visited[v] = 1; //访问第v个顶点
ArcNode *p= G.adjlist[v].firstarc; //p指向v的边链表的第一个边结点
while(p!=NULL) //边结点非空
{
w=p->adjvex; //表示w是v的邻接点
if(!visited[w])
DFS(G, w); //如果w未访问,则递归调用DFS
p=p->nextarc; //p指向下一个边结点
}
}
用邻接矩阵来表示图,遍历图中每一个顶点都要从头扫描该顶点所在行,时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
用邻接表来表示图,虽然有 2e 个表结点,但只需扫描 e 个结点即可完成遍历,加上访问 n个头结点的时间,时间复杂度为O(n+e)。
广度优先搜索( BFS——Breadth First Search)
广度优先遍历的过程:
- 访问初始点v,v入队;
- 出队一个节点记为w,访问w的每个临节点p,将p入队;
- 若队列为空,退出,否则返回步骤2;
基于邻接表的BFS算法的实现
void BFS(ALGraph G,int v)
{
int w, i;
ArcNode *p;
SqQueue *qu; //定义环形队列指针
InitQueue(qu); //初始化队列
int visited[MAXV]; //定义顶点访问标记数组
for (i=0;i<G.vexnum;i++)
visited[i]=0; //访问标记数组初始化
printf("%2d",v); //输出被访问顶点的编号
visited[v]=1; //置已访问标记
enQueue(qu,v);
while (!QueueEmpty(qu)) //队不空循环
{ deQueue(qu,w); //出队一个顶点w
p=G.adjlist[w].firstarc; //指向w的第一个邻接点
while (p!=NULL) //查找w的所有邻接点
{ if (visited[p->adjvex]==0) //若当前邻接点未被访问
{ printf("%2d",p->adjvex); //访问该邻接点
visited[p->adjvex]=1; //置已访问标记
enQueue(qu,p->adjvex); //该顶点进队
}
p=p->nextarc; //找下一个邻接点
}
}
printf("\n");
}