目录
下一篇:最小生成树
一、操作概览:
这里只探讨邻接矩阵与邻接表怎么实现这些操作
二、操作详解
1、判断图是否有边<x,y>或(x,y)
对于无向图
Adjacent(G,x,y)
邻接矩阵:
O(1)的时间复杂度,只需要检查表G[x][y]==1即可
邻接表:
要查询其中一个顶点的链表中的结点是否与另一个顶点相连接,最好O(1),最坏O(|V|)
对于有向图是一样的
2、列出G中与结点X邻接的边
Neighbor(G,x)
无向图:
邻接矩阵:遍历对应顶点的那一行或一列有1就是邻接,0就是不临接,时间复杂度为O(|V|)
邻接表:则只需要遍历其邻接的链表即可,时间复杂度为O(1)到O(|V|),优于邻接矩阵
有向图:
邻接矩阵
找出边:遍历行
找入边:遍历列
O(|V|)
邻接表:
出边:遍历顶点链表O(1)到O(|V|)
入边:遍历所有的顶点,O(|E|)
在非稀疏图时显然矩阵更好
3、在图G中插入顶点X
InertVertex(G,x)
无向图:
邻接矩阵:在矩阵加入X的一行一列即可,因为这里仅仅是刚加入一个顶点,没有连任何的边所以时间复杂度为O(1)
邻接表:在存储顶点的数组的末尾加入一个新元素(顶点)即可,其first指针指向NULL(没有连任何的边)
有向图也是一样
4、从G中删除顶点X
DeleteVertex(G,x)
无向图:
邻接矩阵:
方案一:将该顶点所在的行和列全部删除(就像它的代数余子式一样)
缺点:移动大量元素不适用
方案二:在顶点的结构中加入一个bool型标记,表示该顶点是否为空,在删除时将该顶点的行和列情况然后标记置0(表示顶点为空)
缺点:多次删除后会产生很多空顶点
邻接表:
先删除表中的顶点和顶点所连的边结点,再遍历邻接表把每个顶点指向被删除顶点的边全删除
最好情况:该顶点没有连任何的边O(1)
最坏情况:该顶点连了尽可能多的边且,这些边都在顶点所连链表的最后一个位置,为O(|E|)
有向图:
邻接矩阵一样
邻接表:
删除逻辑和无向图一样,只是删除出边和入边的时间复杂度不同
删除出边:O(1)~O(|V|)
删除入边:O(|E|)
5、添加边
AddEdge(G,x,y):若无向边(x,y)或有向边<x,y>不存在,则向图中添加边
有向图和无向图都差不多
邻接矩阵:O(1)
邻接表:头插法O(1)效率高于尾插法
6、删除边
RemoveEdge(G,x,y)
有向和无向差不多
邻接矩阵将对应位置上的1清零即可O(1)
邻接矩阵删除对应的边结点O(1)~O(|V|)
7、找到顶点x的第一个邻接点,若有则返回顶点号,若x没有邻接点或不存在x则返回-1
无向图:
邻接矩阵:找到对应行的第一个1级可以了,O(1)~O(|V|)
邻接表:只用找到顶点中第一个边结点即可O(1)
有向图:
邻接矩阵:出边找行,入边找列O(1)~O(|V|)
邻接表:出边O(1),入边O(1)~O(|E|)
8、找下一邻接点
NextNeighbor:G中顶点y是x的一个邻接点,返回出了y外x的下一个邻接点,若y是最后一个则返回-1
有向图:
邻接矩阵:O(1)~O(|V|),最好就是第一个1后面立马是1
邻接表:O(1)连着找两个就可以了
无向图类似
9、Get_edge_value(G,x,y) 获取对应权值
10、Set_edge_value(G,x,y)设置权值
9和10与找边一样
三、广度优先算法
(1)和树的广度优先遍历是很相似的
与树的广度优先遍历的区别:
1、树无环路,图有环路,解决方法:结点打上标记
2、树可以方便的找到下一个结点,图可以利用上面的基本操作完成
(2)要点:
1、找到与一个顶点相邻的所有顶点
通过FirstNeighbor和NextNeighbor方法
2、标记哪些顶点被访问过
3、需要一个辅助队列
(3)代码
//bfs代码实现,省略了图的存储结构
bool visited[MAX_VERTEX_NUM];//全局变量初始为0也就是FALSE
//bfs
void BFS(Graph G,int v){//begin v
visit(v);//访问初始顶点v
visited[v]=TRUE;//v标记为已访问
Enqueue(Q,v);//v入队
while(!isEmpty(Q))
{
DeQueue(Q,v);//v出队(把队首顶点赋值给v)
//初始时把将v的第一个邻接顶点给w,判断w是否被访问过
//若被访问过则使用NextNeighbor方法访问下一个邻接顶点
//若没有下一个顶点这两个方法返回-1循环停止,进入下一个while循环
//直到队列为空
for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
if(!visited[v]){//若没访问过
visit(w);
visited[w]=1;//标记为访问过
EnQueue(Q,w);//w入队
}
}
}
}
注意:邻接矩阵得到的遍历序列是唯一的,邻接表由于边的存储顺序可能不同得到不同的遍历序列
(4)非连通图处理
void BFSTraverse(Graph G){
//访问数组初始化
for(int i=0;i<G.vexnum;i++){
visited[i]=FALSE;
}
//初始化辅助队列
InitQueue(Q);
//遍历所有顶点,若存在没有遍历过的顶点就对该顶点调用一次BFS
for(int i=0;i<G.vexnum;i++){
if(!visited[i]){
BFS(G,i);
}
}
}
调用BFS次数为极大连通子图的(连通分量)数量
(5)效率分析:
对于邻接矩阵:
空间复杂度(主要是使用队列的辅助空间):
最坏:
为O(|V|)
时间复杂度:
一共|V|个顶点,确定邻接的顶点数要查找|V|次,所以为O(|V|^2),其中去掉了访问|V|个顶点的时间
邻接表:根据邻接表的特点时间复杂度为O(|V|+|E|)
(6)广度优先生成树
根据BFS的过程生成一棵树
例子:其中标红的线就是生成树
由于邻接表边结点顺序的不唯一性可以得到不同的生成树,邻接举证就不唯一
(7)广度优先生成森林
由非连通图生成的树
小结:
四、图的深度优先遍历
类似于树的深度优先遍历,就是多了个标记数组
(1)代码:
bool visited[MAX_VERTEX_NUM];//标记数组
void DFS(Graph g,int v){//从顶点出发DFS图G
visit(v);//访问顶点v
visited[v]=TRUE;//标记为已访问
for(w=FirstNeighbor(G,v);w>=0;NextNeighbor(G,w)){
if(!visited[w]){//若为访问
DFS(G,W);
}
}
}
执行演示:
从顶点2出发
之后就是正常回溯了
(2)对非连通图的处理
void DFSTraverse(Gragh G){
for(v=0;v<G.vexnum;v++){
visited[v]=FALSE;
}
//从顶点0开始遍历
for(v=0;v<G.vexnum;v++){
if(!visited[v]){
DFS(G,v);
}
}
}
(3)空间复杂度
主要是递归调用深度
最坏为O(|V|)
最好为
题目中若没有说明就是最坏复杂度
时间复杂度
可以简化为访问和查找邻接顶点的时间复杂度,于是和BFS是差不多的
邻接矩阵O(|V|^2)
邻接表O(|V|+|E|)
(4)深度优先生成树和森林
和广度优先生成树是差不多的,就将图中,把遍历过程标出来,去掉其他的边,同样有邻接表不唯一的问题
森林同理
(5)图的遍历与图的连通性
对无向图进行BFS/DFS遍历调用函数的次数=连通分量数
对于连通图只用调用1次
对于有向图:要对具体情况具体分析
若是个强连通图则只需要1次调用即可
小结: