2013-02-20 1 views
3

J'ai fait quelques lectures sur la complexité temporelle, et j'ai les bases couvertes. Pour renforcer le concept, j'ai regardé une réponse que j'ai donnée récemment sur SO. La question est maintenant close, pour raison, mais ce n'est pas le but. Je n'arrive pas à comprendre quelle est la complexité de ma réponse, et la question était close avant que je puisse avoir un retour utile.Complexité temporelle de cet algorithme

The task était de trouver le premier caractère unique dans une chaîne. Ma réponse était quelque chose de simple:

public String firstLonelyChar(String input) 
{ 
    while(input.length() > 0) 
    { 
     int curLength = input.length(); 
     String first = String.valueOf(input.charAt(0)); 
     input = input.replaceAll(first, ""); 
     if(input.length() == curLength - 1) 
      return first; 
    } 
    return null; 
} 

Link to an ideone example

Ma première pensée était que puisqu'il regarde chaque personnage, puis regarde à chaque fois pendant replaceAll(), il serait O (n^2).

Cependant, cela m'a fait penser à la façon dont cela fonctionne réellement. Pour chaque caractère examiné, il supprime toutes les occurrences de ce caractère dans la chaîne. Donc, n diminue constamment. Comment cela compte-t-il? Est-ce que cela fait O (log n), ou y a-t-il quelque chose que je ne vois pas?

Ce que je vous demande:

Quelle est la complexité temporelle de l'algorithme comme il est écrit, et pourquoi?

Ce que je ne demande pas:

Je ne cherche pas des suggestions pour l'améliorer. Je comprends qu'il y a probablement de meilleurs moyens de le faire. J'essaie de mieux comprendre le concept de complexité du temps, de ne pas trouver la meilleure solution.

+1

*** ce *** est n ?? – UmNyobe

+1

La longueur de la chaîne d'entrée, j'imagine. – Geobits

+0

Je pense que cela dépend d'un certain nombre de choses: 1) 'longueur()' doit-elle compter chaque caractère de la chaîne à chaque fois, ou est-ce que la longueur est stockée séparément afin qu'elle puisse être retournée sans balayage linéaire? 2) Si 'longueur()' est linéaire, nous souvenons-nous au moins du résultat pour les appels ultérieurs? 3) Quel type de gestion de la mémoire est utilisé par la classe - est-ce que l'effacement d'un caractère est une simple opération à temps constant ou nécessite-t-il une recopie linéaire du reste de la chaîne? 4) Quand, si jamais, les effacements cumulatifs provoquent une réaffectation et une copie? Et ainsi de suite ... – twalberg

Répondre

4

La plus grande complexité de temps que vous aurez est pour la chaîne aabb... et ainsi de suite chaque cahracter répété exactement deux fois. Maintenant, cela dépend de la taille de votre alphabet, disons S. Annotons également la longueur de votre chaîne initiale avec L. Donc, pour chaque lettre, vous devrez parcourir toute la chaîne. Cependant, la première fois que vous faites cela, la chaîne sera de taille L, la deuxième fois L-2 et ainsi de suite. Dans l'ensemble, vous devrez effectuer dans l'ordre de L + (L-2) + ... + (L- S*2) opérations et c'est L*S - 2*S*(S+1), en supposant que L est plus de 2*S. En passant, si la taille de votre alphabet est constante, et je suppose que c'est le cas, la complexité de votre code est O(L) (mais avec une grande constante).

1

Le pire des cas est O(n^2) où n est la longueur de la chaîne d'entrée. Imaginez le cas où chaque personnage est doublé sauf le dernier, comme "aabbccddeeffg". Ensuite, il y a n/2 itérations de boucle, et chaque appel à replaceAll doit balayer toute la chaîne restante, qui est également proportionnelle à n. Editer: Comme le souligne Ivaylo, si la taille de votre alphabet est constante, c'est techniquement O(n) puisque vous n'envisagez jamais un personnage plus d'une fois.

+0

Je ne pense pas que ce soit 'N^2' la complexité dépend de la taille de l'alphabet - il ne considérera jamais un caractère plus d'une fois donc il est plus de l'ordre de' L * S' où L est la longueur et 'S' est la taille de l'alphabet. En fait, si nous supposons que S est constante, la complexité n'est que «O (N)». –

+0

@IvayloStrandjev: C'est un bon point. Dans le cas de l'ensemble du jeu de caractères Unicode, 'N^2' peut être encore plus serré. Je vais faire une note dans ma réponse en le reflétant. – recursive

1

la marque Let:

m = nombre de lettres uniques dans le mot
n = longueur d'entrée

Ceci est le calcul de la complexité:
La boucle principale passe au plus m fois, parce qu'il y a m lettres différentes, le .Replaceall vérifie au plus O (n) comparaisons dans chaque cycle.

le total est: O (m * n) du cycle

un exemple de O (m * n) est la suivante: entrée = aabbccdd,

m = 4, n = 8

les étapes de l'algorithme:

1. input = aabbccdd, complex - 8 
2. input = bbccdd, complex = 6 
3. input = ccdd, complex = 4 
4. input = dd, complex = 2 

total = 6 + 8 + 4 + 2 = 20 = O (m * n)

+0

Il cherche le premier caractère unique, pas le premier non-unique. Votre chaîne d'exemple s'arrêterait à 'a'. – Geobits

+0

Merci pour le commentaire, j'ai changé l'exemple. Le calcul de la complexité est toujours valide. – Wasafa1

1

Soit m soit la taille de votre alphabet, et laissez n la longueur de votre chaîne. Le pire serait de répartir uniformément les caractères de votre chaîne entre les lettres de l'alphabet, ce qui signifie que vous aurez n/m caractères pour chaque lettre de votre alphabet, marquons cette quantité avec q. Par exemple, la chaîne aabbccddeeffgghh est la distribution uniforme de 16 caractères entre les lettres a-h, alors ici n=16 et m=8 et vous avez q=2 caractères pour chaque lettre. Maintenant, votre algorithme passe réellement les lettres de l'alphabet (il utilise juste l'ordre dans lequel elles apparaissent dans la chaîne), et pour chaque itération il doit aller sur la longueur de la chaîne (n) et rétrécir par q (n -= q). Ainsi, sur tout le nombre d'opérations que vous faites dans le pire des cas sont:

s = n + n-(1*q) + ... + n-((m-1)*q) 

Vous pouvez voir que s est la somme des premiers m éléments de la série arithmétique:

s = (n + n-((m-1)*q) * m/2 = 
    (n + n-((m-1)*(n/m)) * m/2 ~ n * m/2 
Questions connexes