Boyer-Moore Majority Vote Algorithm: Working, Explanation, and Applications

boyer moore majority vote algorithm and working of this algorithm

I find the Boyer-Moore Majority Vote algorithm to be one of those rare algorithms that feels almost too simple to work, yet it solves its problem with remarkable efficiency. It finds the majority element in a list, meaning the element that appears more than half the time, using only a single pass through the data and a constant amount of extra memory. I appreciate how it manages to avoid sorting or hashing entirely, relying instead on a clever cancellation strategy.

History and Background

I trace this algorithm to Robert S. Boyer and J Strother Moore, who introduced it in a 1981 technical report and later published it more formally, though it took some years for the algorithm to become widely known outside specialized circles. I should note that these are the same two researchers behind the famous Boyer-Moore string searching algorithm, a completely different algorithm despite sharing the same authors and a similar name, so I always double check which one is being discussed when I see “Boyer-Moore” mentioned without further qualification. The majority vote algorithm gained wider popularity as a teaching example for its unusual and non-obvious correctness argument, and it has since become a standard interview and coursework problem for demonstrating clever linear-time, constant-space techniques.

Problem Statement

The Boyer-Moore Majority Vote algorithm addresses the problem of finding the majority element in a sequence, defined as the element that occurs more than $\lfloor n/2 \rfloor$ times among $n$ total elements, if such an element exists. A naive approach would involve counting the occurrences of every distinct element, which typically requires a hash map and uses $O(n)$ extra space, or sorting the array first, which takes $O(n \log n)$ time. I want a solution that finds the majority element, if one exists, in a single linear pass using only constant extra space, which is exactly what this algorithm provides.

Core Concepts

Terms I rely on throughout my explanation:

How It Works

I break the algorithm into two phases: finding a candidate, and (optionally, if I do not already know a majority element definitely exists) verifying that candidate.

  1. Phase 1, finding a candidate: I initialize a candidate variable (empty or undefined at first) and a count variable set to 0.
  2. I scan through the sequence one element at a time. If count is 0, I set candidate to the current element.
  3. If the current element equals candidate, I increment count; otherwise, I decrement count.
  4. After processing all elements, candidate holds the potential majority element.
  5. Phase 2, verification: since the algorithm’s first phase always produces some candidate even if no true majority element exists, I do a second pass through the data, counting how many times candidate actually appears, and confirm it is a true majority element only if that count exceeds $\lfloor n/2 \rfloor$.

Working Principle

I think of the algorithm as playing out a kind of tug-of-war or voting process. Every time I see the current candidate again, I add a vote in its favor (incrementing the counter); every time I see something different, I subtract a vote (decrementing the counter), effectively letting that different element “cancel out” one occurrence of the candidate. When the counter hits zero, it means the votes so far have been perfectly balanced between the current candidate and everything else, so I discard that candidate and start fresh with whatever element I am looking at next. Because the true majority element appears more than half the time, no matter how the cancellations play out, there simply are not enough non-majority elements to cancel out every single occurrence of the majority element, so it must survive as the final candidate by the time I finish scanning the sequence.

Mathematical Foundation

I can formalize the cancellation intuition. Suppose the majority element $m$ appears $k$ times among $n$ total elements, where $k > \lfloor n/2 \rfloor$, meaning:

$$k > \frac{n}{2}$$

Every time the counter decreases, it is because I encountered an element different from the current candidate, effectively “cancelling” one occurrence of some element against another. In the worst case, every single occurrence of every non-majority element could be paired against and cancel out one occurrence of the majority element. The total number of non-majority elements is:

$$n – k < n – \frac{n}{2} = \frac{n}{2}$$

Since the number of non-majority elements is strictly less than $k$ (the number of majority element occurrences), there are not enough of them to cancel out every occurrence of $m$, even in the worst-case pairing scenario. This guarantees that at least one occurrence of $m$ remains “unpaired,” which is enough to ensure it ends up as (or contributes to) the final candidate.

Diagrams

flowchart TD
    A["Initialize candidate = none, count = 0"] --> B["Read next element x"]
    B --> C{"Is count equal to 0?"}
    C -->|Yes| D["Set candidate = x"]
    C -->|No| E{"Does x equal candidate?"}
    D --> E
    E -->|Yes| F["Increment count"]
    E -->|No| G["Decrement count"]
    F --> H{"More elements?"}
    G --> H
    H -->|Yes| B
    H -->|No| I["candidate holds potential majority element"]

Pseudocode

function BOYER_MOORE_FIND_CANDIDATE(sequence):
    candidate = null
    count = 0
    for x in sequence:
        if count == 0:
            candidate = x
        if x == candidate:
            count = count + 1
        else:
            count = count - 1
    return candidate

function VERIFY_MAJORITY(sequence, candidate):
    count = 0
    for x in sequence:
        if x == candidate:
            count = count + 1
    return count > length(sequence) / 2

function FIND_MAJORITY_ELEMENT(sequence):
    candidate = BOYER_MOORE_FIND_CANDIDATE(sequence)
    if VERIFY_MAJORITY(sequence, candidate):
        return candidate
    else:
        return "No majority element exists"

Step-by-Step Example

