【算法】回溯:与递归,dfs的同质与分别,剪枝与恢复现场的详细理解,n皇后的回溯解法及算法复杂度分析。

0f39cc4c87a5417c80c0394d33d21531.gif

目录

​编辑

1.什么是回溯

2.关于剪枝

3.关于恢复现场

4.题目:二叉树的所有路径(凸显恢复现场:切实感受回溯与深搜)

问题分析

①函数设置为:void Dfs(root)

②函数设置为:void Dfs(root,path)

解题思想:使⽤深度优先遍历(DFS)求解。

代码实现

5.N后问题

问题分析

4皇后的放置方式

首先我们先在第一行进行落子:共有四种放置方式

接下来我们考虑往第二行的落子:

接下来我们考虑第三行落子:

接下来我们考虑第4行落子:

代码实现:

①对同列分析

②对于对角线的位置:

主对角线:

副对角线:

代码实现:

递归展开图

时间复杂度

空间复杂度

6.总结


1.什么是回溯

如果说递归是一个大的集合,搜索是递归的一个分支,如果说搜索是一个大的集合,回溯是搜索的分支,二者之间就差一步。

一个故事引入:

在初中的时候,那时候黑网吧很多,几个小伙伴周五放学没事就要去网吧玩几把lol,但是黑网吧为了不被查封,往往很隐蔽,那么现在有个网吧老板反侦察意识很强,把网吧放在一个迷宫后面,几个同学听说这边新网吧刚开业,冲一送四个钟,泡面随便冲,周五放学就按捺不住要去了,但是第一次去也没有路线,没有办法,只能走迷宫,越是几个哥们就出发了,来到分叉路口,哥几个决定先走一边尝试一下,万一就选对路线了呢。

52d35e6d50c742959f9ecce78fab86fa.png

然后来到第一个路口向右转,发现走不同,然后就回头到上一个路口,从新选择方向。

(从哥几个撞墙走不通回到上一个起点重新选择的这个过程就叫做回溯)。哥几个回到了路口,重新选择,这时候有个伙伴珊珊来迟,看见几个伙伴站在路口,就问,走那边,所有人都说走左边,新来的说为什么不走右边,几个弟兄回答说:左边去过了走不通,果断放弃,你要去你就自己去。 (明确知道其中一个选择不是我们想要的结果的时候,我们不走这个选择。这个就叫做剪枝)于是哥几个就用这样的方法走出迷宫,来到网吧度过了一个快乐的周五。

所以:

回溯算法实际上一个类似枚举的搜索尝试过程,在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法本质是一种深度搜索法,按条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择达不到目标,就退回一步重新选择,这种走不通就退回再走的方法为回溯法,可以说回溯就是深度搜索。

啊,这么一说回溯不就是递归吗?实际上这么说也对,因为递归当中就隐藏着回溯的过程,我们来看一下深度搜索的一种例子:比如我们二叉树的后续遍历,(遍历是一种方法,搜索是目的)

二叉树的后续遍历中,我们先访问左子树,在访问右子树,最后访问节点,是不是涉及到到左右子树在回到左右子树的过程,这实际上就是一种深度遍历,

深度优先遍历 dfs:一条道走到黑,走到不可以再往下走,回去有分支往深处走

深度优先搜索:遍历目的就是为了找值也就是搜索(可以画出决策树的问题都可以使用搜索。)

d35d19b5012c40cabdc42f38dcbd1b12.png

紫色的过程就是回溯 

3471c4a4d29d4a579a97bedc86eceaee.jpeg

这是递归和深度搜索dfs

而回溯算法的基本思想:从⼀个初始状态开始,按照⼀定的规则向前搜索,当搜索到某个状态⽆法前进 时,回退到前⼀个状态,再按照其他的规则搜索。回溯算法在搜索过程中维护⼀个逻辑状态树,通过遍历 状态树来实现对所有可能解的搜索。
回溯算法的核⼼思想:“试错”,即在搜索过程中不断地做出选择,如果选择正确,则继续向前搜 索;否则,回退到上⼀个状态,重新做出选择。回溯算法通常⽤于解决具有多个解,且每个解都需要 搜索才能找到的问题。关于多米洛问题的理解:问题衍生出子问题,子问题又衍生出相同的子问题。

