一. 图的广度优先遍历
图的广度优先遍历可以联系二叉树的层序遍历,其基本思想是:首先,访问起始节点v,之后,访问与起始节点v相连的各个未被访问过的节点w1,w2,...,wi,之后,再对这些节点,进行上面的操作。
在分析其算法的时间和空间开销之前,我们必须知道算法的实现流程:
广度优先遍历是一种分层查找的过程,与深度优先遍历的不同之处在于,它不需要有退回的情况,因此,它不是一个递归的算法。但是,为了实现逐层的访问,我们必须借助辅助队列来实现
void BFS(Graph G,int v){//从顶点v出发,广度优先遍历图G
visit(v);
visited[v] = true;
EnQueue(Q,v);
while(!isEmpty(Q)){
DeQueue(Q,v);
for(w = FirstNeighbor(G,v);w>=0;w = NextNeighbor(G,v,w)){//不断找到与v节点相邻的节点
if(!visited[w]){
visit(w);
visited[w] = true;
EnQueue(Q,w);
}
}
}
void BFSTraverse(Graph G){
for(int i = 0;i<G.vexnum;i++){//初始化visit数组
visited[i] = false;
}
InitQueue(Q);
for(int i = 0;i<G.vexnum;i++){
if(!visited[i]){
BFS(G,i);
}
}
}
二. 图的常见存储模式
常见的图的存储方式有邻接矩阵存储和邻接表存储,邻接矩阵是用一维数组存储节点信息,用二维数组存储边的信息,时间复杂度为O(V2),而邻接表是采用顺序存储图的节点信息(顶点表),链式存储图的边的信息(边表),对于无向图的情况,由于一条边会增加节点的两个度,因此邻接表存储无向图的情况,边节点的个数为边数的两倍,即2E,加之有V个节点,因此邻接表存储的空间开销是O(V+2E)
三. 基于邻接矩阵和邻接表的查找
1. FirstNeighbor(G,v):查找节点v的第一个邻接点,对于无向图和有向图是相同的情况。第一,如果图是采取邻接矩阵的存储模式,那么最好的情况,就是遍历序列的第一个元素就符合情况,因此,最好情况下的时间复杂度为O(1),最坏的情况则是要遍历整个图的节点,此时的时间复杂度为O(n),而对于邻接表的存储,因为边节点直接体现了节点相连的节点,因此,通过邻接表查找第一个邻接点的时间开销为O(1)(对于有向图仅是查找出边的情况)
2. NextNeighbor(G,v,w):假设节点w是v的第一个邻接点,该函数返回除了w以外,与v相连的下一个邻接点,其时间和空间开销同上
四. BFS的复杂度分析:
1. 时间复杂度:
首先,我们需要确定BFS遍历的时间开销来源是什么,这是最重要的,BFS遍历的时间开销有二:访问节点+探索边。访问节点需要遍历整个图,因此时间复杂度为O(V),而遍历边,需要在二维数组中查看每一个节点与其相连的边。因此对于邻接矩阵存储模式,总的时间开销为O(V+V2),保留最大项,为O(V2),而对于邻接矩阵存储,遍历节点的时间复杂度为O(V),遍历边则直接遍历边节点即可,因此遍历边的时间复杂度为O(E),故总的时间复杂度为O(V+E)
2. 空间复杂度:
使用了辅助队列,n个节点均需要入队一次,故空间复杂度为O(V)