回溯法--深度优先搜索

1、概念

      回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。

   回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

     许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。

2、基本思想

   在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。

       若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。

       而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束

3、步骤

    (1)针对所给问题,确定问题的解空间:

            首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解。

    (2)确定结点的扩展搜索规则

    (3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

例子:

矩阵中的路径,剑指offer

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如[ab c e s f c s a d e e]是3*4矩阵,其包含字符串”bcced”的路径,但是矩阵中不包含“abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

回溯算法 这是一个可以用回朔法解决的典型题。

首先,在矩阵中任选一个格子作为路径的起点。如果路径上的第i个字符不是ch,那么这个格子不可能处在路径上的第i个位置。如果路径上的第i个字符正好是ch,那么往相邻的格子寻找路径上的第i+1个字符。除在矩阵边界上的格子之外,其他格子都有4个相邻的格子。重复这个过程直到路径上的所有字符都在矩阵中找到相应的位置。

由于回朔法的递归特性,路径可以被开成一个栈。当在矩阵中定位了路径中前n个字符的位置之后,在与第n个字符对应的格子的周围都没有找到第n+1个字符,这个时候只要在路径上回到第n-1个字符,重新定位第n个字符。由于路径不能重复进入矩阵的格子,还需要定义和字符矩阵大小一样的布尔值矩阵,用来标识路径是否已经进入每个格子。 当矩阵中坐标为(row,col)的格子和路径字符串中相应的字符一样时,从4个相邻的格子(row,col-1),(row-1,col),(row,col+1)以及(row+1,col)中去定位路径字符串中下一个字符如果4个相邻的格子都没有匹配字符串中下一个的字符,表明当前路径字符串中字符在矩阵中的定位不正确,

我们需要回到前一个,然后重新定位。一直重复这个过程,直到路径字符串上所有字符都在矩阵中找到合适的位置。

public class Solution {
	// 选取一个节点(i,j)为起始点,递归遍历这个节点上下左右的元素是否和待匹配的字符数组中元素相同,
	// 相同则继续遍历这个相同节点的上下左右元素,不同则后退回来。
	public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
		boolean[] visited = new boolean[matrix.length];

		for (int i = 0; i < rows; i++)
			for (int j = 0; j < cols; j++)
				if (hasPathCore(matrix, rows, cols, i, j, str, visited, 0))
					return true;
		return false;
	}

	/**
	 * @param matrix
	 *            矩阵
	 * @param rows
	 *            矩阵行数
	 * @param cols
	 *            矩阵列数
	 * @param i
	 *            矩阵行数索引
	 * @param j
	 *            矩阵列数索引
	 * @param str
	 *            要查找的目标字符串
	 * @param visited
	 *            对应的boolean矩阵是否访问过
	 * @param index
	 *            str字符串的下标,从0开始
	 * @return
	 */
	private boolean hasPathCore(char[] matrix, int rows, 
			int cols, int i, int j, char[] str, boolean[] visited,
			int index) {
		int idx = i * cols + j;
		if (i < 0 || j < 0 || i >= rows || j >= cols)
			return false;
		if (visited[i * cols + j] || matrix[idx] != str[index])
			return false;
		if (index == str.length - 1)
			return true;
		// 剪枝 状态位
		visited[idx] = true;
		index++;
		if (hasPathCore(matrix, rows, cols, i + 1, j, str, visited, index)
				|| hasPathCore(matrix, rows, cols, i, j + 1, str, visited, index)
				|| hasPathCore(matrix, rows, cols, i - 1, j, str, visited, index)
				|| hasPathCore(matrix, rows, cols, i, j - 1, str, visited, index))
			return true;
		visited[idx] = false;
		// 此题不index--也通过,why????
		index--;
		return false;

	}
}

机器人的运动范围 ,剑指offer

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

public class Solution {
	public int movingCount(int threshold, int rows, int cols) {
		if (threshold < 1 || rows < 1 || cols < 1) {
			return 0;
		}
		boolean[][] visited = new boolean[rows][cols];
		return movingCountCore(0, 0, rows, cols, visited, threshold);
	}

	private int movingCountCore(int i, int j, int rows, int cols, boolean[][] visited, int threshold) {

		if (i < 0 || i >= rows || j < 0 || j >= cols)
			return 0;
		if (getDigitNum(i) + getDigitNum(j) > threshold || visited[i][j])
			return 0;
		visited[i][j] = true;
		// 标记访问过,这个标志visited不需要回溯,因为只要被访问过即可。
		// 因为如果能访问,访问过会加1.不能访问,也会标记下被访问过。
		return movingCountCore(i - 1, j, rows, cols, visited, threshold)
				+ movingCountCore(i + 1, j, rows, cols, visited, threshold)
				+ movingCountCore(i, j - 1, rows, cols, visited, threshold)
				+ movingCountCore(i, j + 1, rows, cols, visited, threshold)
				+ 1;
	}

	private int getDigitNum(int num) {
		int sum = 0;
		while (num > 0) {
			sum += num % 10;
			num /= 10;
		}
		return sum;
	}
}

AutoPilot01(去哪儿2017校招真题)

