Comment compter dans le pire des cas en temps linéaire?

8

Cette question et cette question m'ont fait réfléchir un peu. Pour trier un tableau de longueur avec éléments uniques dans , nous devons être en mesure de stocker le nombre de valeurs dans le tableau. Il y a quelques suggestions, mais je cherche un moyen de le faire dans le pire des cas, le temps linéaire. Plus précisement:nkO(n+klogk)

Compte tenu de la liste de éléments avec éléments distincts, déterminer une liste de tuples de tous les éléments uniques tel que est le nombre d'éléments dans .AnkU={(xi,ci)}kxiAcixiA

Voici quelques idées (ratées) que j'ai eues et qui ont été suggérées:

  1. Arbre de recherche binaire équilibré - Avec cela, il faudra pour insérer dans l'arbre et augmenter les valeurs. Après les insertions, nous pourrions faire une traversée d'arbre dans . Ainsi, le temps total sort à qui est trop lent.O(logk)O(k)O(nlogk)
  2. Hash Map - Avec cela, nous pouvons obtenir inserts attendus et donc temps prévu . Cependant, ce n'est toujours pas le pire cas .O(1) O(n) O(n)
  3. Espace vide Mapping - Trouvez le minimum et maximum dans l' élément . Allouez (mais n'initialisez pas ) suffisamment de mémoire pour couvrir cette plage. Utilisez cette mémoire essentiellement comme une carte de hachage et incluez un hachage aléatoire afin que nous n'essayions pas d'accéder à la mémoire corrompue. Cette stratégie présente des problèmes. (1) Il est probabiliste avec une probabilité d'échec très très très faible, mais toujours pas garanti. L'utilisation de la mémoire comme celle-ci nous limite aux contraintes à virgule flottante ou entière.UNE
  4. Tableaux associatifs - Il existe de nombreux autres tableaux associatifs qui peuvent être utilisés, similaires aux cartes de hachage et aux BST, mais je n'en trouve pas qui correspondent à ces contraintes.

Peut-être qu'il y a une méthode évidente qui me manque, mais je pense aussi que cela pourrait ne pas être possible. Quelles sont vos pensées?

ryan
la source
3
Cela ne peut pas être fait dans le modèle de comparaison car le problème de la distinction des éléments a une borne inférieure de la complexité de l'arbre de décision . Ω(nJournaln)
John L.
@ Apass.Jack, oh c'est vrai. Une réduction insignifiante que je n'ai pas envisagée. Si vous l'écrivez comme une réponse rapide, je l'accepterai.
ryan
Pourquoi le HashMap n'est- il pas assuré de O (n) amorti ?
javadba
1
@javadba Par exemple, supposons que tous les éléments soient hachés à la même valeur.
John L.
Ah ok donc si c'est un hachage imparfait.
javadba

Réponses:

6

C'est une belle question.

Dans le modèle de comparaison ou, plus généralement, dans le modèle d'arbre de décision algébrique, le problème de la distinction des éléments a une limite inférieure de Θ(nJournaln)complexité temporelle dans le pire des cas, comme dit dans cet article Wikipedia . Il n'y a donc pas d'algorithme pour compter des éléments distincts en temps linéaire dans le pire des cas, même sans compter les duplicités.

Cependant, il n'est pas clair si cela peut être fait dans un autre modèle de calcul. Cela semble peu probable dans tout modèle de calcul déterministe raisonnable.

John L.
la source
Est-ce vraiment une instance du problème de la distinction des éléments? La simple génération des tuples ne nécessite pas la vérification de la distinction. Pas en désaccord, juste curieux.
mascoj du
2
Ce que je dis, c'est que si vous pouvez produire ce tuple d'éléments distincts, vous pouvez également résoudre le problème de la distinction des éléments en vérifiant si la taille du tuple est n.
John L.
Bon appel. Merci
mascoj
1

Il existe des algorithmes randomisés dont le temps d'exécution prévu est O(n); ou lorsque la probabilité que le temps d'exécution dure plus longtemps quecn est exponentiellement petit c.