那么深度搜索和回溯算法的区别在哪里呢: 

回溯本质是深搜这个说法没错,先说结论:在我的理解中,回溯和递归或者深度优先搜索算法的区别是在某些题目回溯强化了:①”剪枝的动作 ②回溯里面的‘恢复现场’的概念和实现。

就是说深度优先搜索或者遍历来说是一种穷举就是列出所有的情况,但是回溯算法中可以通过剪枝动作来规避掉一些不想要的结果,一般情况下,这就减少了工程量,虽然在算法复杂度量级上体现的不明显。 但是如果所有的结果都是我们想要的,此时剪枝动作就没有很大的意义,或者说没有办法发挥作用,那么我们的回溯和一个深度穷举效率是差不多的。也可以理解为二者是一个方法

所以:我们可以说:回溯 = 深度搜索+强化剪枝+强化恢复现场

下面我们理解一下什么叫做剪枝和恢复现场: 

2.关于剪枝

在我们家乡,二三月份果树开苞的时候,就要将那些没有果和果少的树枝剪去,让好的和大的果苞能吸收到更多的养分。我们减去的树枝首先就是不符合我们的要求才剪去了,比如没有果子。

1f2b759f09c340b9b2c42aca2502387b.png

在回溯算法中,我们将明确知道其的中一个选择不是我们想要的结果的时候,我们不走或者叫做排除这个选择。由于回溯算法的问题一般都可以转换成一颗逻辑决策树,

比如求3皇后的问题:不用知道只是介绍剪枝

4b39977af1984f398afc2e2caaecd275.png

所以在使用回溯算法时,将我们不需要的结果规避掉(直接不走那个分支,因为知道那个分支没有我们需要的结果),也就叫做剪枝,生动形象。

3.关于恢复现场

在刚才的迷宫问题中,所有人回退到路口重新进行选择的过程就是一种恢复现场的动作。

很多时候,特别是当我们学完回溯过后,有一种错觉:看到了代码中的“恢复现场”动作,我们就大脑自动反应:这个题解使用了回溯算法,实际上这种想法需要纠正一下:是因为出现了回溯我们才要想到去恢复现场。这样我们在代码实现的时候才能反应过来,出现了回溯的过程,那么就要考虑恢复现场。

所以:

什么是回溯:只要出现递归就伴随着回溯,只要出现深度优先遍历就伴随着回溯,只不过,在某些简单的题目中,我们递归调用函数传入参数的时候实际上已经是一个恢复现场的动作了。

比如二叉树的后续遍历中,

48788af6d37a4715971f55a03475a547.png

左子树遍历完,进行右子树的遍历,这个传递的参数实际上就是一种简单的恢复现场

但是如果传递的参数是一个全局变量或者是传地址调用的时候,我们在进行下步操作的时候就要考虑一下需不需要恢复现场了。

有了上面的铺垫大家心里应该对回溯有了一点认识,接下来我们来一道简单的题目来配合理解一下上面的概念。

4.题目:二叉树的所有路径(凸显恢复现场:切实感受回溯与深搜)

71afda172607432599306ad360304d30.png

问题分析

首先我们需要两个数组,一个用来存储所有的路径,也就是最终的结果。一个就是我们保存我们的单条路径。

  • 1. 如果当前节点不为空,就将当前节点的值加⼊路径 path 中,否则直接返回;
  • 2. 判断当前节点是否为叶⼦节点,如果是,则将当前路径加⼊到所有路径的存储数组 paths 中;
  • 3. 否则,将当前节点值加上 "->" 作为路径的分隔符,继续递归遍历当前节点的左右⼦节点。
  • 4. 返回结果数组。

①函数设置为:void Dfs(root)

b04f6cb7d15344818817c4b48a206100.png

9d60bc3407ab47ffb7d1f1c750aae704.png

②函数设置为:void Dfs(root,path)

 8c910be5c0ae451bb19f16dd1ae41dc9.png

剪枝的体现:在判断叶子节点的时候,如果当前节点的左右节点不为空我们就进入,为空不满足我们的条件,我们就不进入。这道题目剪枝不剪枝都可以,不剪枝可能就理解成深度搜索算法,有个剪枝也可以进一步理解为回溯。

