图之深度优先遍历

什么叫深度优先遍历 

就是说,怎么遍历一个图结构,那么这里就有前辈给我们提供了一个思路,叫深度优先搜索,也就是DFS(Depth First Search)。

它的核心思想是:DFS在回溯之前会尽可能深入探索分支,从根或者任意结点开始,开始调用递归函数,并将结点标记为已访问,对于每一个相邻的子结点,如果尚未访问,就会重复该过程

分析过程 

它的思路:假设我们这里的图就是一张树的结构

其实对于一张图的深度遍历,还是采用的递归的思想来做:

 具体的递归分析过程 

 话不多说,直接上代码,下面就是对于一个无向图的邻接矩阵的实现遍历

undirected_graph.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//最大顶点数
#define MAX_VERTEX 30
//设置一个标识顶点是否被访问的数组
int visit[MAX_VERTEX];

//定义图结构
typedef struct _graph
{
	//顶点数组,存储顶点名字
	char **vertex;
	//边的数组
	int edge[MAX_VERTEX][MAX_VERTEX];
	//顶点个数
	int vertex_num;
	//边的条数
	int edge_num;
}graph;

//算出用户输入的顶点在数组中的位置
//这里就是比如输入一个v1,v2,那么对于无向图来说,(v1,v2)与(v2,v1)都代表的是同一条边
int calc_vertex_pos(graph &g,char* v)
{
	//遍历顶点数组
	for(int i = 0;i < g.vertex_num;i++) {
		//这里相当于就是字符串比较
		if(strcmp(v,g.vertex[i]) == 0) {
			return i;//找到直接返回这个位置
		}
	}
	return -1;
}

//创建一个图
void create_graph(graph &g)
{
	printf("请输入图的顶点数目与边数目:顶点 边\n");
	scanf("%d %d",&g.vertex_num,&g.edge_num);
	printf("请输入%d个顶点值\n",g.vertex_num);

	//开始循环给顶点数组赋顶点值
	//在赋值之前需要在堆上开辟空间存放字符串
	g.vertex = (char**)malloc(sizeof(char*) * g.vertex_num);
	for(int i = 0;i < g.vertex_num;i++) {
		char str[100] = {0};
		scanf("%s",str);
		//这个数组又指向了分配的一个空间
		g.vertex[i] = (char*)malloc(sizeof(char) * (strlen(str) + 1));
		strcpy(g.vertex[i],str);
	}
	//把边的二维数组也给初始化一下
	//以顶点数n,形成一个n*n的二维数组
	for(int i = 0;i < g.vertex_num;i++) {
		for(int j = 0;j < g.vertex_num;j++) {
			//把对应的边全部初始化为0表示不存在
			g.edge[i][j] = 0;
		}
	}

	//上面二个数组内容给初始化好了
	//下面来设置边与边的关系,就是是否有边
	printf("请输入%d条边和与之对应的顶点1,顶点2\n",g.edge_num);
	//设置两个有边的顶点之间的关系
	char v1[10];
	char v2[10];
	//有多少条边,就对应了有多少个顶点
	for(int i = 0;i < g.edge_num;i++) {
		scanf("%s %s",v1,v2);
		//然后我们计算顶点在数组中的位置
		//是0啊,1啊,还是2啊等等这样的关系
		int m = calc_vertex_pos(g,v1);//比如v1=0
		int n = calc_vertex_pos(g,v2);//v2 = 1 (0,1)就表示有边,就要把这个位置值设为1
		//同时(1,0)这个位置也要设置为1,毕竟是无向图
		g.edge[m][n] = 1;
		g.edge[n][m] = 1;
	}
}

//打印一张图
void print_graph(graph& g)
{
	//打印图也就是打印这个二维数组
	printf("\t");
	//循环水平的顶点表头
	for(int i = 0;i < g.vertex_num;i++) {
		printf("%s\t",g.vertex[i]);
	}
	//循环二维数组内容
	for(int i = 0;i < g.vertex_num;i++) {
		//打印水平的顶点表头
		printf("\n");//每次换一个行
		printf("%s\t",g.vertex[i]);
		//然后输出一行的内容
		for(int j = 0;j < g.vertex_num;j++) {
			printf("%d\t",g.edge[i][j]);
		}
	}
	printf("\t");
}

//对一张图进行深度优先搜索
void DFS(graph &g,int index) 
{
	//把这个点的访问标记变为1
	visit[index] = 1;
	//然后打印这个点
	printf("%s ",g.vertex[index]);
	//然后一直往下遍历邻接点
	for(int i = 0;i < g.vertex_num;i++) {
		if(g.edge[index][i] == 1 && visit[i] != 1) {
			DFS(g,i);//表明这个点也是可以打印的
		}
	}
}

void DFSTraverse(graph &g) 
{
	//初始化顶点数组的被过滤标识
	for(int i = 0;i < g.vertex_num;i++) {
		visit[i] = 0;//全部没有被访问
	}
	//一次来遍历每一个顶点
	for(int j = 0;j < g.vertex_num;j++) {
		//判断一下这个点的标识
		if(visit[j] != 1) {
			DFS(g,j);
		}
	}
}

//构建图
void test01()
{
	graph g;
	create_graph(g);
	print_graph(g);
	printf("\n---------\n");
	DFSTraverse(g);
}

int main() 
{
	test01();
	return 0;			
}

运行结果:

好了,这就是邻接矩阵无向图的深度优先搜索,有向图也是一样的操作,这是不是就类似于一棵二叉树的前序遍历,但是这个只是对于这个邻接矩阵来说的。下面来看看邻接表表示的一张图怎么来遍历。

