Idea básica e implementación del algoritmo de retroceso

idea básica

Un algoritmo de backtracking es un algoritmo recursivo que intenta resolver un problema probando diferentes alternativas. Su idea básica es comenzar a buscar a partir de posibles decisiones, si se encuentra que este camino no puede obtener una respuesta efectiva, volver a la capa anterior para elegir otra posible decisión y repetir los pasos anteriores.

Específicamente, el algoritmo de backtracking atraviesa el espacio de solución del problema a través de la búsqueda en profundidad. En el proceso de búsqueda primero en profundidad, cuando se busca en una determinada capa, se juzga si el estado del nodo de esta capa satisface la condición de acuerdo con las condiciones de restricción del problema. Si no se cumple la condición, se excluye el estado de este nodo y se rastrea el nodo principal del nodo actual para atravesarlo nuevamente. Si se cumple la condición, continúe buscando los nodos de la siguiente capa y repita las operaciones anteriores hasta que se encuentre una solución calificada o se atraviese todo el espacio de la solución.

Los algoritmos de backtracking son generalmente adecuados para resolver problemas combinatorios, problemas de permutación, problemas de búsqueda, etc. Dado que un cierto estado necesita ser reseleccionado y descartado constantemente durante el proceso de búsqueda, el algoritmo de backtracking tiene la característica de una complejidad espacial de O(N).

Debido a que la ruta de búsqueda del algoritmo de seguimiento inverso presenta una estructura de árbol, a veces se denomina algoritmo de "búsqueda de seguimiento inverso" o "búsqueda en profundidad + revocación de estado".

Marco recursivo para algoritmos de retroceso

El algoritmo de retroceso es un algoritmo recursivo típico, que generalmente debe implementarse mediante funciones recursivas. El marco recursivo del algoritmo de backtracking es generalmente de la siguiente forma:

def backtrack(candidate, state):
    if state == target:                        # 满足条件,输出结果
        output(candidate)
        return
    # 选择
    for choice in choices:
        make_choice(choice)                    # 做选择
        backtrack(candidate, state + 1)        # 递归进入下一层状态
        undo_choice(choice)                    # 撤销选择

Este marco recursivo generalmente consta de tres partes: selección, recursividad y deselección. Específicamente, en cada iteración, el algoritmo elige un estado o variable disponible para probar. Luego ingrese la siguiente capa de estado y continúe recursivamente. Si se encuentra que el estado actual no cumple con los requisitos durante el proceso recursivo, debe cancelar la selección anterior, volver al estado anterior y comenzar a buscar nuevamente.

En el algoritmo de retroceso, la atención se centra en cómo definir las operaciones de selección y deshacer. Estas operaciones suelen estar relacionadas con problemas específicos y deben definirse de acuerdo con las características del problema. La implementación del algoritmo de backtracking debe incluir los siguientes pasos:

  1. Determine si se ha alcanzado el estado objetivo. Cuando se encuentre un estado que cumpla con los requisitos, envíelo y finalice la búsqueda.
  2. Seleccione el estado o variable actual. Entre todas las secuencias opcionales, hijos opcionales, etc. del estado actual, selecciona un estado o variable que no ha sido buscada.
  3. Intente seleccionar un nuevo estado. Antes de ingresar al siguiente estado, debe intentar seleccionar un nuevo estado y completar las operaciones correspondientes, como agregar a la lista de selección.
  4. Ingrese recursivamente al siguiente nivel de estado. Vaya al siguiente nivel de estado y continúe buscando.
  5. Deseleccionar. Si el estado actual no cumple con los requisitos, debe deshacer todas las selecciones anteriores y las operaciones completadas, volver al estado anterior y continuar buscando otras secuencias opcionales o nodos secundarios.

problema de combinacion

LeetCode Pregunta 77: https://leetcode.cn/problems/combinations/
inserte la descripción de la imagen aquí

Primero defina dos variables globales para almacenar el conjunto de resultados y el conjunto de candidatos actual:

// 存放所有满足条件的结果
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 当前得到的候选集
List<Integer> temp = new ArrayList<Integer>();
  1. Condición de terminación recursiva: se encuentra una combinación de tamaño de subconjunto k, es decir
if (temp.size() == k) {
    
    
    ans.add(new ArrayList<Integer>(temp));
    return;
}
  1. elegir
for(int i = cur; i<=n;i++){
    
    
    temp.add(i);	// 当前元素加入候选集合
    dfs(i+1,n,k);	// 递归进入下一层状态
    temp.remove(temp.size() - 1);	// 当前元素移除候选集合
}

código completo

class Solution {
    
    
    List<List<Integer>> ans = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();

    public List<List<Integer>> combine(int n, int k) {
    
    
        dfs(1, n, k);
        return ans;
    }

    public void dfs(int cur, int n, int k) {
    
    
    		// 剪枝:可选的元素不足K个
        if (temp.size() + (n - cur + 1) < k) {
    
    
            return;
        }
        if (temp.size() == k) {
    
    
            ans.add(new ArrayList<Integer>(temp));
            return;
        }

        for(int i = cur; i<=n;i++){
    
    
            temp.add(i);
            dfs(i+1,n,k);
            temp.remove(temp.size() - 1);
        }
    }
}

suma combinada

LeetCode Pregunta 39: Suma combinada: https://leetcode.cn/problems/combination-sum/
inserte la descripción de la imagen aquí

Primero defina dos variables globales para almacenar el conjunto de resultados y el conjunto de candidatos actual:

// 存放所有满足条件的结果
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 当前得到的候选集
List<Integer> temp = new ArrayList<Integer>();
  1. Condición de terminación recursiva: la suma del conjunto de candidatos actual es mayor que el objetivo o se encuentra una combinación cuya suma es el objetivo, es decir
if(sum > target){
    
    
    return;
}
if(sum == target){
    
    
    res.add(new ArrayList<Integer>(temp));
    return;
}
  1. elegir
for(int i=index;i<candidates.length;i++){
    
    
    temp.add(candidates[i]);	// 当前元素加入候选集合
    sum+=candidates[i];			// 当前候选集总和
    dfs(candidates, target, i);	// 递归进入下一层状态
    // 当前元素移除候选集合
    sum -= temp.get(temp.size()-1);
    temp.remove(temp.size()-1);
}

código completo

class Solution {
    
    
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    int sum = 0;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
    
    
        dfs(candidates, target, 0);
        return res;
    }

    public void dfs(int[] candidates, int target, int index){
    
    
        if(sum > target){
    
    
            return;
        }
        if(sum == target){
    
    
            res.add(new ArrayList<Integer>(temp));
            return;
        }
        for(int i=index;i<candidates.length;i++){
    
    
            temp.add(candidates[i]);
            sum+=candidates[i];
            dfs(candidates, target, i);
            sum -= temp.get(temp.size()-1);
            temp.remove(temp.size()-1);
        }
    }
}

Deduplicación combinada

LeetCode Pregunta 40: Suma combinada II: https://leetcode.cn/problems/combination-sum-ii/
inserte la descripción de la imagen aquí

Sobre la base de la suma combinada de la pregunta anterior, defina si una matriz utilizada registra si el elemento ha pasado:

  1. Condición de terminación recursiva: la suma del conjunto de candidatos actual es mayor que el objetivo o se encuentra una combinación cuya suma es el objetivo, es decir
if(sum > target){
    
    
    return;
}
if(sum == target){
    
    
    res.add(new ArrayList<Integer>(temp));
    return;
}
  1. elegir
for(int i=cur;i<candidates.length;i++){
    
    
	//当前元素是否有效
    if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){
    
    
        continue;
    }
    temp.add(candidates[i]);	// 当前元素加入候选集合
    used[i] = true;		// 标记当前元素已使用
    sum += candidates[i];		// 当前候选集总和
    dfs(candidates,target,i+1,sum,used);		// 递归进入下一层状态
    // 当前元素移除候选集合
    sum -= temp.get(temp.size() - 1);
    temp.remove(temp.size() - 1);
    used[i] = false;
}
for(int i=index;i<candidates.length;i++){
    
    
    temp.add(candidates[i]);	// 当前元素加入候选集合
    sum+=candidates[i];			// 当前候选集总和
    dfs(candidates, target, i);	// 递归进入下一层状态
    // 当前元素移除候选集合
    sum -= temp.get(temp.size()-1);
    temp.remove(temp.size()-1);
}

código completo

class Solution {
    
    
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    
    
        Arrays.sort(candidates);
        boolean[] used = new boolean[candidates.length];
        dfs(candidates, target, 0, 0, used);
        return res;
    }

    public void dfs(int[] candidates, int target, int cur, int sum, boolean[] used){
    
    
        if(sum > target){
    
    
            return;
        }
        if(sum == target){
    
    
            res.add(new ArrayList<Integer>(temp));
            return;
        }

        for(int i=cur;i<candidates.length;i++){
    
    
            if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){
    
    
                continue;
            }
            temp.add(candidates[i]);
            used[i] = true;
            sum += candidates[i];
            dfs(candidates,target,i+1,sum,used);
            sum -= temp.get(temp.size() - 1);
            temp.remove(temp.size() - 1);
            used[i] = false;
        }
    }
}

Subconjunto

  1. Subconjuntos: https://leetcode.cn/problems/subsets/
    inserte la descripción de la imagen aquí
class Solution {
    
    
    List<List<Integer>> res = new ArrayList<List<Integer>>();

    Deque<Integer> temp = new ArrayDeque<Integer>();
    public List<List<Integer>> subsets(int[] nums) {
    
    

        dfs(nums, 0);

        return res;
    }

    public void dfs(int[] nums, int index){
    
    
        res.add(new ArrayList<Integer>(temp));

        for(int i=index;i<nums.length;i++){
    
    
            temp.addLast(nums[i]);
            dfs(nums, i+1);
            temp.removeLast();
        }
    }
}

gama completa

  1. Permutaciones completas: https://leetcode.cn/problems/permutations/
    inserte la descripción de la imagen aquí
class Solution {
    
    
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    List<Integer> temp = new ArrayList<Integer>();
    public List<List<Integer>> permute(int[] nums) {
    
    
        boolean[] used = new boolean[nums.length];
        dfs(nums, used, 0);
        return res;
    }

    public void dfs(int[] nums, boolean[] used, int index){
    
    
        if(temp.size() == nums.length){
    
    
            res.add(new ArrayList<Integer>(temp));
            return;
        }
        for(int i=0;i<nums.length;i++){
    
    
            if(used[i]){
    
    
                continue;
            }
            used[i] = true;
            temp.add(nums[i]);
            dfs(nums, used, i+1);
            used[i] = false;
            temp.remove(temp.size()-1);
        }
    }
}

Supongo que te gusta

Origin blog.csdn.net/weixin_43598687/article/details/131233780
Recomendado
Clasificación