web-dev-qa-db-fra.com

Y a-t-il un bon algorithme de recherche pour un seul caractère?

Je connais plusieurs algorithmes de base-assortis de cordes telles que KMP ou Boyer-Moore, mais tous ceux qui analysent le motif avant de rechercher. Cependant, si l'on a un seul caractère, il n'ya pas grand chose à analyser. Il y a donc un meilleur algorithme que la recherche naïve de la comparaison de chaque caractère du texte?

23
Christian

Il est compris que le pire des cas est O(N), il existe de très belles micro-optimisations.

La méthode naïve effectue une comparaison de caractères et une comparaison finale de texte pour chaque caractère.

Utilisation d'un Sentinel (c'est-à-dire une copie du caractère cible à la fin du texte) réduit le nombre de comparaisons à 1 par caractère.

Au niveau de la Twiddling, il y a:

#define haszero(v)      ( ((v) - 0x01010101UL) & ~(v) & 0x80808080UL )
#define hasvalue(x, n)  ( haszero((x) ^ (~0UL / 255 * (n))) )

pour savoir si un octet dans un mot (x) a une valeur spécifique (n).

La subexpression v - 0x01010101UL, évalue à un bit élevé défini dans n'importe quel octet chaque fois que l'octet correspondant dans v est égal ou supérieur à 0x80.

La sous-expression ~v & 0x80808080UL Évalue à des bits élevés définis dans des octets où l'octet de v n'a pas son jeu de bits élevé (donc l'octet était inférieur à 0x80).

En anding ces deux sous-expressions (haszero), le résultat est le jeu de bits élevés où les octets de v étaient nuls, car les bits élevés sont définis en raison d'une valeur supérieure à 0x80 Dans la première sous-expression sont masqués par la seconde (27 avril 1987 par Alan Mycroft).

Maintenant, nous pouvons XOR= la valeur à tester (x) avec un mot rempli de la valeur d'octet dans lequel nous sommes intéressés (n) . Parce que Xoring une valeur avec elle-même entraîne un octet zéro et un non -ero autrement, nous pouvons passer le résultat à haszero.

Ceci est souvent utilisé dans une mise en œuvre typique strchr .

(Stephen M Bennet l'a suggéré le 13 décembre 2009. Détails supplémentaires dans le bien connu bit Twiddling hacks ).


Ps

ce code est cassé pour une combinaison de 1111 à côté d'un 0

Le hack passe le test de la force brute (juste être patient):

#include <iostream>
#include <limits>

bool haszero(std::uint32_t v)
{
  return (v - std::uint32_t(0x01010101)) & ~v & std::uint32_t(0x80808080);
}

bool hasvalue(std::uint32_t x, unsigned char n)
{
  return haszero(x ^ (~std::uint32_t(0) / 255 * n));
}

bool hasvalue_slow(std::uint32_t x, unsigned char n)
{
  for (unsigned i(0); i < 32; i += 8)
    if (((x >> i) & 0xFF) == n)
      return true;

  return false;
}

int main()
{
  const std::uint64_t stop(std::numeric_limits<std::uint32_t>::max());

  for (unsigned c(0); c < 256; ++c)
  {
    std::cout << "Testing " << c << std::endl;

    for (std::uint64_t w(0); w != stop; ++w)
    {
      if (w && w % 100000000 == 0)
        std::cout << w * 100 / stop << "%\r" << std::flush;

      const bool h(hasvalue(w, c));
      const bool hs(hasvalue_slow(w, c));

      if (h != hs)
        std::cerr << "hasvalue(" << w << ',' << c << ") is " << h << '\n';
    }
  }

  return 0;
}

Beaucoup de susvotes pour une réponse qui rend l'hypothèse un chararacter = un octet, qui est aujourd'hui pas la norme.

Merci pour la remarque.

La réponse était censée être tout sauf un essai sur les codages multi-octets/largeur variables :-) (dans toute l'équité qui n'est pas mon domaine d'expertise et je ne suis pas sûr que ce soit ce que l'OP cherchait).

Quoi qu'il en soit, il me semble que les idées/astuces ci-dessus pourraient quelque peu être adaptées à MBE (en particulier codingings auto-synchronisantes ):

  • comme indiqué dans commentaire de Johan Le hack peut "facilement" être étendu au travail pour les doubles octets ou quoi que ce soit (bien sûr, vous ne pouvez pas trop l'étirer trop);
  • une fonction typique qui localise un caractère dans une chaîne de caractères multibyte: [.____]
    • contient des appels à strchr/strstr (par exemple gnulib corutils mbschr )
    • s'attend à ce qu'ils soient bien adaptés.
  • la technique Sentinel peut être utilisée avec un peu de prévoyance.
29
manlio

Si vous avez besoin de rechercher des caractères dans cette même chaîne plus d'une fois, une approche possible consiste à diviser la chaîne en parties plus petites, éventuellement de manière récursive et à utiliser des filtres de fleurs pour chacune de ces pièces.

Étant donné qu'un filtre de floraison peut vous dire avec certitude si un caractère est non dans la partie de la chaîne qui est "représentée" par le filtre, vous pouvez ignorer certaines pièces lors de la recherche de caractères.

A l'exemple: pour la chaîne suivante, on pourrait le diviser en 4 parties (chacune de 11 caractères) et remplir chaque partie un filtre de floraison (peut-être 4 octets grand) avec les caractères de cette partie:

The quick brown fox jumps over the lazy dog 
          |          |          |          |

Vous pouvez accélérer votre recherche, par exemple. Pour le personnage a: Utiliser de bonnes fonctions de hachage pour les filtres de floraison, ils vous le diront - avec une probabilité élevée - vous n'avez pas à chercher en premier, deuxième ni troisième partie. Ainsi, vous vous épargnez de vérifier 33 caractères et de vérifier que 16 octets (pour les 4 filtres de bloom). Ceci est toujours O(n), juste avec un facteur constant (fractionnaire) (et pour que cela soit efficace, vous devez choisir de plus grandes pièces, afin de minimiser les frais généraux du calcul des fonctions de hachage pour le caractère de recherche. ).

L'utilisation d'une approche récursive de type arborescence devrait vous faire près de O(log n):

The quick brown fox jumps over the lazy dog 
   |   |   |   |   |   |   |   |---|-X-|   |  (1 Byte)
       |       |       |       |---X---|----  (2 Byte)
               |               |-----X------  (3 Byte)
-------------------------------|-----X------  (4 Byte)
---------------------X---------------------|  (5 Byte)

Dans cette configuration, on a besoin (encore une fois, en supposant que nous avons eu de la chance et n'a pas eu de faux positif de l'un des filtres) pour vérifier

5 + 2*4 + 3 + 2*2 + 2*1 bytes

pour arriver à la partie finale (où l'on doit vérifier 3 caractères jusqu'à la recherche du a).

En utilisant un bon (meilleur comme ci-dessus) Schéma de subdivision, vous devriez obtenir de beaux bons résultats avec cela. (Remarque: les filtres de fleurs à la racine de l'arborescence doivent être supérieurs à proximité des feuilles, comme indiqué dans l'exemple, pour obtenir une faible probabilité de faux positifs)

1
Daniel Jour

Si la chaîne va être recherchée plusieurs fois (recherche "de recherche" typique "), la solution peut être O (1). La solution consiste à créer un index.

Par exemple :

Carte, où la clé est le caractère et la valeur est une liste d'indices pour ce caractère de la chaîne.

Avec cela, une recherche de carte unique peut fournir la réponse.

1
Shamit Verma