500017e189144b298767e199f60a6ee3.png

解题思想:使⽤深度优先遍历(DFS)求解。

路径以字符串形式存储,从根节点开始遍历,每次遍历时将当前节点的值加⼊到路径中,如果该节点
为叶⼦节点,将路径存储到结果中。否则,将 "->" 加⼊到路径中并递归遍历该节点的左右⼦树。

代码实现

09e3d31c3150472098a1659605cc025f.png

  void dfs(struct TreeNode* root,char* path,int len , char** str,int * strcount)
    {
        assert(root);
         sprintf(path+len,"%d",root->val);
         len = len+1;
         
        if(root->left==NULL&&root->right==NULL)
        {
           
           str[*strcount] = path;
           *strcount++;
           return;
        }
         
        if(root->left)
        {
          sprintf(path+len,"->");
          len = len+1;
          dfs(root->left,path,len,str,strcount);
        }
         if(root->right)
        {
          sprintf(path+len,"->");
          len = len+1;
          dfs(root->right,path,len,str,strcount);
        }
    {
        
    }
    }

    char** str = (char**)malloc(sizeof(char*)*n);//定义一个字符串数组来存储路径
 char *path = (char*)malloc(1001);

int len = 0;
int strcount = 0;
   dfs(root,path,len,str,&strcount);
   free(path);
   path = NULL;
   return str;

 C语言代码目前有点问题,不过可以提供参考。

5.N后问题

. - 力扣(LeetCode)

bb6fb28633e94bd985e482d46f8f6659.png

问题分析

简单点:有几个皇后就是一个几乘以几的棋盘,然后当我们在一个位置放上一个棋子后,同行同列,同对角线不可以放第二枚棋子,然后要求n个棋子有多少种方法。

算法思想:①对每个位置进行枚举,也就是一个小格子一个小格子的去判断,就是对于每个位置试着放,看能不能放,如果是从1到N个位置进行判断,时间复杂度为:O(N^3)

89e6da9b890b4a89990993a8fbf86ff4.png

第一次放第一个格子,,然后去判断N^2-1个格子可不可以放置

第二次放在第二个格子,然后去判断剩下N^2-2个格子可不可以放置

时间复杂度应该为O(N^3)

②以行为单位,去看每一行的棋子应该怎么放:每一行落子后就考虑下一行,然后当我们行数来到n行的时候,就得到一个合理的结果了

4皇后的放置方式

首先我们先在第一行进行落子:共有四种放置方式

70a7c489e9174935ad18e8129a876d71.png

接下来我们考虑往第二行的落子:

第二行依然是四种情况,但是对角线和同行同列排除不放:

1e9f65a048fc42c4a36fdf669894131f.png

接下来我们考虑第三行落子:

 5f7736aa113147768fdf31c0ca504595.png

接下来我们考虑第4行落子:

fa720ec799e145ffb671aa11abcf3651.png

按照这样的方法就可以得到结果,我们的4皇后的结果是以上两种方法。最后得到的这个树状图就是经过剪枝的效果。

代码实现:

首先创建一个N*N的棋盘,然后在每一行试着放上一个皇后,判断是否可以放;

①对同列分析

首先检查该位置所在的列有没有皇后。这里我们这样用一个bool类型的数组来保存每一列的情况,某一列上有皇后,我们就保存为true,没有就保存folse:由于放置皇后位置这一列的的列数是一样的

8e4b278f6efe44cd8252d77001d6f183.png

那么通过列数作为数组下标,访问这个数组的值,就可以知道这一行有没有元素。

704d177359bc4b73b59ee99b78d35856.png

②对于对角线的位置:

主对角线:

 处于同一条对角线的格子,都在一条直线上:y = X+b

也就是说,处于一条对角线上的格子都满足:y-x = b,

那么我们就可以像列一样将对角线的情况用一个数组存储起来,然后当放置皇后的时候,用该点的横纵坐标来计算出当前在那条对角线上,然后通过数组就可以知道这条对角线上有没有皇后。

但是由于数组的下标不能出现负数,所以这里计算的时候,可以将等式两边同时加上n,x相当于将棋盘向上平移n.