邻接表的分析

相当于是用指针来关联所有的点,每一个结点建立一个表头

直接上代码:

undirect_graph1.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//最多能存放的顶点数目
#define MAX_VERTEX 100

int visit[MAX_VERTEX];

struct edge_node {
	int position;//这个表示vertex数组中的哪一个结点索引
	edge_node *next;//存放下一个邻接点的指针
	//这里权重可写可不写
};

struct vertex {
	char *name;//存放顶点名字,一级指针指向一个堆上面分配的空间
	//还有存放每一个与之相邻的第一个邻接点,其实这里它就相当于是头结点
	edge_node *first_node;
};

//最后需要整个图的结构
//来存放相应图的信息
struct graph {
	//存放顶点的所有信息
	vertex head[MAX_VERTEX];
	//顶点数与边数
	int vertex_num;
	int edge_num;
};



//这里还是来确定一下顶点位置
int find_pos(graph &g,char *v)//这里v如果有指向就可以打印,我的意思就是scanf直接赋值是不行的
{
	for (int i = 0; i < g.vertex_num; i++) {
		//循环存放顶点名字的那一片空间
		if (strcmp(g.head[i].name, v) == 0) {
			return i;
		}
	}
	return -1;
}

//先来创建一张图
void create_graph(graph &g)
{
	printf("请输入顶点数与边数:\n");
	scanf("%d %d", &g.vertex_num, &g.edge_num);
	//循环输入顶点的值
	printf("请输入%d个顶点值:\n", g.vertex_num);
	//循环给顶点赋值
	for (int i = 0; i < g.vertex_num; i++){
		char str[30] = { 0 };
		scanf("%s", str);
		//单纯scanf("%s",&g.head[i].name)肯定不行,这个name事一个二级指针
		g.head[i].name = (char*)malloc(sizeof(char) * (strlen(str) + 1));
		strcpy(g.head[i].name, str);//在哪一个索引位置存放顶点
		//除了赋值字符串之外,还需要初始化一个邻接点的地址
		g.head[i].first_node = NULL;//类似于每一个头顶点的next全是NULL
	}

	//下面开始来输入边,也就是两个顶点
	printf("请输入%d条边,顶点1,顶点2", g.edge_num);
	char v1[10];
	char v2[10];
	//循环给边赋值
	for (int i = 0; i < g.edge_num; i++) {
		scanf("%s %s", v1,v2);
		int m = find_pos(g, v1);
		int n = find_pos(g, v2);
		//在内存中找到了每个顶点的编号
		//然后比如说m是不是就是代表某个顶点的头部,这里可以
		//当成链表的头部处理
		//然后我们实现头插,表示某种联系
		//最后我们实现关系的一个交互,这是对于无向图来说v1与v2和v2与v1的关系一样
		
		//创建一个新的邻接点
		edge_node *p_new = (edge_node*)malloc(sizeof(edge_node));
		//然后开始在头部插入,这个头部也就是m点
		p_new->position = n;
		//这里p_new必须先指
		p_new->next = g.head[m].first_node;
		g.head[m].first_node = p_new;

		//然后实现v0与v1的一个交替,也就是一个意思
		edge_node *p_new1 = (edge_node*)malloc(sizeof(edge_node));
		//其实就是实现一个m与n的交替
		p_new1->position = m;
		p_new1->next = g.head[n].first_node;
		g.head[n].first_node = p_new1;
	}
}


//打印这张图
void print_graph(graph &g)
{
	for (int i = 0; i < g.vertex_num; i++) {
		printf("%s: ", g.head[i].name);
		//拿到头部结点进行一个单链表的遍历
		edge_node *p_node =  g.head[i].first_node;
		while (p_node != NULL) {
			//拿到的是n,找它的m
			int index = p_node->position;
			printf("%s ", g.head[index].name);
			p_node = p_node->next;
		}
		//换行
		printf("\n");
	}
}

//邻接表的深度优先遍历
void DFS(graph &g,int index)
{
	//把这个标识变为1
	visit[index] = 1;
	printf("%s ",g.head[index].name);//打印顶点
	edge_node *p_node = g.head[index].first_node;
	//内部进行一个循环
	while(p_node != NULL) {
		int index = p_node->position;
		//判断这个点有没有被遍历过
		if(!visit[index]) {
			DFS(g,index);
		}
		p_node = p_node->next;
	}
}

void DFSTraverse(graph &g)
{
	//把顶点遍历标识全部初始化为0
	for(int i = 0;i < g.vertex_num;i++) {
		visit[i] = 0;
	}
	//然后开始遍历每一个顶点
	for(int j = 0;j < g.vertex_num;j++) {
		//没有被遍历过的顶点才会进来
		if(!visit[j]) {
			DFS(g,j);
		}
	}
}

int main()
{
	graph g;
	create_graph(g);
	print_graph(g);
	printf("\n---------------\n");
	DFSTraverse(g);
	return 0;
}

   运行结果:

为什么这个就不想前序遍历,是因为我们在用邻接表插入数据的时候,是选择头插,比如A B 存入之后,A C又进来,那么内存表示就死A C B ,所以你想前序遍历,输入 边的时候就可以把边变成A B C就可以了。

下面分析一下邻接矩阵与邻接表的这个大O,对于邻接矩阵来说,他在与从二维数组中寻找所有对应的边,因此它的时间复杂度在O(n^2),对于邻接表来说,在于顶点与边的一个数量,越多越耗时,所以它的时间复杂度就是O(n),相对来说还可以,特别是对于点多,边少的稀疏图,时间效率是非常高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值