Revisitando o Método de Votação Moore de Estrutura de Dados e Algoritmo

prefácio

Em estatística, a moda é um importante indicador de tendência central, que representa o valor que ocorre com mais frequência em um conjunto de dados. Além disso, em um conjunto, se um elemento aparece mais do que a soma de todos os outros elementos, então é chamado de modo absoluto do conjunto (equivalentemente, o modo absoluto tem mais ocorrências do que a metade do número total de elementos). Por exemplo, no conjunto de dados {1,2,3,3,4}, 3 é a moda porque aparece duas vezes, enquanto os demais valores aparecem apenas uma vez, mas não há moda absoluta .

Encontrar a moda pode nos ajudar a entender as principais características e distribuição dos dados e, em alguns casos, também pode ser usado como um valor representativo ou aproximado dos dados. Por exemplo,

  • Nas pesquisas de opinião, podemos nos concentrar em qual candidato tem mais apoio;
  • Nas análises de produtos, podemos prestar atenção em quais classificações ocupam a maioria;
  • No processamento de imagem, podemos estar interessados ​​em qual valor de cor ou escala de cinza ocorre com mais frequência.

Existem diferentes métodos e algoritmos para encontrar a maioria e, neste artigo, apresentaremos dois métodos comuns: força bruta e votação de Moore.

  • O método de força bruta conta o número de ocorrências de cada elemento por travessia ou hash e, em seguida, encontra o elemento correspondente ao valor máximo. Este método é simples e intuitivo, mas requer espaço adicional para armazenar cada elemento e sua frequência, e possui alta complexidade temporal ou espacial.
  • O método de votação Moore mantém um elemento candidato e um contador, atualiza o elemento candidato e o contador ao percorrer a matriz e, finalmente, verifica se o elemento candidato é a maioria. Esse método aproveita habilmente a condição de que haja um elemento que represente mais da metade (ou outros limites) na matriz, não exija espaço adicional e tenha uma complexidade de tempo baixa, mas esse algoritmo não pode encontrar o modo geral , pode encontrar o modo absoluto .

Este artigo apresentará os princípios, a implementação, a expansão e as limitações do método de violência e do método de votação de Moore, além de fornecer perguntas e respostas relacionadas ao LeetCode.

1. A Lei da Violência

O método de força bruta é um método simples e intuitivo para encontrar a maioria, sua ideia básica é percorrer cada elemento do array, contar o número de ocorrências do elemento e então encontrar o elemento correspondente ao valor máximo. Essa abordagem pode ser implementada de duas maneiras: uma com loops aninhados e outra com uma tabela hash.

1.1 Loops aninhados

O método de loops aninhados é percorrer cada elemento da matriz no loop externo, contar o número de ocorrências do elemento no loop interno e compará-lo com o valor máximo atual. Se o elemento ocorrer mais do que o máximo atual, atualize o máximo e o modo. Este método não requer espaço adicional, mas tem uma complexidade de tempo maior de O(n^2).

Aqui está um exemplo de código Java:

// 嵌套循环方法
public static int majorityElement1(int[] nums) {
    
    
    // 初始化最大值和众数
    int maxCount = 0;
    Integer majority = null;
    // 外层循环遍历数组
    for (int i = 0; i < nums.length; i++) {
    
    
        // 初始化当前元素出现次数为0
        int count = 0;
        // 内层循环统计当前元素出现次数
        for (int j = 0; j < nums.length; j++) {
    
    
            if (nums[j] == nums[i]) {
    
    
                count++;
            }
        }
        // 如果当前元素出现次数超过最大值,则更新最大值和众数
        if (count > maxCount) {
    
    
            maxCount = count;
            majority = nums[i];
        }
    }
    return majority;
}

1.2 Tabela de hash

A abordagem da tabela hash (ou dicionário) é utilizar uma estrutura de dados para armazenar cada elemento e sua contagem de ocorrências. Ao percorrer o array, se o elemento não existir na tabela hash, ele é adicionado e sua contagem é inicializada em 1; se existir, sua contagem é incrementada em 1. Ao mesmo tempo, mantenha um valor máximo e um número de modo e atualize-os ao atualizar a tabela de hash. Essa abordagem requer espaço adicional O(n), mas tem uma complexidade de tempo menor de O(n).