I will trace through the sequence [3, 3, 4, 2, 3, 4, 3, 3], which has 8 elements, so a majority element would need to appear more than 4 times.

  1. I start with candidate = null, count = 0.
  2. I read 3. Since count == 0, I set candidate = 3. Since 3 == candidate, I increment count to 1.
  3. I read 3. Since 3 == candidate, I increment count to 2.
  4. I read 4. Since 4 != candidate, I decrement count to 1.
  5. I read 2. Since 2 != candidate, I decrement count to 0.
  6. I read 3. Since count == 0, I set candidate = 3 (unchanged). Since 3 == candidate, I increment count to 1.
  7. I read 4. Since 4 != candidate, I decrement count to 0.
  8. I read 3. Since count == 0, I set candidate = 3. Since 3 == candidate, I increment count to 1.
  9. I read 3. Since 3 == candidate, I increment count to 2.
  10. After the scan, candidate = 3.
  11. I verify: I count how many times 3 appears in the sequence, which is 5 times. Since 5 > 4 (half of 8), I confirm 3 is indeed the majority element.

Time Complexity

The candidate-finding phase makes a single pass through the sequence, examining each of the $n$ elements exactly once and performing constant-time work per element, giving $O(n)$ time. The optional verification phase also makes a single pass, again giving $O(n)$ time. Since these two phases run one after the other rather than nested, the total time complexity remains $O(n)$, regardless of whether a true majority element exists, and this holds for the best, average, and worst cases alike, since the algorithm always processes every element exactly twice (once per phase) with no early termination shortcuts that would change based on input arrangement.

Space Complexity

I only need a constant amount of extra space, $O(1)$, to store the candidate and count variables, regardless of how large the input sequence is. This is what makes the algorithm particularly attractive compared to a hash-map-based counting approach, which would require $O(k)$ space for $k$ distinct elements in the sequence.

Correctness Analysis

I proved the core correctness argument mathematically above: because the majority element occurs more than $n/2$ times, there are strictly fewer non-majority elements than majority elements, so even in the worst-case cancellation pattern, where every non-majority element is used to cancel out one occurrence of the majority element, at least one occurrence of the majority element remains uncancelled by the end of the scan. I should be careful to note that the first phase alone only guarantees a correct answer if I already know a majority element exists; if no true majority element exists, the algorithm still produces some candidate (whatever “survives” the cancellation process), but that candidate is not guaranteed to be a genuine majority element. This is exactly why the second verification phase matters whenever I am not certain in advance that a majority element is present: it confirms the candidate by explicit counting, providing a definitive correctness guarantee in all cases, not just the case where a majority element is known to exist.

Advantages

Disadvantages

Applications

I find this algorithm useful whenever I need to detect a dominant value within a large stream or array efficiently, such as in distributed systems for identifying a majority “vote” or consensus value among many replicas or nodes, in data stream processing for detecting an element that dominates a stream without needing to store the entire stream, in bioinformatics for finding a dominant pattern within genetic sequence data, and as a classic building block in various voting and consensus algorithms in distributed computing, where confirming that more than half the participants agree on a value is a common requirement.

Implementation in C

#include <stdio.h>

int find_candidate(int arr[], int n) {
    int candidate = 0;
    int count = 0;

    for (int i = 0; i < n; i++) {
        if (count == 0) {
            candidate = arr[i];
        }
        if (arr[i] == candidate) {
            count++;
        } else {
            count--;
        }
    }

    return candidate;
}

int verify_majority(int arr[], int n, int candidate) {
    int count = 0;
    for (int i = 0; i < n; i++) {
        if (arr[i] == candidate) {
            count++;
        }
    }
    return count > n / 2;
}

int main() {
    int arr[] = {3, 3, 4, 2, 3, 4, 3, 3};
    int n = sizeof(arr) / sizeof(arr[0]);

    int candidate = find_candidate(arr, n);

    if (verify_majority(arr, n, candidate)) {
        printf("Majority element found: %d\n", candidate);
    } else {
        printf("No majority element exists.\n");
    }

    return 0;
}

Sample Input and Output

Running the program on the array {3, 3, 4, 2, 3, 4, 3, 3} (8 elements) produces the output Majority element found: 3, matching the manual trace I walked through earlier, since 3 appears 5 times out of 8, exceeding the required threshold of more than 4 occurrences. If I instead ran the program on an array like {1, 2, 3, 4} with no repeated majority element, the candidate-finding phase would still produce some candidate, but the verification phase would correctly report No majority element exists. since no single value would appear more than twice (more than half of 4).

Optimization Techniques

I have found a few practical refinements worth considering:

Common Mistakes

I often see the mistake of skipping the verification phase entirely, even when the input is not guaranteed to contain a true majority element, which leads to silently returning an incorrect answer when no majority element actually exists. Another mistake is misunderstanding the cancellation logic and assuming the counter directly reflects the true count of the candidate within the whole sequence, when in fact the counter can be reset multiple times during the scan and does not correspond to the candidate’s actual frequency until the verification phase explicitly counts it. I also see confusion between this majority vote algorithm and the unrelated Boyer-Moore string searching algorithm, given the shared author names and similar naming, despite the two solving completely different problems with entirely different mechanisms.

Further Reading

Exit mobile version