图的基本操作与遍历

目录

一、操作概览:

二、操作详解

三、广度优先算法

(1)和树的广度优先遍历是很相似的

(2)要点:

(3)代码

(4)非连通图处理

(5)效率分析:

(6)广度优先生成树

(7)广度优先生成森林

小结:

四、图的深度优先遍历

(1)代码:

(2)对非连通图的处理

(3)空间复杂度

(4)深度优先生成树和森林

(5)图的遍历与图的连通性

小结:

下一篇:最小生成树


一、操作概览:

这里只探讨邻接矩阵与邻接表怎么实现这些操作

二、操作详解

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){//从顶点出发DFSG

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次调用即可

 

小结:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值