y-x+n = b+n

65b4145f05af41ac9f742a36e9765f43.png

副对角线:

34e4018b071448889207bd3c36590c1f.png

代码实现:

#include<stdio.h>
#include<stdbool.h>

#define N 4//定义宏,控制皇后数量
bool CheckCol[N];//0 1 2 3 列的情况
bool CheckDig[3*N];//主对角线
bool CheckBig[2*N];//副对角线

char pan[N][N];//定义棋盘大小

int num = 0;//全局变量,记录方案
//初始化棋盘函数,将棋盘初始化为.
void InitPan()
{
	
	int i = 0;
	int j = 0;
	for (i = 0; i < N; i++)
	{
		for (j = 0; j < N; j++)
		{
			pan[i][j] = '.';
		}
	}

}
//打印棋盘函数,用于对棋盘进行输出
void PrintPan()
{
	
	int i = 0;
	int j = 0;
	for (i = 0; i < N; i++)
	{

		for (j = 0; j < N; j++)
		{
			
			printf("%c ", pan[i][j]);
			
		}
		printf("\n");
	}

}

void dfs(int row)
{
	if (row == N)
	{
		num++;
		PrintPan();
		printf("________________\n");
		return;
	}
	for (int col = 0; col < N; col++)
	{
		if (!CheckCol[col] && !CheckDig[row - col + N] && !CheckBig[row + col])//剪枝
		{
			pan[row][col] = 'Q';
			CheckCol[col] = CheckDig[row - col + N] = CheckBig[row + col] = true;
			
			dfs(row + 1);
			//恢复现场,回退到了上一行
			pan[row][col] = '.';
			CheckCol[col] = CheckDig[row - col + N] = CheckBig[row + col] = false;
		
		}
	}
}

int main()
{
	InitPan();//初始化一下棋盘
	PrintPan();
	printf("________________\n");
	dfs(0);
	printf("共有%d种方案\n",num);
	return 0;
}

 8b7365e5ae3c40049fda95e2ad7b21ec.png

69d150097d09425f95fe585576da6816.png

递归展开图

f211591bd3e94c988a2b2178807f6ee2.png

时间复杂度

时间复杂度是一个稳健的保守预期,就是一般只关注最坏的情况,算法复杂度和算法调用中执行的基本语句的次数成正比。

最坏的情况:第一行有N中方法,第一行的每一种方法都匹配第2行的n-1中方法

第二行:至多N-1中放法

第三行:至多N-2中放法:

第N行:至多1中方法

时间复杂度为:N*N-1*n-2......*1

时间复杂度为O(N!)

空间复杂度

对算法使用的一个额外空间进行估算。

引入斐波那契数列的递归计算进行讲解:

重点:计算时间复杂度的时候时间是可以累加的,但是空间却是可以重复利用的

先上代码:

// 计算斐波那契递归Fib的时间复杂度?
long long Fib(size_t N)
{
 if(N < 3)
 return 1;
 
 return Fib(N-1) + Fib(N-2);
}

我们在计算其时间复杂度的时候我们这样来理解这个算法的调用的:

1d1b16d951af4cf9b0b468bad324172c.png

在这个时候我们理解的是:递归调用函数是一起调用的,但是在真正的递归在内存中跑起来却不是这样调用的:

我们以Fib(4)举例讲解:

abef2cc6e18840f589d839c77ddcac2c.png

然后后面的调用都是使用这片空间,我们如果在程序中调试去看,我们的N的值应该会这样变化:4-3-2-1-3-4-2-4:

adf6447d2aec4859ae1093fc967cb684.gif

那么当我们有n个递归:

4897ae4420d4402bbb3183fb02287db4.png

是不是只会总的开辟N个空间从N到1,那么空间复杂度就为O(N)

时间一去不复返,空间可以重复再利用。函数最多递归n次,也就是开辟n次栈帧空间,

da0aef1fbbb54567b245b0c978b87e02.png

所以时间复杂度为O(N)

6.总结

