目录
这篇文章主要目的是借各种题目🍌学习深度优先搜索🍌(depth-first search)
😄纯新手0.o😊
题目一:组队(回溯法)
介绍:
👹👹深度优先搜索:
简称深搜或者👍爆搜(没有什么是暴力解决不了的)
目的是遍历,是无序的。过程是:
1、访问顶点v
2、依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中所有和v有路径相通的顶点被访问💪
3、若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止
看图:
路径:
a b d h d b e i e j e b a c f k f c g(橙色表示初次经过)
过程描述:
第一条路访问了a,b,d,h,走到底了,就从h退回d,发现节点d没有除h以外的没访问过的,就退回b,b有除d以外未访问过的e,所以又从e开始进行dfs...
被访问的顺序:
a --> b --> d --> h --> e --> i --> j --> c --> f --> k --> g
当然初始是随机的,它可以先从a到c,也可以先从a到b
dfs一般找不到最优解,所以它适合给了初始条件,寻找解但不要求找最优解的题目
👹👹回溯法:
目的是求解过程,是有序的。回溯算法 = 树的深度优先搜索 + 剪枝函数,是dfs的一种改进
一般用递归实现
题目:
2019蓝桥杯C/C++B组ヽ(✿゚▽゚)ノ
作为篮球队教练,你需要从以下名单中选出 1 号位至 5 号位各一名球员,
组成球队的5人首发阵容。
每位球员担任 1 号位至 5 号位时的评分如下表所示。请你计算首发阵容 1
号位至 5 号位的评分之和最大可能是多少?
代码(C++递归)
#include<iostream>
#include<algorithm>//max()
using namespace std;
int a[20][6] = //声明初始化二维数组存储表格数据
{
{1,97,90,0,0,0},
{2,92,85,96,0,0},
{3,0,0,0,0,93},
{4,0,0,0,80,86},
{5,89,83,97,0,0},
{6,82,86,0,0,0},
{7,0,0,0,87,90},
{8,0,97,96,0,0},
{9,0,0,89,0,0},
{10,95,99,0,0,0},
{11,0,0,96,97,0},
{12,0,0,0,93,98},
{13,94,91,0,0,0},
{14,0,83,87,0,0},
{15,0,0,98,97,98},
{16,0,0,0,93,86},
{17,98,83,99,98,81},
{18,93,87,92,96,98},
{19,0,0,0,89,92},
{20,0,99,96,95,81}
};//因为是声明二维数组,最后这里要加上";"
int visit[20];//记录20个队员中谁被访问过
int max_score = 0;//声明全局变量max_score保存dfs函数里的最大值
//index表示当前位置, sum是当前组合总分
void dfs(int index, int sum)
{
if(index == 6)//已经选完5个人,可以比较总分了
{
max_score = max(max_score, sum);//第一次敲我漏了这个
return;
}
for(int i = 0; i < 20; i++)//遍历20个队员
{
if(!visit[i])//如果他没被访问
{
visit[i] = 1;//已被访问
dfs(index+1, sum+a[i][index]);//在函数参数实现了累加
visit[i] = 0;//继续尝试下一种5人组合
}
}
}
int main()
{
dfs(1,0);
cout<<max_score<<endl;
return 0;
}
输出: 490
卡点①
代码39~45行,for循环的遍历中,dfs的递归,开始有点懵😫
so, 草稿纸上列出index, sum, i在第一种情况的大小
index | sum | i |
1 | a[0][1] | 0 |
2 | a[0][1]+a[1][2] | 1 |
3 | ... + a[2][3] | 2 |
4 | ... + a[3][4] | 3 |
5 | ... + a[4][5] | 4 |
index = 5时,就完成了5个队员分数的相加
卡点②
代码37行,if(index == 6)后的return;
不清楚index到了6后,是如何返回原来的值的
下面看解析:
先分析递归函数中的return ,看代码👇👇
#include<iostream>
using namespace std;
int Sum(int n)
{
cout<<n<<" ";//让大家看下递归累加顺序
if (n <= 1)//终止条件, 且Sum(1) == 1
return n;
return n+Sum(n - 1);//递归累加
}
int main()
{
int n;
while(cin>>n)
{
int a = Sum(n);//累加和
cout<<endl<<a<<endl;
cout<<endl;
}
}
输入输出
5
5 4 3 2 1
15
10
10 9 8 7 6 5 4 3 2 1
55
20
20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
210
分析
1,C++ 递归函数中的return是指:
从被调用函数返回到主调函数中继续执行,并非一遇到return整个递归结束
2,return 语句,顾名思义是终止当前正在执行的函数并将控制权返回到调用该函数的地方
3,在void的函数中也可以多次使用return,功能和循环中的break一样,在中间位置提前退出正在执行函数,也就是回到原来位置执行下一行代码
4,return; 表示结束本次函数
5,⭐递归中的return常用来作为递归终止的条件,当达到递归终止条件时,首先return的是最底层调用的函数,return之后,继续执行上一层调用该函数之后的代码⭐
在我对return进行理解时,这篇文章给了我一定启发关于递归中return的理解(最浅显易懂)_Pledgee的博客-CSDN博客_递归函数return怎么理解
🔒首先1,2,3,4分别占据1,2,3,4位置,5号位置就可以依次让队员5-20占据;然后4号位给5号队员,又是一种新的情况,5号位可以依次给6-20号位占据;依次这样搜索,每个人都可以换个位置。
其实就是一个排列组合问题,在20个人中选择五个人,依次排列
🔒所以说,index到了6后,返回原来递归位置的下一步,return到index=5,然后五号位 通过i ++,由5号队员占据,到6号占据,一直到20号,接着return到四号位index=4,i++使5号队员占据4号位,index+1,index = 5,五号位 i 又由6到20占据... ...
看图
题目二:迷宫
基础概念:
1,栈中的“先进后出,后进先出”什么意思
栈类似于弹匣,就像装子弹压弹进弹匣一样,一粒一粒压进去,但是退子弹是从最上面开始的,最先压进去的最后弹出来,最后一个压进去的第一个弹出来,这就是“先进后出”,也叫“后进先出”
2,栈的定义
栈就是数据暂时存储的地方,所以才有进栈,出栈的说法
3,栈与队列的区别
栈是把子弹子弹压进弹匣,后进的先出;队列就像排队,你第一个排,那就第一个轮到你,这就是先进先出,也叫后进后出。dfs(深度优先搜索)应用栈,而bfs(广度优先搜索)应用队列。栈占用空间小,而堆中的bfs常用于最短路。
4,栈在计算机领域的解释
栈作为一种数据结构,是只能在一端进行插入和删除的特殊线性表,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始读。允许进行插入和删除操作的一段称为栈顶(top),另一端称为栈底(bottom)。栈底固定,而栈顶浮动;栈中元素个数为0时成为空栈。插入数据称为进栈(PUSH),删除数据称为退栈(POP),栈也称后进先出表。栈在函数调用时可以可以存储断点,做递归时要用到栈!
5,堆和栈的区别
栈快捷但自由度小,堆过程长但自由度大
6,内存分配
一个由C/C++编译的程序占用的内存分为以下几部分:
(1)栈区(stack)--由编译器自动分配释放,存放函数参数值,局部变量的值等。操作方式类似于数据结构中的栈
(2)堆区(heap)--由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。它与数据结构中的堆是两回事,分配方式类似链表
(3)全局(静态)区(static)--初始化的全局变量和静态变量在一块区域,未初始化的在相邻的另一块区域。
(4)文字常量区--常量字符串放在这里
(5)程序代码区--存放函数体的二进制代码
7,关于dfs,只要递归理解了,相关语法学习(这个我还没学)并练习过,也就差不多了
还涉及到:malloc函数,stack(栈基本数据结构),pop()函数,结构体还有相关头文件等,害,没时间搞了,我先把C++基础语法学完再回来
最后奉上几张图
第二第三张图片来源于以下博客
迷宫算法(DFS)_热爱编程的大忽悠的博客-CSDN博客_迷宫算法
以下是我在学习dfs过程中看的博客,就差在了语法不懂,先去MOOC学习吧:
1,最短路径问题(更新)_skycrygg的博客-CSDN博客_最短路径问题
2,c++深度优先搜索详解_练习时长六年半的Programmer的博客-CSDN博客_c++深度优先搜索
3,回溯法与深度优先搜索(DFS)_lonely喆的博客-CSDN博客_回溯法和深度优先算法区别
4,深度优先搜索(DFS) 总结(算法+剪枝+优化总结)_HeartFireY的博客-CSDN博客
5,迷宫算法(DFS)_热爱编程的大忽悠的博客-CSDN博客_迷宫算法
6,BFS和DFS_shiki11111的博客-CSDN博客_dfs和bfs
题目三:走方格
题目
给定一个m*n方格阵,沿着方格边线走,从左上角(0, 0)开始,每次只能往右或往下走一个单位距离,问走到右下角(m, n)一共有多少种不同走法
输入
一行包含两个整数 m 和 n (m >= 1 && m <= 10) && (n >= 1 && n <= 10)
输出
输出一个整数,表示走法数量
代码
#include<iostream>
using namespace std;
int m, n, num = 0;
int dfs(int x, int y)
{
if(x == m && y == n)
num++;//走法数量+1
else
{
if(x < m)
dfs(x + 1, y);//递归行加一
if(y < n)
dfs(x, y + 1);//递归列加一
}
}
int main()
{
while(cin>>m>>n)
{
dfs(0, 0);
cout<<num<<endl;
num = 0;
}
return 0;
}
输入输出
1 1
2
1 2
3
2 2
6
2 3
10
3 3
20
10 10
184756
分析
1,首先我们明确,只能向右或下走,然后看图
图上粗线表示第一次路径,代码的关键在于第13行和第15行对dfs的递归,这也是深度优先搜索的精髓
当第一次满足x == 2 && y == 3后,此时已到达(2, 3),函数会return回上一步,也就是(2, 2),但是下一步的路之前走过了,所以又返回到(2, 1),以此类推一直return到(1, 0),
开始第二条路径: (1, 0) --> (1, 1) --> (1, 2) --> (1, 3) --> (2, 3)...然后继续重复上述步骤
关于递归的理解其实没什么好方法,首先你得知道你敲出dps(x + 1, y)类似代码会出现什么样的结果,多做点考察dfs的题,自然就懂了