32 votes

Élément majoritaire - parties d'un tableau

J'ai un tableau, rempli d'entiers. Mon travail consiste à trouver rapidement l'élément majoritaire pour n'importe quelle partie d'un tableau, et je dois le faire... temps log n Je ne suis pas linéaire, mais avant, je peux prendre le temps de préparer le tableau.

Par exemple :

1 5 2 7 7 7 8 4 6

Et des questions :

[4, 7] renvoie à 7

[4, 8] renvoie à 7

[1, 2] renvoie à 0 (pas d'élément majoritaire), et ainsi de suite...

J'ai besoin d'avoir une réponse pour chaque requête, si possible, elle doit s'exécuter rapidement.

Pour la préparation, je peux utiliser Temps O(n log n)

16voto

Evgeny Kluev Points 16685

O(log n) requêtes et O(n log n) prétraitement/espace pourrait être atteint en trouvant et en utilisant intervalles de majorité avec les propriétés suivantes :

  1. Pour chaque valeur à partir du tableau d'entrée, il peut y avoir un ou plusieurs intervalles de majorité (ou il peut n'y en avoir aucun si les éléments avec ces valeurs sont trop épars ; nous n'avons pas besoin de intervalles de majorité de longueur 1 car ils peuvent être utiles uniquement pour les intervalles de requête de taille 1 qui sont mieux traités comme un cas spécial).
  2. Si l'intervalle de requête se trouve complètement à l'intérieur d'une de ces intervalles de majorité correspondant valeur mai être l'élément majoritaire de cet intervalle d'interrogation.
  3. S'il n'y a pas de intervalle majoritaire contenant complètement l'intervalle de requête, correspondant valeur ne peut pas être l'élément majoritaire de cet intervalle d'interrogation.
  4. Chaque élément du tableau d'entrée est couvert par O(log n) intervalles de majorité .

En d'autres termes, le seul but de intervalles de majorité est de fournir O(log n) candidats d'éléments majoritaires pour tout intervalle de requête.

Cet algorithme utilise les éléments suivants structures de données :

  1. Liste des postes pour chaque valeur à partir du tableau d'entrée ( map<Value, vector<Position>> ). Ou bien unordered_map peut être utilisé ici pour améliorer les performances (mais nous devrons extraire toutes les clés et les trier pour que la structure n°3 soit remplie dans le bon ordre).
  2. Liste des intervalles de majorité pour chaque valeur ( vector<Interval> ).
  3. Structure de données pour le traitement des requêtes ( vector<small_map<Value, Data>> ). Où Data contient deux index du vecteur approprié de la structure #1 pointant vers les positions suivantes/précédentes des éléments avec les valeurs suivantes valeur . Mise à jour : Grâce à @justhalf, il est préférable de stocker en Data les fréquences cumulées des éléments avec des valeur . small_map peut être implémenté comme un vecteur trié de paires - le prétraitement ajoutera les éléments déjà dans l'ordre trié et la requête utilisera la fonction small_map uniquement pour la recherche linéaire.

Prétraitement :

  1. Scanner le tableau d'entrée et pousser la position actuelle vers le vecteur approprié dans la structure #1.
  2. Effectuez les étapes 3 4 pour chaque vecteur de la structure #1.
  3. Transformer une liste de positions en une liste de intervalles de majorité . Voir les détails ci-dessous.
  4. Pour chaque indice du tableau d'entrée couvert par l'un des éléments suivants intervalles de majorité insérer des données dans l'élément approprié de la structure n° 3 : valeur et les positions des éléments précédents/suivants avec ceci valeur (ou la fréquence cumulée de cette valeur ).

Une requête :

  1. Si la longueur de l'intervalle de requête est égale à 1, l'élément correspondant du tableau source est renvoyé.
  2. Pour le point de départ de l'intervalle de requête, on obtient l'élément correspondant du vecteur de la 3ème structure. Pour chaque élément de la carte, effectuer l'étape 3. Scanner tous les éléments de la carte correspondant au point final de l'intervalle de requête en parallèle avec cette carte pour permettre une complexité O(1) pour l'étape 3 (au lieu de O(log log n)).
  3. Si la carte correspondant au point final de l'intervalle de requête contient des correspondances valeur , calculez s3[stop][value].prev - s3[start][value].next + 1 . S'il est supérieur à la moitié de l'intervalle d'interrogation, on retourne valeur . Si les fréquences cumulatives sont utilisées à la place des indices suivant/précédent, calculez s3[stop+1][value].freq - s3[start][value].freq à la place.
  4. Si rien n'est trouvé à l'étape 3, renvoyer "Rien".

Partie principale de l'algorithme est d'obtenir intervalles de majorité de la liste des postes :

  1. Affecter poids à chaque position dans la liste : number_of_matching_values_to_the_left - number_of_nonmatching_values_to_the_left .
  2. Filtre uniquement poids dans un ordre strictement décroissant (avec avidité) vers le tableau "préfixe" : for (auto x: positions) if (x < prefix.back()) prefix.push_back(x); .
  3. Filtre uniquement poids dans un ordre strictement croissant (avec avidité, à rebours) vers le tableau "suffixe" : reverse(positions); for (auto x: positions) if (x > suffix.back()) suffix.push_back(x); .
  4. Balayez les tableaux "prefix" et "suffix" ensemble et trouvez les intervalles de chaque élément "prefix" à la place correspondante dans le tableau "suffix" et de chaque élément "suffix" à la place correspondante dans le tableau "prefix". (Si le poids de tous les éléments "suffixe" est inférieur à celui de l'élément "préfixe" donné ou si leur position n'est pas à droite de celui-ci, aucun intervalle n'est généré ; s'il n'y a pas d'élément "suffixe" ayant exactement le poids de l'élément "préfixe" donné, trouver l'élément "suffixe" le plus proche ayant un poids plus grand et étendre l'intervalle avec cette différence de poids vers la droite).
  5. Fusionner les intervalles qui se chevauchent.

Propriétés 1 3 pour intervalles de majorité sont garanties par cet algorithme. Quant à la propriété n°4, la seule façon que je puisse imaginer pour couvrir un élément avec un nombre maximum de intervalles de majorité c'est comme ça : 11111111222233455666677777777 . Ici, l'élément 4 est couvert par 2 * log n intervalles, donc cette propriété semble être satisfaite. Voir la preuve plus formelle de cette propriété à la fin de ce billet.

Exemple :

Pour le tableau d'entrée "0 1 2 0 0 1 1 0", les listes de positions suivantes seraient générées :

value  positions
    0  0 3 4 7
    1  1 5 6
    2  2

Positions pour la valeur 0 obtiendra les propriétés suivantes :

weights:    0:1 3:0 4:1 7:0
prefix:     0:1 3:0          (strictly decreasing)
suffix:     4:1 7:0          (strictly increasing when scanning backwards)
intervals:  0->4 3->7 4->0 7->3
merged intervals: 0-7

Positions pour la valeur 1 obtiendra les propriétés suivantes :

weights:    1:0  5:-2  6:-1
prefix:     1:0  5:-2
suffix:     1:0  6:-1
intervals:  1->none 5->6+1 6->5-1 1->none
merged intervals: 4-7

Structure de données des requêtes :

positions value next prev
        0     0    0    x
     1..2     0    1    0
        3     0    1    1
        4     0    2    2
        4     1    1    x
        5     0    3    2
    ...

Requête [0,4] :

prev[4][0]-next[0][0]+1=2-0+1=3
query size=5
3>2.5, returned result 0

Requête [2,5] :

prev[5][0]-next[2][0]+1=2-1+1=2
query size=4
2=2, returned result "none"

Notez qu'il n'y a aucune tentative d'inspecter l'élément "1" parce que son intervalle majoritaire ne comprend pas l'un ou l'autre de ces intervalles.

Preuve de la propriété n° 4 :

Intervalles de majorité sont construits de telle sorte que strictement plus d'un tiers de tous leurs éléments ont un correspondant valeur . Ce rapport est le plus proche de 1/3 pour les sous-réseaux tels que any*(m-1) value*m any*m par exemple, 01234444456789 .

Pour rendre cette preuve plus évidente, nous pouvons représenter chaque intervalle comme un point en 2D : chaque point de départ possible est représenté par l'axe horizontal et chaque point d'arrivée possible par l'axe vertical (voir le diagramme ci-dessous).

enter image description here

Tous les intervalles valides sont situés sur ou au-dessus de la diagonale. Le rectangle blanc représente tous les intervalles couvrant un élément du tableau (représenté par un intervalle de taille unitaire dans son coin inférieur droit).

Recouvrons ce rectangle blanc de carrés de taille 1, 2, 4, 8, 16, ... partageant le même coin inférieur droit. Cela divise la zone blanche en O(log n) zones similaires à la zone jaune (et un seul carré de taille 1 contenant un seul intervalle de taille 1 qui est ignoré par cet algorithme).

Comptons combien intervalles de majorité peut être placé en zone jaune. Un intervalle (situé le plus près du coin diagonal) occupe 1/4 des éléments appartenant à l'intervalle le plus éloigné du coin diagonal (et ce plus grand intervalle contient tous les éléments appartenant à n'importe quel intervalle de la zone jaune). Cela signifie que le plus petit intervalle contient strictement plus de 1/12e des éléments appartenant à l'intervalle le plus éloigné du coin diagonal. valeurs disponible pour toute la zone jaune. Donc, si nous essayons de placer 12 intervalles dans la zone jaune, nous n'aurons pas assez d'éléments pour les différentes zones. valeurs . La zone jaune ne peut donc pas contenir plus de 11 intervalles de majorité . Et le rectangle blanc ne peut contenir plus de 11 * log n intervalles de majorité . Preuve complétée.

11 * log n est une surestimation. Comme je l'ai dit plus tôt, il est difficile d'imaginer plus que 2 * log n intervalles de majorité couvrant un élément. Et même cette valeur est beaucoup plus grande que le nombre moyen de recouvrement. intervalles de majorité .

Mise en œuvre de C++11. Vous pouvez le voir sur idéone ou ici :

#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <functional>
#include <random>

constexpr int SrcSize = 1000000;
constexpr int NQueries = 100000;

using src_vec_t = std::vector<int>;
using index_vec_t = std::vector<int>;
using weight_vec_t = std::vector<int>;
using pair_vec_t = std::vector<std::pair<int, int>>;
using index_map_t = std::map<int, index_vec_t>;
using interval_t = std::pair<int, int>;
using interval_vec_t = std::vector<interval_t>;
using small_map_t = std::vector<std::pair<int, int>>;
using query_vec_t = std::vector<small_map_t>;

constexpr int None = -1;
constexpr int Junk = -2;

src_vec_t generate_e()
{ // good query length = 3
    src_vec_t src;
    std::random_device rd;
    std::default_random_engine eng{rd()};
    auto exp = std::bind(std::exponential_distribution<>{0.4}, eng);

    for (int i = 0; i < SrcSize; ++i)
    {
        int x = exp();
        src.push_back(x);
        //std::cout << x << ' ';
    }

    return src;
}

src_vec_t generate_ep()
{ // good query length = 500
    src_vec_t src;
    std::random_device rd;
    std::default_random_engine eng{rd()};
    auto exp = std::bind(std::exponential_distribution<>{0.4}, eng);
    auto poisson = std::bind(std::poisson_distribution<int>{100}, eng);

    while (int(src.size()) < SrcSize)
    {
        int x = exp();
        int n = poisson();

        for (int i = 0; i < n; ++i)
        {
            src.push_back(x);
            //std::cout << x << ' ';
        }
    }

    return src;
}

src_vec_t generate()
{
    //return generate_e();
    return generate_ep();
}

int trivial(const src_vec_t& src, interval_t qi)
{
    int count = 0;
    int majorityElement = 0; // will be assigned before use for valid args

    for (int i = qi.first; i <= qi.second; ++i)
    {
        if (count == 0)
            majorityElement = src[i];

        if (src[i] == majorityElement) 
           ++count;
        else 
           --count;
    }

    count = 0;
    for (int i = qi.first; i <= qi.second; ++i)
    {
        if (src[i] == majorityElement)
            count++;
    }

    if (2 * count > qi.second + 1 - qi.first)
        return majorityElement;
    else
        return None;
}

index_map_t sort_ind(const src_vec_t& src)
{
    int ind = 0;
    index_map_t im;

    for (auto x: src)
        im[x].push_back(ind++);

    return im;
}

weight_vec_t get_weights(const index_vec_t& indexes)
{
    weight_vec_t weights;

    for (int i = 0; i != int(indexes.size()); ++i)
        weights.push_back(2 * i - indexes[i]);

    return weights;
}

pair_vec_t get_prefix(const index_vec_t& indexes, const weight_vec_t& weights)
{
    pair_vec_t prefix;

    for (int i = 0; i != int(indexes.size()); ++i)
        if (prefix.empty() || weights[i] < prefix.back().second)
            prefix.emplace_back(indexes[i], weights[i]);

    return prefix;
}

pair_vec_t get_suffix(const index_vec_t& indexes, const weight_vec_t& weights)
{
    pair_vec_t suffix;

    for (int i = indexes.size() - 1; i >= 0; --i)
        if (suffix.empty() || weights[i] > suffix.back().second)
            suffix.emplace_back(indexes[i], weights[i]);

    std::reverse(suffix.begin(), suffix.end());
    return suffix;
}

interval_vec_t get_intervals(const pair_vec_t& prefix, const pair_vec_t& suffix)
{
    interval_vec_t intervals;
    int prev_suffix_index = 0; // will be assigned before use for correct args
    int prev_suffix_weight = 0; // same assumptions

    for (int ind_pref = 0, ind_suff = 0; ind_pref != int(prefix.size());)
    {
        auto i_pref = prefix[ind_pref].first;
        auto w_pref = prefix[ind_pref].second;

        if (ind_suff != int(suffix.size()))
        {
            auto i_suff = suffix[ind_suff].first;
            auto w_suff = suffix[ind_suff].second;

            if (w_pref <= w_suff)
            {
                auto beg = std::max(0, i_pref + w_pref - w_suff);

                if (i_pref < i_suff)
                    intervals.emplace_back(beg, i_suff + 1);

                if (w_pref == w_suff)
                    ++ind_pref;

                ++ind_suff;
                prev_suffix_index = i_suff;
                prev_suffix_weight = w_suff;
                continue;
            }
        }

        // ind_suff out of bounds or w_pref > w_suff:
        auto end = prev_suffix_index + prev_suffix_weight - w_pref + 1;
        // end may be out-of-bounds; that's OK if overflow is not possible
        intervals.emplace_back(i_pref, end);
        ++ind_pref;
    }

    return intervals;
}

interval_vec_t merge(const interval_vec_t& from)
{
    using endpoints_t = std::vector<std::pair<int, bool>>;
    endpoints_t ep(2 * from.size());

    std::transform(from.begin(), from.end(), ep.begin(),
                   [](interval_t x){ return std::make_pair(x.first, true); });

    std::transform(from.begin(), from.end(), ep.begin() + from.size(),
                   [](interval_t x){ return std::make_pair(x.second, false); });

    std::sort(ep.begin(), ep.end());

    interval_vec_t to;
    int start; // will be assigned before use for correct args
    int overlaps = 0;

    for (auto& x: ep)
    {
        if (x.second) // begin
        {
            if (overlaps++ == 0)
                start = x.first;
        }
        else // end
        {
            if (--overlaps == 0)
                to.emplace_back(start, x.first);
        }
    }

    return to;
}

interval_vec_t get_intervals(const index_vec_t& indexes)
{
    auto weights = get_weights(indexes);
    auto prefix = get_prefix(indexes, weights);
    auto suffix = get_suffix(indexes, weights);
    auto intervals = get_intervals(prefix, suffix);
    return merge(intervals);
}

void update_qv(
    query_vec_t& qv,
    int value,
    const interval_vec_t& intervals,
    const index_vec_t& iv)
{
    int iv_ind = 0;
    int qv_ind = 0;
    int accum = 0;

    for (auto& interval: intervals)
    {
        int i_begin = interval.first;
        int i_end = std::min<int>(interval.second, qv.size() - 1);

        while (iv[iv_ind] < i_begin)
        {
            ++accum;
            ++iv_ind;
        }

        qv_ind = std::max(qv_ind, i_begin);

        while (qv_ind <= i_end)
        {
            qv[qv_ind].emplace_back(value, accum);

            if (iv[iv_ind] == qv_ind)
            {
                ++accum;
                ++iv_ind;
            }

            ++qv_ind;
        }
    }
}

void print_preprocess_stat(const index_map_t& im, const query_vec_t& qv)
{
    double sum_coverage = 0.;
    int max_coverage = 0;

    for (auto& x: qv)
    {
        sum_coverage += x.size();
        max_coverage = std::max<int>(max_coverage, x.size());
    }

    std::cout << "             size = " << qv.size() - 1 << '\n';
    std::cout << "           values = " << im.size() << '\n';
    std::cout << "     max coverage = " << max_coverage << '\n';
    std::cout << "     avg coverage = " << sum_coverage / qv.size() << '\n';
}

query_vec_t preprocess(const src_vec_t& src)
{
    query_vec_t qv(src.size() + 1);
    auto im = sort_ind(src);

    for (auto& val: im)
    {
        auto intervals = get_intervals(val.second);
        update_qv(qv, val.first, intervals, val.second);
    }

    print_preprocess_stat(im, qv);
    return qv;
}

int do_query(const src_vec_t& src, const query_vec_t& qv, interval_t qi)
{
    if (qi.first == qi.second)
        return src[qi.first];

    auto b = qv[qi.first].begin();
    auto e = qv[qi.second + 1].begin();

    while (b != qv[qi.first].end() && e != qv[qi.second + 1].end())
    {
        if (b->first < e->first)
        {
            ++b;
        }
        else if (e->first < b->first)
        {
            ++e;
        }
        else // if (e->first == b->first)
        {
            // hope this doesn't overflow
            if (2 * (e->second - b->second) > qi.second + 1 - qi.first)
                return b->first;

            ++b;
            ++e;
        }
    }

    return None;
}

int main()
{
    std::random_device rd;
    std::default_random_engine eng{rd()};
    auto poisson = std::bind(std::poisson_distribution<int>{500}, eng);
    int majority = 0;
    int nonzero = 0;
    int failed = 0;

    auto src = generate();
    auto qv = preprocess(src);

    for (int i = 0; i < NQueries; ++i)
    {
        int size = poisson();
        auto ud = std::uniform_int_distribution<int>(0, src.size() - size - 1);
        int start = ud(eng);
        int stop = start + size;
        auto res1 = do_query(src, qv, {start, stop});
        auto res2 = trivial(src, {start, stop});
        //std::cout << size << ": " << res1 << ' ' << res2 << '\n';

        if (res2 != res1)
            ++failed;

        if (res2 != None)
        {
            ++majority;

            if (res2 != 0)
                ++nonzero;
        }
    }

    std::cout << "majority elements = " << 100. * majority / NQueries << "%\n";
    std::cout << " nonzero elements = " << 100. * nonzero / NQueries << "%\n";
    std::cout << "          queries = " << NQueries << '\n';
    std::cout << "           failed = " << failed << '\n';

    return 0;
}

Travail connexe :

Comme indiqué dans autre réponse à cette question Il existe d'autres travaux où ce problème est déjà résolu : "Range majority in constant time and linear space" par S. Durocher, M. He, I Munro, P.K. Nicholson, M. Skala .

L'algorithme présenté dans ce document a une meilleure complexité asymptotique pour le temps d'interrogation : O(1) au lieu de O(log n) et pour l'espace : O(n) au lieu de O(n log n) .

Une meilleure complexité spatiale permet à cet algorithme de traiter des ensembles de données plus importants (par rapport à l'algorithme proposé dans cette réponse). Moins de mémoire nécessaire pour les données prétraitées et un modèle d'accès aux données plus régulier, très probablement, permettent à cet algorithme de prétraiter les données plus rapidement. Mais ce n'est pas aussi facile avec le temps de requête...

Supposons que nous ayons des données d'entrée les plus favorables à l'algorithme de l'article : n=1000000000 (il est difficile d'imaginer un système avec plus de 10..30 gigaoctets de mémoire, en 2013).

L'algorithme proposé dans cette réponse doit traiter jusqu'à 120 (ou 2 limites de requête * 2 * log n) éléments pour chaque requête. Mais il effectue des opérations très simples, similaires à la recherche linéaire. Et il accède séquentiellement à deux zones de mémoire contiguës, ce qui le rend favorable au cache.

L'algorithme de l'article doit effectuer jusqu'à 20 opérations (ou 2 limites de requête * 5 candidats * 2 niveaux d'arbre d'ondelettes) pour chaque requête. C'est 6 fois moins. Mais chaque opération est plus complexe. Chaque requête pour la représentation succincte des compteurs de bits contient elle-même une recherche linéaire (ce qui signifie 20 recherches linéaires au lieu d'une). Le pire, c'est que chacune de ces opérations doit accéder à plusieurs zones de mémoire indépendantes (à moins que la taille de la requête et donc du quadruple soit très petite), ce qui fait que la requête n'est pas favorable au cache. Ce qui signifie que chaque requête (bien qu'il s'agisse d'une opération en temps constant) est assez lente, probablement plus lente que l'algorithme proposé ici. Si nous diminuons la taille du tableau d'entrée, les chances que l'algorithme proposé ici soit plus rapide augmentent.

L'inconvénient pratique de l'algorithme dans le document est l'arbre d'ondelettes et la mise en œuvre succincte du compteur de bits. Les mettre en œuvre à partir de zéro peut prendre beaucoup de temps. L'utilisation d'une implémentation préexistante n'est pas toujours pratique.

9voto

flup Points 12250

l'astuce

Lorsque vous recherchez un élément majoritaire, vous pouvez écarter les intervalles qui n'ont pas d'élément majoritaire. Voir Trouver l'élément majoritaire dans un tableau . Cela vous permet de résoudre ce problème très simplement.

préparation

Au moment de la préparation, continuez à diviser récursivement le tableau en deux moitiés et stockez ces intervalles de tableau dans un arbre binaire. Pour chaque nœud, comptez l'occurrence de chaque élément dans l'intervalle du tableau. Vous avez besoin d'une structure de données qui offre O(1) insertions et lectures. Je suggère d'utiliser un unsorted_multiset, qui en moyenne se comporte comme nécessaire (mais dans le pire des cas les insertions sont linéaires). Vérifiez également si l'intervalle a un élément majoritaire et stockez-le si c'est le cas.

temps de fonctionnement

Au moment de l'exécution, lorsqu'on vous demande de calculer l'élément majoritaire pour une plage, plongez dans l'arbre pour calculer l'ensemble des intervalles qui couvrent exactement la plage donnée. Utilisez l'astuce pour combiner ces intervalles.

Si nous avons un intervalle de tableau 7 5 5 7 7 7 avec l'élément majoritaire 7 on peut séparer et rejeter 5 5 7 7 puisqu'il n'a pas d'élément majoritaire. En fait, les cinq ont englouti deux des sept. Ce qui reste est un tableau 7 7 o 2x7 . Appelez ce numéro 2 el compte de la majorité de l'élément majoritaire 7 :

En compte de la majorité d'un élément majoritaire d'un intervalle de tableau est la nombre d'occurrences de l'élément majoritaire moins les occurrences combinées de tous les autres éléments.

Utilisez les règles suivantes pour combiner les intervalles afin de trouver l'élément majoritaire potentiel :

  • Ecarter les intervalles qui n'ont pas d'élément majoritaire
  • Il est facile de combiner deux tableaux ayant le même élément majoritaire, il suffit d'additionner les nombres majoritaires des éléments. 2x7 y 3x7 devenir 5x7
  • Lorsque l'on combine deux tableaux avec des éléments majoritaires différents, c'est le nombre majoritaire le plus élevé qui l'emporte. Soustrayez le nombre majoritaire le plus faible du nombre majoritaire le plus élevé pour obtenir le nombre majoritaire résultant. 3x7 y 2x3 devenir 1x7 .
  • Si leurs éléments majoritaires sont différents mais que leurs nombres majoritaires sont égaux, ne tenez pas compte des deux tableaux. 3x7 y 3x5 s'annulent mutuellement.

Lorsque tous les intervalles ont été éliminés ou combinés, soit il ne reste rien, auquel cas il n'y a pas d'élément majoritaire. Soit vous avez un intervalle combiné contenant un élément majoritaire potentiel. Recherchez et ajoutez le nombre d'occurrences de cet élément dans tous les intervalles du tableau (y compris les intervalles précédemment rejetés) pour vérifier s'il s'agit vraiment de l'élément majoritaire.

exemple

Pour le tableau 1,1,1,2,2,3,3,2,2,2,3,2,2 vous obtenez l'arbre (nombre de majorités x élément majoritaire entre parenthèses).

                        1,1,1,2,2,3,3,2,2,2,3,2,2    
                                  (1x2)
                      /                           \
             1,1,1,2,2,3,3                       2,2,2,3,2,2
                                                    (4x2)
            /              \                   /            \
        1,1,1,2           2,3,3            2,2,2             3,2,2
         (2x1)            (1x3)            (3x2)             (1x2)
        /     \          /    \            /    \            /    \
     1,1      1,2       2,3     3        2,2     2        3,2      2
    (1x1)                     (1x3)     (2x2)  (1x2)             (1x2)
    /   \     /  \     /   \            /  \             /   \
   1     1   1   2    2    3           2    2           3     2
(1x1) (1x1)(1x1)(1x2)(1x2)(1x3)       (1x2)(1x2)       (1x3) (1x2)     

L'intervalle [5,10] (1-indexé) est couvert par l'ensemble des intervalles 2,3,3 (1x3), 2,2,2 (3x2). Ils ont des éléments majoritaires différents. En soustrayant leurs éléments majoritaires, il vous reste 2x2. Donc 2 est l'élément majoritaire potentiel. Recherchez et additionnez les occurrences réelles de 2 dans les tableaux : 1+3 = 4 sur 6. 2 est l'élément majoritaire.

L'intervalle [1,10] est couvert par l'ensemble des intervalles 1,1,1,2,2,3,3 (sans élément majoritaire) et 2,2,2 (3x2). Ne tenez pas compte du premier intervalle puisqu'il n'a pas d'élément majoritaire, donc 2 est l'élément majoritaire potentiel. Additionnez le nombre d'occurrences de 2 dans tous les intervalles : 2+3 = 5 sur 10. Il n'y a pas d'élément majoritaire.

3voto

flup Points 12250

En fait, cela peut être fait en temps constant et dans un espace linéaire ( !).

Véase http://cs.stackexchange.com/questions/16671/range-majority-queries-most-freqent-element-in-range y S. Durocher, M. He, I Munro, P.K. Nicholson, M. Skala, Range majority in constant time and linear space, Information and Computation 222 (2013) 169-179, Elsevier.

Leur temps de préparation est O(n log n), l'espace nécessaire est O(n) et les requêtes sont O(1). C'est un article théorique et je ne prétends pas tout comprendre mais cela semble loin d'être impossible à mettre en œuvre. Ils utilisent arbres d'ondelettes .

Pour une mise en œuvre des arbres d'ondelettes, voir https://github.com/fclaude/libcds

0voto

klm123 Points 3001

Si vous disposez d'une mémoire illimitée, vous pouvez et une gamme de données limitée (comme les int courts) le faire même en Temps O(N) .

  1. Parcourez le tableau et comptez le nombre de 1, 2, 3, eta (nombre d'entrées pour chaque valeur que vous avez dans le tableau). Vous aurez besoin d'un tableau X supplémentaire avec des éléments sizeof(YouType) pour cela.
  2. Passez par le tableau X et trouvez le maximum.

Au total, O(1) + O(N) opérations.


Vous pouvez également vous limiter à O(N) mémoire, si vous utilisez map au lieu de array X. Mais alors vous devrez trouver l'élément à chaque itération à l'étape 1. Vous aurez donc besoin de O(N*log(N)) temps au total.

-1voto

Shrikant Points 64

Vous pouvez utiliser MAX Heap, avec la fréquence du nombre comme facteur décisif pour garder la propriété Max Heap, Je voulais dire, par exemple, pour le tableau d'entrée suivant

1 5 2 7 7 7 8 4 6 5

Heap would have all distinct elements with their frequency associated with them
    Element = 1  Frequency = 1,
    Element = 5  Frequency = 2,
    Element = 2  Frequency = 1,
    Element = 7  Frequency = 3,
    Element = 8  Frequency = 1,
    Element = 4  Frequency = 1,
    Element = 6  Frequency = 1

Comme son tas MAX, l'élément 7 avec la fréquence 3 serait au niveau de la racine, Vérifiez simplement si la plage d'entrée contient cet élément, si oui, c'est la réponse ; si non, allez dans le sous-arbre gauche ou le sous-arbre droit selon la plage d'entrée et effectuez les mêmes vérifications.

O(N) ne serait nécessaire qu'une seule fois lors de la création d'un tas, mais une fois celui-ci créé, la recherche sera efficace.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X