数据结构(17)--图的遍历DFS和BFS

参考书籍:数据结构(C语言版)严蔚敏吴伟民编著清华大学出版社

本文中的代码可从这里下载:https://github.com/qingyujean/data-structure

    从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次。这一过程就叫做图的遍历

示例:

数据结构(17)--图的遍历DFS和BFS_第1张图片

1.深度优先遍历

基本思想:
    从图中某顶点V0出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到;
    若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点;
    重复上述过程,直至图中所有顶点都被访问到为止。

分析:

    在遍历图时,对图中每个顶点至多调用一次DFS函数,因为一旦某个顶点被标志成已被访问,就不再从它出发进行搜索。
    因此,遍历图的过程实质上是对每个顶点查找其邻接点的过程。其耗费的时间则取决于所采用的存储结构。 当使用二维数组表示邻接矩阵作图的存储结构时,查找每个顶点的邻接点所需时间为O(n^2),其中n为顶点数。而当以邻接表作图的存储结构时,找邻接点所需时间为O(e),其中e为无向图中边的数目或有向图中弧的数目。由此,当以邻接表作存储结构时,深度优先搜遍遍历图的时间复杂度为O(n+e)

代码实现:

 

int visited[MAX_VERTEX_NUM];//访问标识数组
void DFS(ALGraph G, int ivex){
	//从第i个顶点出发递归的深度优先遍历图G
	visited[ivex] = 1;
	printf("v%d ", G.vexs[ivex].data);//打印(访问)该顶点
	for(ArcNode *p = G.vexs[ivex].firstarc; p; p = p->nextarc){//对于第ivex个顶点的每个未被访问的邻接点递归调用DFS
		if(!visited[p->adjvex]){
			DFS(G, p->adjvex);
		}
	}
}
//深度优先遍历无向图G(相当于树的先序遍历)(递归算法)
void DFSTraverseGraph(ALGraph G){
	//初始化访问标志数组
	for(int i = 0; i < G.vexnum; i++){
		visited[i] = 0;//0表示未被访问,1表示已被访问
	}
	printf("请输入遍历的起始顶点(如:v1):");
	VertexType startVex;
	scanf("v%d", &startVex);
	int startVexPos = locateVex(G, startVex);
	printf("一条深度优先遍历序列为:");
	if(!visited[startVexPos])
		DFS(G, startVexPos);
	printf("\n");
	/*
	for(i = 0; i < G.vexnum; i++){//图中每个顶点至多调用一次DFS函数
		if(!visited[i]){//对还未访问过的顶点调用DFS
			DFS(G, i);
		}
	}
	*/
}
//深度优先遍历无向图G(相当于树的先序遍历)(非递归算法)
void DFSTraverseGraph2(ALGraph G){
	int stack[MAX_VERTEX_NUM];//维护一个栈来存储访问图的顶点的(位置)信息
	int top = 0;//初始化栈顶指针,为空栈

	//初始化访问标志数组
	for(int i = 0; i < G.vexnum; i++){
		visited[i] = 0;//0表示未被访问,1表示已被访问
	}
	printf("请输入遍历的起始顶点(如:v1):");
	VertexType startVex;
	scanf("v%d", &startVex);
	int startVexPos = locateVex(G, startVex);
	printf("一条深度优先遍历序列为:");

	ArcNode *p;// = G.vexs[startVexPos].firstarc;
	int ivex = startVexPos;
	while(!visited[ivex] || top!=-1){//栈不为空
		if(!visited[ivex]){//第vex结点没有被访问过
			visited[ivex] = 1;
			printf("v%d ", G.vexs[ivex].data);
			stack[top++] = ivex;
		}
			
		p = G.vexs[ivex].firstarc;
		while(p && visited[p->adjvex])//p不为空且p已经被访问过,就跳过
			p = p->nextarc;
		//此时p指向以当前顶点为头的且未被访问第一个尾顶点
		if(p){//如果p不为空
			ivex = p->adjvex;
		}else{//如果p为空,说明当前顶点的所有和他有路径相通的顶点均已访问,则栈顶元素出栈,查找下一个尚未被访问的顶点
			ivex = stack[--top];//栈顶元素出栈
		}
	}
	printf("\n");
}

 

2.广度优先遍历

 

基本思想:
    从图中某个顶点V0出发,并在访问此顶点后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路径相通的顶点都被访问到;
    若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点;
    重复上述过程,直至图中所有顶点都被访问到为止。

分析:
    每个顶点至多进一次队列。遍历图的过程实质上是通过边或弧找邻接点的过程,因此广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同,两者不同之处仅仅在于对顶点访问的顺序不同。 

代码实现:

 

int visited[MAX_VERTEX_NUM];//访问标识数组

 

 