Aqui está um exemplo de código Java:

// 哈希表方法
public static int majorityElement2(int[] nums) {
    
    
    // 初始化哈希表、最大值和众数
    HashMap<Integer, Integer> counter = new HashMap<>();
    int maxCount = 0;
    Integer majority = null;
    // 遍历数组
    for (int num : nums) {
    
    
        // 如果该元素在哈希表中不存在,则将其加入并初始化其次数为1;如果存在,则将其次数加1。
        counter.put(num, counter.getOrDefault(num, 0) + 1);
        // 如果该元素出现次数超过最大值,则更新最大值和众数。
        if (counter.get(num) > maxCount) {
    
    
            maxCount = counter.get(num);
            majority = num;
        }
    }
    return majority;
}

2. Método de votação de Moore

2.1 Princípio

O Algoritmo de Voto da Maioria de Boyer-Moore é um método eficiente e que economiza espaço para encontrar a maioria absoluta , que foi proposto por Robert S. Boyer e J Strother Moore em 1981.

O processo do método de votação de Moore é muito simples, imaginemos o processo de encontrar a maioria absoluta como uma eleição. Mantemos um m, representando o candidato atual, e depois mantemos um cnt. Para cada nova cédula, se votou no candidato atual, some cnt1, senão cntsubtraia 1 (talvez você possa imaginar que um torcedor fanático de B foi bater em um torcedor de A refeição, e então nenhum dos dois pode votar). Em particular, quando os votos são contados cnt=0, podemos pensar que ninguém tem vantagem no momento, então quem quer que seja o novo voto será o novo candidato.

Por exemplo, se 5 votos forem dados para 1,3,3,2,3 respectivamente, então:

voto candidato contar cnt
1 1 1
3 3 0
3 3 1
2 2 0
3 3 1

O último candidato restante é 3, que é a maioria absoluta.

A complexidade de tempo do método de votação de Moore é O(n), porque ele só precisa percorrer o array uma vez. A complexidade do espaço é O(1), pois apenas duas variáveis ​​são necessárias para acompanhar os candidatos e as contagens.

2.2 Extensão

A votação de Moore pode ser estendida para encontrar elementos que ocorrem mais de n/k vezes (n sendo o número de elementos).

  • Em primeiro lugar, o modo de n/k é apenas k-1 no máximo, porque a definição de modo significa que o número de ocorrências é maior que n/k. Se houver k no modo, então a soma de todos os elementos do modo deve ser maior que n. não coincidem. Portanto, podemos usar uma tabela hash para registrar k-1 candidatos e suas contagens correspondentes.
  • para cada novo elemento
    • se já estiver na tabela hash, incremente sua contagem;
    • Se não estiver na tabela de hash e a tabela de hash ainda não estiver cheia, adicione-a à tabela de hash e inicialize a contagem em 1;
    • Se não estiver na tabela de hash e a tabela de hash estiver cheia, diminua a contagem de todos os candidatos em 1 e remova os candidatos com uma contagem de 0.
  • Os últimos candidatos restantes são elementos que podem ocorrer mais de n/k vezes, mas precisam percorrer o array novamente para verificar se realmente atendem às condições.

O código de amostra Java é o seguinte:

// 输入:数组arr,整数k
// 输出:一个列表,包含所有出现次数超过n/k的元素
public List<Integer> mooreVoting(int[] arr, int k) {
    
    
  // 初始化一个哈希表,用来存储候选人和计数
  HashMap<Integer, Integer> candidates = new HashMap<>();
  // 遍历数组中的每个元素
  for (int x : arr) {
    
    
    // 如果x已经是候选人,则增加其计数
    if (candidates.containsKey(x)) {
    
    
      candidates.put(x, candidates.get(x) + 1);
    }
    // 如果x不是候选人,并且候选人还没有满k-1个,则将x加入候选人并初始化计数为1
    else if (candidates.size() < k - 1) {
    
    
      candidates.put(x, 1);
    }
    // 如果x不是候选人,并且候选人已经满了k-1个,则将所有候选人的计数减1,并删除那些计数为0的候选人
    else {
    
    
      for (Integer y : new ArrayList<>(candidates.keySet())) {
    
    
        candidates.put(y, candidates.get(y) - 1);
        if (candidates.get(y) == 0) {
    
    
          candidates.remove(y);
        }
      }
    }
  }

  // 初始化一个列表,用来存储最终结果
  List<Integer> result = new ArrayList<>();
  // 遍历剩下的候选人,验证它们是否真的超过n/k次出现
  for (Integer x : candidates.keySet()) {
    
    
    int count = 0;
    for (int y : arr) {
    
    
      if (x == y) {
    
    
        count++;
      }
    }
    if (count > arr.length / k) {
    
    
      result.add(x);
    }
  }

  // 返回结果列表
  return result;
}

