Ideia básica e implementação do algoritmo de retrocesso

ideia básica

Um algoritmo de backtracking é um algoritmo recursivo que tenta resolver um problema tentando diferentes alternativas. Sua idéia básica é iniciar a busca a partir de possíveis decisões, caso se verifique que este caminho não consegue uma resposta efetiva, volte para a camada anterior para escolher outra possível decisão, e repita os passos anteriores.

Especificamente, o algoritmo de retrocesso percorre o espaço de solução do problema por meio da busca em profundidade. No processo de busca em profundidade, quando uma determinada camada é pesquisada, é julgado se o estado do nó dessa camada satisfaz a condição de acordo com as condições de restrição do problema. Se a condição não for atendida, o estado desse nó será excluído e o nó pai do nó atual será rastreado para percorrer novamente. Se a condição for satisfeita, continue a procurar os nós da próxima camada e repita as operações acima até que uma solução qualificada seja encontrada ou todo o espaço de soluções seja percorrido.

Algoritmos de backtracking são geralmente adequados para resolver problemas combinatórios, problemas de permutação, problemas de busca, etc. Como um determinado estado precisa ser constantemente reselecionado e descartado durante o processo de busca, o algoritmo de retrocesso tem como característica uma complexidade espacial de O(N).

Como o caminho de pesquisa do algoritmo de retrocesso apresenta uma estrutura de árvore, às vezes é chamado de algoritmo de "pesquisa de retrocesso" ou "pesquisa em profundidade + revogação de estado".

Estrutura recursiva para algoritmos de backtracking

O algoritmo de backtracking é um algoritmo recursivo típico, que geralmente precisa ser implementado usando funções recursivas. A estrutura recursiva do algoritmo de retrocesso é geralmente da seguinte 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)                    # 撤销选择

Essa estrutura recursiva geralmente consiste em três partes: seleção, recursão e desmarcação. Especificamente, em cada iteração, o algoritmo escolhe um estado ou variável disponível para tentar. Em seguida, entre na próxima camada de estado e continue a recursão. Se for constatado que o estado atual não atende aos requisitos durante o processo recursivo, será necessário cancelar a seleção anterior, retornar ao estado anterior e iniciar a busca novamente.

No algoritmo de retrocesso, o foco está em como definir operações de seleção e desfazer. Essas operações geralmente estão relacionadas a problemas específicos e precisam ser definidas de acordo com as características do problema. A implementação do algoritmo de backtracking deve incluir as seguintes etapas:

  1. Determine se o estado de destino foi alcançado. Quando um estado que atende aos requisitos for encontrado, gere-o e finalize a pesquisa.
  2. Selecione o estado ou variável atual. Entre todas as sequências opcionais, filhos opcionais, etc. do estado atual, seleciona um estado ou variável que não foi pesquisado.
  3. Tente selecionar um novo estado. Antes de entrar no próximo estado, você precisa tentar selecionar um novo estado e concluir as operações correspondentes, como adicionar à lista de seleção.
  4. Insira recursivamente o próximo nível de estado. Vá para o próximo nível de estado e continue pesquisando.
  5. Desmarcar. Se o estado atual não atender aos requisitos, você precisará desfazer todas as seleções anteriores e operações concluídas, retornar ao estado anterior e continuar a procurar outras sequências opcionais ou nós filhos.

problema de combinação

Pergunta LeetCode 77: https://leetcode.cn/problems/combinations/
insira a descrição da imagem aqui

Primeiro defina duas variáveis ​​globais para armazenar o conjunto de resultados e o conjunto de candidatos atual:

// 存放所有满足条件的结果
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 当前得到的候选集
List<Integer> temp = new ArrayList<Integer>();
  1. Condição de terminação recursiva: uma combinação de tamanho de subconjunto k é encontrada, ou seja
if (temp.size() == k) {
    
    
    ans.add(new ArrayList<Integer>(temp));
    return;
}
  1. escolher
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);
        }
    }
}

soma combinada

Questão LeetCode 39: Soma combinada: https://leetcode.cn/problems/combination-sum/
insira a descrição da imagem aqui

Primeiro defina duas variáveis ​​globais para armazenar o conjunto de resultados e o conjunto de candidatos atual:

// 存放所有满足条件的结果
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 当前得到的候选集
List<Integer> temp = new ArrayList<Integer>();
  1. Condição de terminação recursiva: a soma do conjunto candidato atual é maior que o alvo ou uma combinação cuja soma é o alvo é encontrada, ou seja
if(sum > target){
    
    
    return;
}
if(sum == target){
    
    
    res.add(new ArrayList<Integer>(temp));
    return;
}
  1. escolher
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);
        }
    }
}

desduplicação combinada

LeetCode Questão 40: Combinação Soma II: https://leetcode.cn/problems/combination-sum-ii/
insira a descrição da imagem aqui

Com base no somatório combinado da questão anterior, defina se um array usado registra se o elemento passou:

  1. Condição de terminação recursiva: a soma do conjunto candidato atual é maior que o alvo ou uma combinação cuja soma é o alvo é encontrada, ou seja
if(sum > target){
    
    
    return;
}
if(sum == target){
    
    
    res.add(new ArrayList<Integer>(temp));
    return;
}
  1. escolher
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/
    insira a descrição da imagem aqui
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();
        }
    }
}

matriz completa

  1. Permutações completas: https://leetcode.cn/problems/permutations/
    insira a descrição da imagem aqui
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);
        }
    }
}

Acho que você gosta

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