DFS叫做深度优先搜索算法,我们通过DFS来实现数字的全排列。
前言
DFS叫做深度优先搜索算法,我们通过DFS来实现数字的全排列。
一、DFS是什么?
1.算法思路
DFS又叫做深度优先搜索算法。一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。
2.模拟演示
我们用一棵树来模拟一下深度优先搜索的过程,我们用数字来标识每一个结点。
图2.1 树
用DFS搜索从根结点1开始沿着树的深度进行搜索。1->2->4,当搜索到底部时,我们进行回溯,即4->2->5 ,然后重复上述操作,这就是一个简单的深度优先搜索的过程。整个搜索过程如下:
1->2->4->2->5->2->6->2->1->3->7->3->8->3->9->3->1。至此结束。
DFS我们通常会通过使用stack(栈)这种数据结构来实现。
注:栈的特点先进后出。
二、排列数字
1.排列数字
给定一个整数n,我们把1-n排成一排,那么我们可以得到很多种排列方法,最后我们按照字典序的方式排列出所有方法。如图所示:
图1.1 排列数字3
2.DFS模拟过程
图2.1DFS模拟过程
假设有 3 个空位,从前往后填数字,每次填一个位置,填的数字不能和前面一样。
最开始的时候,三个空位都是空的: _ _ _
首先填写第一个空位,第一个空位可以填 1,填写后为:1_ _
填好第一个空位,填第二个空位,第二个空位可以填 2,填写后为:1 2 _
填好第二个空位,填第三个空位,第三个空位可以填 3,填写后为: 1 2 3
所以这是一种方案
然后往后退一步,退到了状态:1 2 _ 。剩余第三个空位没有填数。第三个空位上除了填过的 3 ,没有其他数字可以填。
因此再往后退一步,退到了状态:1_ _ 。第二个空位上除了填过的 2,还可以填 3。第二个空位上填写 3,填写后为:1 3 _
填好第二个空位,填第三个空位,第三个空位可以填 2,填写后为: 1 3 2
所以这是一种方案,输出。
然后往后退一步,退到了状态:1 3 _ 。剩余第三个空位没有填数。第三个空位上除了填过的 2,没有其他数字可以填。
因此再往后退一步,退到了状态:1_ _ 。第二个空位上除了填过的 2,3,没有其他数字可以填。
因此再往后退一步,退到了状态: 。第一个空位上除了填过的 1,还可以填 2。第一个空位上填写 2,填写后为:2 _ _
填好第一个空位,填第二个空位,第二个空位可以填 1,填写后为:2 1 _
填好第二个空位,填第三个空位,第三个空位可以填 3,填写后为:2 1 3
这时候,空位填完,无法继续填数,所以这是一种方案,输出。
然后往后退一步,退到了状态:2 1 _ 。剩余第三个空位没有填数。第三个空位上除了填过的 3,没有其他数字可以填。
因此再往后退一步,退到了状态:2_ _ 。第二个空位上除了填过的 1,还可以填 3。第二个空位上填写 3,填写后为:2 3 _
填好第二个空位,填第三个空位,第三个空位可以填 1,填写后为:2 3 1
这时候,空位填完,无法继续填数,所以这是一种方案,输出。
后退一步,退到了状态:2 3 _ 。剩余第三个空位没有填数。第三个空位上除了填过的 1,没有其他数字可以填。
再往后退一步,退到了状态:2_ _ 。第二个空位上除了填过的 1、3,没有其他数字可以填。
因此再往后退一步,退到了状态:_ _ _ 。第一个空位上除了填过的 1、2,还可以填 3。第一个空位上填写 3,填写后为:3 _ _
填好第一个空位,填第二个空位,第二个空位可以填 1,填写后为:3 1 _
填好第二个空位,填第三个空位,第三个空位可以填 2,填写后为:3 1 2
这时候,空位填完,无法继续填数,所以这是一种方案,输出。
然后往后退一步,退到了状态:3 1 _ 。剩余第三个空位没有填数。第三个空位上除了填过的 2,没有其他数字可以填。
因此再往后退一步,退到了状态:3_ _ 。第二个空位上除了填过的 1,还可以填 2。第二个空位上填写 2,填写后为:3 2 _
填好第二个空位,填第三个空位,第三个空位可以填 1,填写后为:3 2 1
这时候,空位填完,无法继续填数,所以这是一种方案,输出。
然后往后退一步,退到了状态:3 2 _ 。剩余第三个空位没有填数。第三个空位上除了填过的 1,2,没有其他数字可以填。
因此再往后退一步,退到了状态:3 _ _。第二个空位上除了填过的 1、2,没有其他数字可以填。
因此再往后退一步,退到了状态: _ _ _。第一个空位上除了填过的 1、2、3,没有其他数字可以填。
至此深度优先搜索结束,全部过程排列完毕。
三、代码如下
import java.io.*;
public class 数字排列 {
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
//保存每一次路径
static int[] path;
//记录数字的状态,即是否被访问过
static boolean[] flag;
static int n;
public static void main(String[] args) throws Exception{
n = nextInt();
path = new int[n+1];
flag = new boolean[n+1];
dfs(0);
pw.flush();
}
//我们通过u来判断是否是叶子节点 u=0是在第一层 u=1是在第二层
public static void dfs(int u) {
//u==n时就说明我们已经彻底结束了 ,因为我们传参是从0开始的,可以结合图2.1来看
if(u == n){
for(int i = 0;i < n;i++){
pw.print(path[i]);
}
pw.println();
return;
}
//n层循环,相当于我们把每一个当第一位处理的每一种情况
for(int i = 1;i <= n;i++){
//我们来判断当前数字是否被处理过 比如我们第一个数字是1 那么后面两位就不能是1
if(!flag[i]){
path[u] = i;
flag[i] = true;
//相当于我们递归地处理后一个位置的数字
dfs(u+1);
//找寻完后,我们需要把所有的数字状态全部恢复
flag[i] = false;
}
}
}
public static int nextInt()throws Exception{
st.nextToken();
return (int)st.nval;
}
}
1.读入数据
3
2.代码运行结果
123
132
213
231
312
321
总结
DFS深度优先搜索算法我们主要还是通过递归来实现的,但是递归的代码量虽然少,但是需要我们仔细的理解递归函数的每一步,熟悉我们搜索的每一步都是干什么的,想要理解透彻的话建议自己按着代码一步一步的debug一遍,加深一下理解。