先贴简单的全排列问题地址 : https://www.luogu.com.cn/problem/P1706 本题说的是n的全排列
本题是最典型的最入门的DFS问题,可采用回溯法。一眼看觉得很简单,第一次动手写发现许多细节问题,也理解了很长时间,所以在此分享一下个人的理解(PS:很多大佬发的题解确实没有仔细地说明缘由和代码的层次问题,我说一下吧)
首先要知道DFS的原理,如果不理解的话一定要多画图,每画一笔都要想想为什么这么画,下一步怎么得出来的。不多说,直接上代码后面分析。
import java.util.Scanner;
public class 全排列_标记法 {
static Scanner sc = new Scanner(System.in);
static int n = sc.nextInt();
static int result[] = new int[n+1];
static int vis[] = new int[n+1];
public static void main(String[] args) {
DFS(1);//对应下文问题1
}
private static void DFS(int step) {
if(step >= n+1) {//对应问题3
print();
return;
}
for(int j = 1 ; j <= n ; j++) {//对应问题3
if(vis[j] == 0) {
vis[j] = 1;//标记数组,见问题2
result[step] = j;
DFS(step+1);
vis[j] = 0;//回溯,见问题2
}
}
}
private static void print() {
for(int i = 1; i <= n; i++) {//对应问题3
System.out.printf("%5d",result[i]);
}
System.out.println();
}
}
对于代码中的一些问题这里说一下。
1.为什么dfs参数从1开始,1是什么意思?
答:我们将题目当成装箱子问题,第一步(step)是选择第一个箱子,然后是第二个箱子,直到把所有的箱子选完并装完数字。要注意的是,每选完一个箱子,就立即选择一个数字装入,再进入dfs循环,选择下一个箱子。
2.这里的vis[]数组怎么理解?
答:vis[]数组的vis -> visit访问,指的是判断访问数组,其数组默认值为0,所以当vis[i]==0时,当作此元素未被访问过(也就是未被标记过),而vis[i]==1时,指此元素已被访问标记过。
3.回溯不知如何层次在哪里,vis[i] = 0有何作用?
答:这个问题,你应该自己去仔细画一遍dfs的图,看看其原理,是什么时候往回退的,一步步往回退的过程就是回溯,过程中将已经访问过的元素重新赋值为0。因为你赋值为0的一个重要作用,就是在你触底,不能在继续访问新元素的时候,要往回退,往回退的过程中还要有另一条路会用到此元素,有种重点理解的地方,这个数字只存在与一个数组中,你访问数字,即为访问数组的元素,而且是只有一个数组,所以要一步步的回溯。例如:从1-4的过程中,当你选择step1:1 -> step2 : 2 -> step3 : 3 -> step4 : 4 。此时1234这4个数字都被访问过,vis[i]都为1,如果你要换一种排列,他们都不在访问范围内,所以第一步回溯,将step4中的4 vis[i]重新置为0,然后发现除了4没有其他数字选择,那么再次回溯,将step3的数字的也置为0(不是数字置为0,而是vis[]置为0),此时就有了选择,除了3以外,4也没被访问过,所以第二种方案,为step3为4,然后继续dfs,step4为3.(最后的结果 为 :1243)。
4.step==n+1为什么不能是step==n,循环为何要从int i = 1开始?
答:前者,step==n是可行的,他与dfs函数一开始的位置有关,但是输出可能不尽人意,dfs从1开始,意味着选择了第一个箱子,所以对应的触底反弹的底就是step==n+1。如果你愿意写从第0个箱子开始,也是可以的。
后者问题的答案,在DFS函数中 int i = 1,指的是数字i从1开始,就是题目要求的1-n之间的数字,不能从0开始,这样违反题目规则。在print函数中,其实与前者问题挂钩,我们舍弃了result[0]这个位置,因为主观上我们都是从第一个箱子开始,当然你改成int i = 0也可以,但是的dfs参数和step==n+1也需要改。这三者是互相联系的。
整体理解:切记dfs的顺序,step到底时不代表全部结束,因为此程序跑完会有很多次step==n或者step==n+1的情况。
这是数组标记法,还有一种交换法dfs的方法,我整理后再发!