2.3 Desvantagens e limitações

  • O método de votação Moore só pode encontrar elementos que aparecem mais do que uma certa porcentagem , mas não pode encontrar o elemento que aparece mais . Se nenhum elemento satisfizer esta condição proporcional, então a votação de Moore pode retornar uma lista vazia ou o elemento errado. Por exemplo, no conjunto de dados {1,1,2,3}, o elemento com mais ocorrências é 1, e o método de votação Moore só consegue encontrar números com razão maior que 1/2, mas não consegue encontrar números com uma razão <=1/2.
  • Outra limitação do método de votação de Moore é que ele requer um limite de porcentagem claro para determinar o número de candidatos. Se não houver tal limite, ou se este limite não for razoável, então o método de votação de Moore pode não encontrar o resultado correto.

3. Combate real do LeetCode

3.1 A maioria dos elementos

169. A maioria dos elementos

Dada uma nmatriz de tamanho nums, retorne a maioria de seus elementos. Um elemento majoritário é um elemento que ocorre mais de vezes na matriz ⌊ n/2 ⌋.

Você pode assumir que as matrizes não estão vazias e que sempre há uma maioria de elementos em uma determinada matriz.

public int majorityElement(int[] nums) {
    
    
    int m = nums[0], cnt = 1; // 初始化候选元素m和计数器cnt
    for (int i = 1; i < nums.length; i++) {
    
     // 遍历数组中的每个元素
        if (cnt == 0) m = nums[i]; // 如果计数器为零,更新候选元素为当前元素
        if (nums[i] == m) {
    
     // 如果当前元素等于候选元素
            cnt++; // 增加计数器
        } else {
    
     // 否则
            cnt--; // 减少计数器
        }
    }
    return m; // 返回最终的候选元素,它就是多数元素
}

3.2 A maioria dos elementos II

229. A maioria dos elementos II

Dado um array inteiro de tamanho n , encontre todos os ⌊ n/3 ⌋elementos que aparecem mais de vezes.

public List<Integer> majorityElement(int[] nums) {
    
    
    // 初始化一个哈希表,用来存储候选人和计数
    HashMap<Integer, Integer> candidates = new HashMap<>();
    int k = 3;
    // 遍历数组中的每个元素
    for (int x : nums) {
    
    
        // 如果x已经是候选人,则增加其计数
        if (candidates.containsKey(x)) {
    
    
            candidates.put(x, candidates.get(x) + 1);
        }
        // 如果x不是候选人,并且候选人还没有满k-1个,则将x加入候选人并初始化计数为1
        else if (candidates.size() < k - 1) {
    
    
            candidates.put(x, 1);
        }
        // 如果x不是候选人,并且候选人已经满了k-1个,则将所有候选人的计数减1,并删除那些计数为0的候选人
        else {
    
    
            for (Integer y : new ArrayList<>(candidates.keySet())) {
    
    
                candidates.put(y, candidates.get(y) - 1);
                if (candidates.get(y) == 0) {
    
    
                    candidates.remove(y);
                }
            }
        }
    }

    // 初始化一个列表,用来存储最终结果
    List<Integer> result = new ArrayList<>();
    // 遍历剩下的候选人,验证它们是否真的超过n/k次出现
    for (Integer x : candidates.keySet()) {
    
    
        int count = 0;
        for (int y : nums) {
    
    
            if (x == y) {
    
    
                count++;
            }
        }
        if (count > nums.length / k) {
    
    
            result.add(x);
        }
    }

    // 返回结果列表
    return result;
}

referência

  1. Notas de Estudo de Algoritmo (78): Votação de Moore

おすすめ

転載: blog.csdn.net/qq_23091073/article/details/129641989