本文先带大家了解什么是回溯:走不通回头,然后给出了第一个回溯算法的定义,然后给大家区分了递归和深度搜素和回溯的区别,然后引出了对回溯的剪枝和恢复现场的讲解,接着通过二叉树路径这道简单题目让大家对以上概念得到运用和更深入理解,最后使用递归解决了n皇后的问题,分析了时间复杂度和空间复杂度。创作不易,希望大家多多指教,如果觉得今天讲解的有学到东西,可以留下一个三连,持续关注后续的文章。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/544157.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Day101:漏洞发现-漏扫项目篇NucleiYakitGobyAfrogXrayAwvs联动中转被动

目录 特征类-三方Poc调用&模版Poc调用 案例1&#xff1a;单点对某特征点进行安全评估 Goby-综合类 Nuclei-较综合类 Afrog-特征类 Yakit-可特征可综合 案例2&#xff1a;新型对某特征点进行安全评估 综合类-主动漏扫&中转联动&被动联动 案例1&#xff1a;…

给自己的机器人部件安装单目摄像头并实现gazebo仿真功能

手术执行器添加摄像头 手术执行器文件夹surgical_new内容展示如何添加单目摄像头下载现成的机器人环境文件启动仿真环境 手术执行器文件夹surgical_new内容展示 进入src文件夹下选择进入vision_obliquity文件夹 选择launch 有两个可用gazebo中rviz展示的launch文件&#xff0…

当我们使用git 上传码云的时候报错:Push rejected Push to origin/master was rejected

在我们推送成果去git&#xff08;码云&#xff09;的过程中报错&#xff1a;Push rejected Push to origin/master was rejected 这个问题是我们在推的时候被拒绝了 控制台报错&#xff1a; 18:46:19.665: [zengqingqingandluoxuwen] git -c credential.helper -c core.quote…

软件无线电安全之GNU Radio基础 -上

GNU Radio介绍 GNU Radio是一款开源的软件工具集&#xff0c;专注于软件定义无线电&#xff08;SDR&#xff09;系统的设计和实现。该工具集支持多种SDR硬件平台&#xff0c;包括USRP、HackRF One和RTL-SDR等。用户可以通过GNU Radio Companion构建流程图&#xff0c;使用不同…

嵌入式学习54-ARM3(中断和时钟)

知识零碎&#xff1a; import &#xff0c;定义表示这是一个外部变量的标号&#xff0c;不是在本程序定义的 export &#xff0c;表示本程序里面用到的变量提供给 其他模块 调用的。 按键模块中&#xff0c;K1和K6所连接的高电阻&#xff0c;根据外部变化变化 …

HiveQL练习(hive3.x)

零、准备工作 1. Hive环境安装 参见搭建Hive 3.x环境&#xff08;CentOS 9 Hadoop3.x&#xff09; 2. 准备数据 在虚拟机HOME目录创建如下文件内容&#xff1a; cd /root vi emp.csv内容如下&#xff1a; 7369,SMITH,CLERK,7902,1980/12/17,800,,20 7499,ALLEN,SALESMAN…

SpringMVC--获取请求参数 / 域对象共享数据

目录 1. SpringMVC 获取请求参数 1.1. 通过ServletAPI获取 1.2. 控制器方法形参获取 1.3. RequestParam 1.4. RequestHeader 1.5. CookieValue 1.6. 通过POJO获取请求参数 1.7. 解决获取请求参数的乱码问题 2. 域对象共享数据 2.1. 三大域对象 2.2. 准备工作 2.3. S…

8:系统开发基础--8.5:系统设计、8.6:系统测试 、8.7:软件维护 、8.8:软件质量保证、8.9:软件文档

转上一节&#xff1a; http://t.csdnimg.cn/X0GjWhttp://t.csdnimg.cn/X0GjW 8.5&#xff1a;系统设计 考点1&#xff1a;系统设计概述 1&#xff1a;软件设计的任务与活动 体系结构设计&#xff1a;定义软件系统各主要部件之间的关系。 数据设计&#xff1a;基于E-R图确定…

免费的 ChatGPT 网站(六个)

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 一、insCode二、讯飞星火三、豆包四、文心一言五、通义千问六、360智脑 现在智能…

专题十三、预处理器

