回溯算法详解

本文深入探讨了回溯算法,通过遍历决策树解决复杂问题。以八皇后问题、子集生成、子集求解、组合计算、全排列及二维数组路径优化等为例,展示了回溯算法在信息技术领域的广泛应用。同时,介绍了如何利用回溯解决具有约束条件的排列组合问题,以及在二维数组中寻找最优路径的方法。
摘要由CSDN通过智能技术生成

回溯算法

解决一个回溯问题,实际上就是决策树的遍历问题。 下图就是一个决策树。因为在每一个节点上你都要做决策,向左或者向右。接下来解释几个名词。【2】是【路径】,【1,3】就是【选择列表】,表示当前可以做出的选择,【结束条件】就是遍历到树的底层。

1.回溯算法框架

result=[]
public void backtrace(路径,选择列表){
    if (满足结束条件){
        result.add(路径);
        return;
    }
    for 选择 in 选择列表{
        做选择;
        backtrace(路径,选择列表)
        撤销选择;
    }
}

2.经典例题

1.八皇后问题

问题描述:

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

代码

import java.util.ArrayList;
import java.util.List;

public class n皇后问题 {
    public static void main(String[] args) {
        List<List<String>> lists = solveNQueens(4);
        System.out.println(lists.toString());
    }
    public static List<List<String>> solveNQueens(int n) {
        char[][] chars = new char[n][n];
        List<List<String>> lists = new ArrayList<>();
        //初始化
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                chars[i][j]='.';
            }
        }
        res(lists,chars,0);
        return lists;
    }

    private static void res(List<List<String>> lists, char[][] chars, int row) {
        if (row==chars.length) {
            lists.add(constract(chars));
            return;
        }
        for (int col = 0; col <chars[0].length ; col++) {
            if (vaild(chars,row,col)) {
                chars[row][col] = 'Q';
                res(lists,chars,row+1);
                chars[row][col]='.';
            }
        }
    }
    //将字符二维数组转化为List类型
    private static List<String> constract(char[][] chars) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < chars.length; i++) {
            list.add(new String(chars[i]));
        }
        return list;
    }
    private static boolean vaild(char[][] chars, int row, int col) {
        //只用判断当前即可
        //判断列是否满足条件
        for (int i = 0; i< row; i++) {
            if (chars[i][col]=='Q')
                return false;
        }
        //判断右上方是否满足条件
        for(int i=row-1,j=col+1; i>=0 && j<chars.length;i--,j++){
            if (chars[i][j]=='Q')
                return false;
        }
        //判断左上方是否满足条件
        for (int i=row-1,j=col-1;i>=0&&j>=0;i--,j--){
            if (chars[i][j]=='Q')
                return false;
        }
        return true;
    }
}

2.活字印刷(子集问题)

题目描述 : 找出所有的组合。

输入:"AAB"
输出:8
解释:可能的序列为 "A", "B", "AA", "AB", "BA", "AAB", "ABA", "BAA"。

代码

public int numTilePossibilities(String tiles) {
        boolean[] booleans = new boolean[tiles.length()];
        Set<String> set = new HashSet<>();
        dfs(booleans,set,tiles,new StringBuilder());
        return set.size()-1;
    }

private void dfs(boolean[] booleans, Set<String> set, String tiles, StringBuilder builder) {
    set.add(builder.toString());
    for (int i = 0; i < tiles.length(); i++) {
        if (!booleans[i]){
            booleans[i]=true;
            builder.append(tiles.charAt(i));
            dfs(booleans,set,tiles,builder);
            builder.deleteCharAt(builder.length()-1);
            booleans[i]=false;
        }
    }
}

3.子集

题目描述: 输入一个不包含重复数字的数组,输出这些数字的所有子集。

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

代码

List<List<Integer>> lists = new ArrayList<>();
public  List<List<Integer>> subsets(int[] nums) {
    if (nums==null){
        return lists;
    }
    List<Integer> list = new ArrayList<>();
    backtrace(nums,list,0);
    return lists;
}

private  void backtrace(int[] nums,List<Integer> list, int i) {
    lists.add(new ArrayList<>(list));
    for (int j = i; j <nums.length; j++) {
        list.add(nums[j]);
        backtrace(nums,list,j+1);
        list.remove(list.size()-1);
    }
}
  • 每个数字使用完之后,就不会再被使用。所以下次回溯就从 j+1 开始。

在这里插入图片描述

4.组合

给定两个整数 nk,返回 1 … n 中所有可能的 k 个数的组合。

输入: n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
  • 这是最常见的回溯问题,k限制了树的高度,n限制了树的宽度。

在这里插入图片描述

代码

public List<List<Integer>> combine(int n, int k){
        List<List<Integer>> lists = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        backtrace(lists,list,k,1,n);
        return lists;
    }

private static void backtrace(List<List<Integer>> lists, List<Integer> list, int k, int i,int n) {
    if (list.size()==k){
        lists.add(new ArrayList<>(list));
    }
    for (int j = i; j <=n; j++) {
        list.add(j);
        //j+1 排除已选择的数
        backtrace(lists,list,k,j+1,n);
        list.remove(list.size()-1);
    }
}

5.排列

如果数组中包含重复元素,一般使用 boolean数组 来判断数字是否使用过。

boolean[] m = new boolean[len];
if (i>0 && nums[i]==nums[i-1] && !m[i-1])
    continue;

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

在这里插入图片描述

代码

List<List<Integer>> lists = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
    ArrayList<Integer> list = new ArrayList<>();
    bfs(nums,list);
    return lists;
}
private  void bfs(int[] nums,ArrayList<Integer> list) {
    if (list.size()==nums.length) {
        lists.add(new ArrayList<>(list));
        return;
    }
    for (int i = 0; i < nums.length; i++) {
        if (list.contains(nums[i]))
            continue;
        list.add(nums[i]);
        bfs(nums,list);
        //撤销选择 (移除最后一个元素)
        list.remove(list.size()-1);
    }
}

6.二维数组路径问题

在一个二维数组中从任一位置开始,可以往他的上下左右4个方向走,然后返回走过的路线中值最大的,0其实就相当于障碍物,不能往位置为0的地方走。

输入:grid = [[0,6,0],[5,8,7],[0,9,0]]
输出:24
解释:
[[0,6,0],
 [5,8,7],
 [0,9,0]]
一种收集最多黄金的路线是:9 -> 8 -> 7。

代码

public int getMaximumGold(int[][] grid) {
        int sum=0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j]==0)
                    continue;
                sum=Math.max(sum,bfs(grid,i,j));
            }
        }
        return sum;
    }
private int bfs(int[][] grid, int i, int j) {
    //边界条件
    if (i<0 || i>=grid.length || j<0 || j>=grid[0].length || grid[i][j]==0)
        return 0;
    int temp=grid[i][j];
    grid[i][j]=0;
    int up = bfs(grid, i - 1, j);  //向上
    int down=bfs(grid,i+1,j); //向下
    int left=bfs(grid,i,j-1); //向左
    int right=bfs(grid,i,j+1);  //向右
    //找到最大值
    int max=Math.max(Math.max(up,down),Math.max(left,right));
    grid[i][j]=temp;
    return grid[i][j]+max;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值