//广度优先遍历无向图G(相当于树的按层次遍历)(非递归算法)
void BFSTraverseGraph(ALGraph G){
	int queue[MAX_VERTEX_NUM];//维护一个队列来存储访问图的顶点的(位置)信息
	int front = 0, rail = 0;//初始化队头、队尾指针,为空队列

	//初始化访问标志数组
	for(int i = 0; i < G.vexnum; i++){
		visited[i] = 0;//0表示未被访问,1表示已被访问
	}

	printf("请输入遍历的起始顶点(如:v1):");
	VertexType startVex;
	scanf("v%d", &startVex);
	int startVexPos = locateVex(G, startVex);
	printf("一条广度优先遍历序列为:");

	queue[rail++] = startVexPos;//起点先入队
	int ivex;// = startVexPos;
	ArcNode *p;
	while(front != rail){//不是空队列
		ivex = queue[front++];//队头元素出队
		if(!visited[ivex]){
			visited[ivex] = 1;
			printf("v%d ", G.vexs[ivex].data);
		}
		p = G.vexs[ivex].firstarc;
		while(p){//p指向与ivex的邻接的(同一个层次的)还未被顶点
			if(!visited[p->adjvex])
				queue[rail++] = p->adjvex;//入队
			p = p->nextarc;
		}
	}	
	printf("\n");	
}

 

3.演示

 

 

//以无向图的邻接表作为存储结构,实现图深度优先遍历算法
#include
#include
/*
图的表示方法
DG(有向图)或者DN(有向网):邻接矩阵、邻接表(逆邻接表--为求入度)、十字链表
UDG(无向图)或者UDN(无向网):邻接矩阵、邻接表、邻接多重表
*/
#define MAX_VERTEX_NUM 10//最大顶点数目
#define NULL 0
//typedef int VRType;//对于带权图或网,则为相应权值
typedef int VertexType;//顶点类型
//typedef enum GraphKind {DG, DN, UDG, UDN};  //有向图:0,有向网:1,无向图:2,无向

typedef struct ArcNode{	
	int adjvex;//该弧所指向的顶点的在图中位置
	//VRType w;//弧的相应权值
	struct ArcNode *nextarc;//指向下一条边的指针
}ArcNode;//弧结点信息

typedef struct VNode{
	VertexType data;//顶点信息
	ArcNode *firstarc;//指向第一条依附该顶点的弧的指针
}VNode, AdjVexList[MAX_VERTEX_NUM];//顶点结点信息

typedef struct{
	AdjVexList vexs;//顶点向量
	int vexnum, arcnum;//图的当前顶点数和弧数
	//GraphKind kind;//图的种类标志
}ALGraph;//邻接表表示的图


//若图G中存在顶点v,则返回v在图中的位置信息,否则返回其他信息
int locateVex(ALGraph G, VertexType v){
	for(int i = 0; i < G.vexnum; i++){
		if(G.vexs[i].data == v)
			return i;
	}
	return -1;//图中没有该顶点
}


//采用邻接表表示法构造无向图G
void createUDN(ALGraph &G){
	printf("输入顶点数和弧数如:(5,3):");
	scanf("%d,%d", &G.vexnum, &G.arcnum);

	//构造顶点向量,并初始化
	printf("输入%d个顶点(以空格隔开如:v1 v2 v3):", G.vexnum);
	getchar();//吃掉换行符
	for(int m = 0; m < G.vexnum; m++){
		scanf("v%d", &G.vexs[m].data);
		G.vexs[m].firstarc = NULL;//初始化为空指针////////////////重要!!!
		getchar();//吃掉空格符
	}

	//构造邻接表
	VertexType v1, v2;//分别是一条弧的弧尾和弧头(起点和终点)
	//VRType w;//对于无权图或网,用0或1表示相邻否;对于带权图或网,则为相应权值	
	printf("\n每行输入一条弧依附的顶点(先弧尾后弧头)(如:v1v2):\n");
	fflush(stdin);//清除残余后,后面再读入时不会出错
	int i = 0, j = 0;
	for(int k = 0; k < G.arcnum; k++){
		scanf("v%dv%d",&v1, &v2);
		fflush(stdin);//清除残余后,后面再读入时不会出错
		i = locateVex(G, v1);//弧起点
		j = locateVex(G, v2);//弧终点
		
		//采用“头插法”在各个顶点的弧链头部插入弧结点
		ArcNode *p1 = (ArcNode *)malloc(sizeof(ArcNode));//构造一个弧结点,作为弧vivj的弧头(终点)
		p1->adjvex = j;
		//p1->w = w;
		p1->nextarc = G.vexs[i].firstarc;
		G.vexs[i].firstarc = p1;
		ArcNode *p2 = (ArcNode *)malloc(sizeof(ArcNode));//构造一个弧结点,作为弧vivj的弧尾(起点)
		p2->adjvex = i;
		//p2->w = w;
		p2->nextarc = G.vexs[j].firstarc;
		G.vexs[j].firstarc = p2;
	}
}
/*测试:8,9
v1 v2 v3 v4 v5 v6 v7 v8 v9

v1v2
v1v3

v2v4
v2v5

v3v6
v3v7

v4v8
v5v8
v6v7
*/
void main(){
	ALGraph G;
	createUDN(G);
	//printAdjList(G);

	printf("\n深度优先遍历(递归算法):\n");
	DFSTraverseGraph(G);
	fflush(stdin);//清除残余后,后面再读入时不会出错

	printf("\n深度优先遍历(非递归算法):\n");
	DFSTraverseGraph2(G);
	fflush(stdin);//清除残余后,后面再读入时不会出错

	printf("\n广度优先遍历(非递归算法):\n");
	BFSTraverseGraph(G);
}

数据结构(17)--图的遍历DFS和BFS_第2张图片

 

 

 

 

 

 

 

你可能感兴趣的:(数据结构,算法+code,python)