En particulier, choisissez au hasard une fonction de hachage à 2 universels, puis utilisez-la pour hacher tous les éléments du tableau. Cela permet d'atteindre les temps de fonctionnement indiqués, si vous choisissez correctement la longueur de sortie du hachage 2 universels.

Comme autre exemple, vous pouvez créer un algorithme randomisé dont le temps d'exécution le plus défavorable est O(n) (il fonctionne toujours en temps linéaire, quoi qu'il arrive) et a une probabilité d'erreur d'au plus 1/2100. (Comment? Exécutez l'algorithme ci-dessus et terminez-le s'il s'exécute plus longtemps quecn étapes pour certains choisis de manière appropriée c.) En pratique, cela suffit, car la probabilité que votre ordinateur émette la mauvaise réponse en raison d'un rayon cosmique est déjà beaucoup plus élevée que 1/2100.

DW
la source
1

Votre approche 3 peut être sécurisée en utilisant une solution pour l'exercice 2.12 de Aho, Hopcroft et Ullman (1974) The Design and Analysis of Computer Algorithms comme décrit, par exemple, dans Utilisation de la mémoire non initialisée pour le plaisir et le profit .

Fondamentalement, en plus de votre tableau d'éléments N avec les décomptes, vous avez deux tableaux d'éléments N et un décompte auxiliaire pour créer un ensemble clairsemé indiquant lesquels des décomptes sont valides.

En pseudocode de type C:

uint* a = malloc(n);
uint* b = malloc(n);
uint* c = malloc(n);
uint len = 0;

get_count(uint x) {
    uint idx = a[x];
    return idx >= 0 && idx < len && b[idx] == x ? c[idx] : 0;
}

increment_count(uint x) {
    uint idx = a[x];
    if (idx < 0 || idx >= len || b[idx] != x) {
        idx = len;
        len++;
        a[x] = idx;
        b[idx] = x;
        c[idx] = 0;
    }
    c[idx]++;
}

L'implémentation pratique de l'ensemble clairsemé est discutée dans cette réponse StackOverflow .

Peter Taylor
la source
PS cpourrait être indexé sur xou idx, mais j'ai utilisé idxpour une meilleure localité de cache.
Peter Taylor
J'aime la réponse, mais je ne sais pas ce qui rend cela sûr. Bien que tout à fait improbable, vous ne pouviez pas accéder à une cellule de mémoire qui, par miracle, contient une entrée "valide" même si elle n'y a jamais été placée. Si vous venez de malchance avec malloc?
ryan
1
Cette solution ne fonctionne que si vous avez une mémoire suffisamment grande: si tous les éléments du tableau sont dans la plage 1..u, alors vous avez besoin d'au moins une mémoire de taille u. En pratique, cela est très limitatif. La façon dont nous créons un grand espace d'adressage virtuel dans la pratique consiste à utiliser des tables de pages, qui sont une structure de données arborescente; le matériel suit invisiblement les tableaux de pages pour nous. En conséquence, alors que nous pensons que l'accès à la mémoire prendO(1)temps, si vous travaillez dans un grand espace d'adressage mémoire, chaque accès mémoire prend en fait un temps logarithmique (pour traverser la structure de l'arborescence du tableau des pages).
DW
@ryan, consultez research.swtch.com/sparse pour savoir ce qui le rend sûr. C'est définitivement un truc très intelligent.
DW
@DW, 3u+1, mais si uest très grand, vous pouvez le faire à plusieurs niveaux, en utilisant un tableau de {a,b,c,len}structures pour cau lieu d'un tableau de nombres. Par exemple, si vous utilisez radix 512 pour que chacun des tableaux tienne dans une page (avec des pointeurs de 8 octets), vous pouvez aller jusqu'àu=5123=134217728 en utilisant tout au plus (3×512+1)(1+2k) mémoire où kest le nombre d'éléments distincts vus.
Peter Taylor