自动驾驶技术这个词因Tesla为人们所熟悉,殊不知tesla仍只是处于非常初级辅助阶段,然而Uber与Volvo已经开始在匹兹堡试验部署真正意义的自动驾驶汽车,原理是在构建好的复杂地图数据基础上,根据在车辆移动时通过GPS结合车上的数个传感器、立体摄像头以及激光雷达收集数据与地图数据比对以此来识别交通标志、行人和障碍物并选择合适的道路自动讲乘客从A点带到B点。假设我们将地图抽象成一个N x N的二维矩阵,0为可行驶道路1为障碍物,车辆只能一步步按东、南、西、北方向行驶,请输出从A点[0,0]至B点[n-1,n-1]的最少步数(移动一次计一步)。
输入
一共n行,每行n个整数(空格分隔),只可能为0或1
输出
输出最短距离步数,不可到达则输出-1
样例输入
0 1 0 0
0 0 0 1
1 0 1 0
0 0 0 0
样例输出
6

import java.util.Scanner;
public class Main {

	private static int min = 0;
	private static int temp = 0;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		while (sc.hasNext()) {
			String str = sc.nextLine();
			String strs[] = str.split("\\s+");
			int count = strs.length;
			int[][] arr = new int[count][count];
			for (int i = 0; i < count; i++)
				arr[0][i] = Integer.parseInt(strs[i]);
			for (int i = 1; i < count; i++) {
				for (int j = 0; j < count; j++) {
					arr[i][j] = sc.nextInt();
				}
			}
			search(arr, 0, 0);
			if (min == 0) {
				System.out.println(-1);
			} else {
				System.out.println(min);
			}
		}
	}

	private static void search(int[][] arr, int row, int col) {
		if (row >= arr.length || col >= arr.length || row < 0 || col < 0)
			return;
		if (row == arr.length - 1 && col == arr.length - 1) {
			if (min == 0)
				min = temp;
			if (temp < min)
				min = temp;
			return;
		}
		if (arr[row][col] == 0) {
			temp++;
			arr[row][col] = 1;
			search(arr, row - 1, col);
			search(arr, row + 1, col);
			search(arr, row, col - 1);
			search(arr, row, col + 1);
			temp--;
			arr[row][col] = 0;
		}
	}
}

AutoPilot02(去哪儿2017校招真题)

自动驾驶技术这个词因Tesla为人们所熟悉,殊不知tesla仍只是处于非常初级辅助阶段,然而Uber与Volvo已经开始在匹兹堡试验部署真正意义的自动驾驶汽车,原理是在构建好的复杂地图数据基础上,根据在车辆移动时通过GPS结合车上的数个传感器、立体摄像头以及激光雷达收集数据与地图数据比对以此来识别交通标志、行人和障碍物并选择合适的道路自动讲乘客从A点带到B点。假设我们将地图抽象成一个N x N的二维矩阵,0为可行驶道路1为障碍物,车辆只能一步步按东、南、西、北方向行驶,请编程找出一条从A点[0,0]至B点[n-1,n-1]的最短路径。
输入
一共n行,每行n个整数(空格分隔),只可能为0或1
输出
输出最短路径,不可到达则输出nopath
样例输入
0 1 0 0
0 0 0 1
1 0 1 0
0 0 0 0
样例输出
0,0
1,0
1,1
2,1
3,1
3,2

3,3

此题过了百分之83,但是不知道错误在哪里,思想是根据求出所有路径,然后找到符合要求的路径,和二叉树路径差不多

class Node {
	public int row;
	public int col;

	public Node(int row, int col) {
		this.row = row;
		this.col = col;
	}
	@Override
	public String toString() {
		return row + "--" + col;
	}
}
public class Main{
	public static ArrayList<ArrayList<Node>> ret = new ArrayList<ArrayList<Node>>();
	public static ArrayList<Node> tep = new ArrayList<Node>();
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		while (sc.hasNext()) {
			String str = sc.nextLine();
			String strs[] = str.split("\\s+");
			int count = strs.length;
			long[][] arr = new long[count][count];
			for (int i = 0; i < count; i++)
				arr[0][i] = Integer.parseInt(strs[i]);
			for (int i = 1; i < count; i++) {
				for (int j = 0; j < count; j++) {
					arr[i][j] = sc.nextInt();
				}
			}
			search(arr, 0, 0);
			ArrayList<Node> list = null;
			for (ArrayList<Node> l : ret)
				if (l.get(l.size() - 1).row == count - 1 && l.get(l.size() - 1).col == count - 1) {
					if (list == null)
						list = l;
					else if (list.size() > l.size())
						list = l;
				}

			if (list == null)
				System.out.println("nopath");
			else {
				for (Node node : list)
					System.out.println(node.row + "," + node.col);

			}
			// System.out.println(ret);
		}

	}

	private static void search(long[][] arr, int row, int col) {
		if (row >= arr.length || col >= arr.length || row < 0 || col < 0) {
			ret.add(new ArrayList<Node>(tep));
			return;
		}
		if (row <= arr.length - 1 && col <= arr.length - 1) {

			tep.add(new Node(row, col));
		}
		if (arr[row][col] == 0) {
			arr[row][col] = 1;
			search(arr, row + 1, col);
			search(arr, row, col + 1);
			search(arr, row - 1, col);
			search(arr, row, col - 1);
			arr[row][col] = 0;
		}
		tep.remove(tep.size() - 1);
	}
}

概念参考:http://www.cnblogs.com/steven_oyj/archive/2010/05/22/1741376.html


    原文作者:回溯法
    原文地址: https://blog.csdn.net/mine_song/article/details/69233774
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