目录
一、递归函数
//计算斐波那契数列的函数
int fib(int n){
if(n <= 1) return n;
return fib(n-1) + fib(n-2)
}
实际使用时,即使是 fib(40) 这样n较小时的结果,也要花费 相当长的时间;
在这个斐波那契数列中,如果 fib(n) 的n是一定的,多次调用都会得到同样的结果。如果计算一次之后,用数列储存起来,便可优化之后的计算。
这种方法是出于 记忆化搜索 或者 动态规划 的想法。
二、栈(Stack)和 队列(Queue)
栈(Stack)通过push和pop在栈的顶端存取数据
LIFO:Last In First Out,即后进先出。
队列(Queue)通过offer和poll在队列的底端存取数据
FIFO:First In Frst Out,即先进先出。
三、深度优先搜索(DFS)
- 深度优先,就是以深度为准则。
- 递归下去(先一条路走到底,直到达到目标)
- 回溯上来(没有达到目标又无路可走了,则退回到上一步的状态,走其他路)
根据DFS的特点,采用 递归 函数实现比较简单
#01 部分求和问题
题目
给定整数a1 、a2、…、a2,判断是否可以从中选出若干数,使它们的和恰好为 k 。
样例
n = 4
a = { l, 2, 4, 7 }
k = 13
Yes ( 13 = 2 + 4 + 7 )
思路
- 从 a1 开始按顺序决定每个数加或不加,在全部n个数都决定后再判断它们的和是不是 k 即可。因为状态数是 2n+1 ,所以复杂度是 O(2n) 。
- 注意α的下标与题目描述中的下标偏移了1 。在程序中使用的是 0 起始的下标规则,题目描述中则是 1 开始的,这一点要注意避免搞混。
递归函数的作用:深度遍历所有分支,判断是否存在和为 k 的组合
递归结束条件:遍历到最后一个数,判断和是否为 k( if i == n,return sum == k)
遍历顺序:后序遍历
代码
static int n, k;
//若使用全局数组不能动态分配空间
static List<Integer> list=new ArrayList<>();
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
n=scan.nextInt();
for(int i=0;i<n;i++)
list.add(scan.nextInt());
k=scan.nextInt();
scan.close();
if(dfs(0, 0)) System.out.println("Yes");
else System.out.println("No");
}
//已经从前 i 项得到了和 sum ,然后对于 i 项之后的进行分支(后序遍历)
public static boolean dfs(int i, int sum) {
//结束条件:遍历到最后一个,判断sum和k是否相等(根节点)
if(i==n) return sum==k;
//不加a[i]的情况(左子树)
if(dfs(i+1, sum)) return true;
//加上a[i]的情况(右子树)
if(dfs(i+1, sum + list.get(i))) return true;
四、广度优先搜索(BFS)
- 广度优先搜索旨在面临一个路口时,把所有的岔路口都记下来,然后选择其中一个进入,然后将它的分路情况记录下来,然后再返回来进入另外一个岔路,并重复这样的操作。最后找出所有可行路径。
- 在BFS中,关键点则是状态的 选取 和 标记
#03 迷宫的最短路径
题目
给定一个大小为 N × M 的迷宫。迷宫由通道和墙壁组成,每一步可以向邻接的上下左右四格的通道移动。请求出从起,点到终点所需的最小步数。请注意,本题假定从起点一定可以移动到终点。
样例
10 10
22
思路
- BFS按照距开始状态 由近及远 的顺序进行搜索,因此可以很容易地用来求 最短路径、最少操作 之类问题的答案。
- 要将已经访问过的状态用标记管理起来,就可以很好地做到由近及远的搜索。(用数组把最短距离保存起来。初始时用充分大的常数INF来初始化它,这样尚未到达的位置就是INF ,也就同时起到了标记的作用。)
- 虽然到达终点时就会停止搜索,可如果继续下去直到队列为空的话,就可以计算出到各个位置的最短距离。此外,如果搜索到最后, d依然为INF的话,便可得知这个位置就是无法从起点到达的位置。
- *这个问题中,状态仅仅是目前所在位置的坐标,因此可以用数组来表示状态。当状态更加复杂时,就需要封
装成一个类来表示状态了。
步骤
- 需要的变量:
map 地图数据
start 、goal :起点和终点的坐标
distance 每个点到起点的最小距离,初始化最大值,起点初始为0
Queue 待遍历的节点,FIFO 初始保存起点
tx, ty 保存新位置的坐标- 先遍历 队列 里的节点
取出底端的节点(current)进行遍历四个方向:
如果符合条件,则算出它到起点的距离( current 到起点的距离+1)
并将其加入队列,等待下一轮遍历
代码
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt(); // 行
int m = scan.nextInt(); // 列
scan.nextLine(); // 吸收上一行输入的\n
char[][] map = new char[n][m]; // 地图数据
int[] start = new int[2]; // 起点
int[] goal = new int[2]; // 终点
// 写入地图数据到map
for (int i = 0; i < n; i++) {
String s = scan.nextLine();
map[i] = s.toCharArray();
// 给起点赋值
if (s.contains("S")) {
start[0] = i;
start[1] = s.indexOf("S");
}
// 给重点赋值
if (s.contains("G")) {
goal[0] = i;
goal[1] = s.indexOf("G");
}
}
scan.close();
System.out.println(bfs(map, start, goal));
}
//
public static int bfs(char[][] map, int[] start, int[] goal) {
// 设置四个方向
int[] dx = { 1, 0, -1, 0 };
int[] dy = { 0, 1, 0, -1 };
//记录起点到每个可能的点的距离
int[][] d = new int[map.length][map[0].length];
//队列保存待遍历的节点
Queue<int[]> que = new LinkedList<>();
for (int i = 0; i < d.length; i++)
for (int j = 0; j < d[0].length; j++)
d[i][j] = Integer.MAX_VALUE;
//把起点加入队列,并设置距离为0
que.offer(start);
d[start[0]][start[1]] = 0;
while (!que.isEmpty()) {
//取出最底端的节点
int[] current = que.poll();
//如果找到则跳出循环
//如果有多条路通往终点,先找到的肯定是最近的
if (current[0] == goal[0] && current[1] == goal[1])
break;
// 遍历四个方向
//计算所有移动的点到起点的距离(上一个点到起点的距离+1)
//将可能的节点加入队列(可能加入多个节点)
for (int i = 0; i < 4; i++) {
//新位置的坐标
int tx = current[0] + dy[i];
int ty = current[1] + dx[i];
// 新位置必须在下标范围内,不能为墙(#),且未被赋值(即不能为初始值MAX_VALUE)
if (ty >= 0 && ty < d[0].length && tx >= 0 && tx < d.length && map[tx][ty] != '#'
&& d[tx][ty] == Integer.MAX_VALUE) {
//新位置等于上一个节点的距离加1
d[tx][ty] = d[current[0]][current[1]] + 1;
//将下一个节点放入队列
int[] c = { tx, ty };
que.offer(c);
}
}
}
//返回终点到起点的距离
return d[goal[0]][goal[1]];
}
五、DFS和BFS的使用
- BFS是用来搜索 最短径路 的解是比较合适的,比如求最少步数的解,最少交换次数的解,因为BFS搜索过程中遇到的解一定是离根最近的,所以遇到一个解,一定就是最优解,此时搜索算法可以终止。
这个时候不适宜使用DFS,因为DFS搜索到的解不一定是离根最近的,只有全局搜索完毕,才能从所有解中找出离根的最近的解。(当然这个DFS的不足,可以使用迭代加深搜索ID-DFS去弥补)
- 空间优劣上,DFS是有优势的,DFS不需要保存搜索过程中的状态,而BFS在搜索过程中需要保存搜索过的状态,而且一般情况需要一个队列来记录。
- DFS适合搜索 全部的解 ,因为要搜索全部的解,那么BFS搜索过程中,遇到离根最近的解,并没有什么用,也必须遍历完整棵搜索树,DFS搜索也会搜索全部,但是相比DFS不用记录过多信息,所以搜素全部解的问题,DFS显然更加合适。
六、全排列模板(整数数组)
确定第一位的可能:1,2,3
循环:
第一位为 1 时,确定第二位的可能(递归):2,3
第一位为 2 时,确定第二位的可能 f ( k+1 ) :1,3
第一位为 3 时,确定第二位的可能 f ( k+1 ) :1,2
static int arr[] = { 1, 2, 3, 4};
public static void main(String[] args) {
f(0);
}
private static void f(int k) { //k:确定排列的第k位
if (k == arr.length) { //全部确认
check();
return;
}
for (int i = k; i < arr.length; i++) {
//将第i位和第k位交换(第k位的数字做第i位的情况)
int t = arr[i];
arr[i] = arr[k];
arr[k] = t;
f(k + 1); // 移交下一层去确认k+1位
// 回溯(换回来,避免出现错误)
t = arr[i];
arr[i] = arr[k];
arr[k] = t;
}
}
//具体检查区
static boolean check(){
}