预处理器 1. 预处理器的工作原理2. 预处理指令3. 宏定义3.1 简单的宏3.2 带参数的宏3.3 # 运算符3.4 ## 运算符3.5 宏的通用属性3.6 宏定义中的圆括号3.7 创建较长的宏3.8 预定义宏3.9 C99 中新增的预定义宏3.10 空的宏参数3.11 参数个数可变的宏3.12 __func__ 标识符 4. 条件编…

Leetcode 416.分割等和子集

题目 思路 使用0-1背包的思路。 之前0-1背包是说有N个物品&#xff0c;一个最大承重重量为W的背包。每个物品都有各自的重量和value,要让放入背包中物品价值总和最大。 这道题如何对应成0-1背包&#xff0c;看下面的分析。 背包的大小&#xff1a;要想两个子集元素和相等&am…

宝塔面板Docker+Uwsgi+Nginx+SSL部署Django项目

这次为大家带来的是从零开始搭建一个django项目并将它部署到linux服务器上。大家可以按照我的步骤一步步操作&#xff0c;最终可以完成部署。 步骤1&#xff1a;在某个文件夹中创建一个django项目 安装django pip install django创建一个django项目将其命名为djangoProject …

【板栗糖GIS】如何给微软拼音输入法加上小鹤双拼

【板栗糖GIS】如何给微软拼音输入法加上小鹤双拼 用过在注册表里新建的方法&#xff0c;结果弄完没有出现小鹤双拼方案&#xff0c;想到了自己写reg表 目录 1. 新建一个txt文件 2. 把.txt的后缀名改成.reg&#xff0c;双击运行 3. 在设置中找到微软输入法-常规 1. 新建一个…

二分查找(函数法)

1.二分查找的前提 只有单调的序列才能进行二分查找&#xff1b; 一般为单调不减&#xff0c;单调不增需要像 sort() 一样修改比较函数&#xff1b; 2.binary_search( ) 函数 binary_search( ) 是算法库&#xff08;algorithm&#xff09;函数里面的&#xff0c;用于在一个已经…

【web网页制作】html+css旅游家乡山西主题网页制作(3页面)【附源码】

山西旅游网页目录 涉及知识写在前面一、网页主题二、网页效果Page1、景点介绍Page2、酒店精选|出行攻略Page3、景色欣赏 三、网页架构与技术3.1 脑海构思3.2 整体布局3.3 技术说明书 四、网页源码4.1 主页模块源码4.2 源码获取方式 作者寄语 涉及知识 山西旅游主题网页制作&am…

【大语言模型】轻松本地部署Stable Diffusion

硬件要求&#xff1a; 配备至少8GB VRAM的GPU&#xff0c;如果你的电脑只有CPU&#xff0c;请看到最后。根据部署规模&#xff0c;需要足够的CPU和RAM。 软件要求&#xff1a; Python 3.7或更高版本。支持NVIDIA GPU的PyTorch。Hugging Face的Diffusers库。Hugging Face的Tr…

Training - PyTorch Lightning 分布式训练的 global_step 参数 (accumulate_grad_batches)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/137640653 在 PyTorch Lightning 中&#xff0c;pl.Trainer 的 accumulate_grad_batches 参数允许在执行反向传播和优化器步骤之前&…

CSS常用十大选择器(理论+代码实操)

HTML代码实例 注意&#xff1a;拷贝后本地运行注意head标签中的link标签的href属性是否正确 我的目录结构&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>Title</title><lin…

人机区别之一在于机器智能还不能提出问题

人机区别在于机器智能目前还不能提出问题。虽然机器智能已经可以通过程序和算法执行各种任务&#xff0c;但它们仍然无法像人类一样主动思考和提出问题。机器智能只能根据预设的指令或对特定情况的响应来进行操作&#xff0c;而无法产生自己的独立思考和主动提问。这是因为机器…

广东省道路货物运输资格证照片回执可手机线上办理

广东省道路运输资格证是从事道路运输业务、危险品道路运输人员的必要证件&#xff0c;而在办理该证件的过程中&#xff0c;驾驶员照片回执是一项必不可少的材料。随着科技的发展和移动互联网的普及&#xff0c;现在办理驾驶员照片回执已经不再需要亲自前往照相馆&#xff0c;而…