Basicamente, o problema é com a repetição duplicada do a no padrão. A parte a* permite qualquer número de a , enquanto o (·)* também permite qualquer número do padrão contido.
Isso permite um grande número de maneiras possíveis de combinar o padrão com uma string de a . Ignorando o b por enquanto, uma string como aaaaa (cinco a ) pode ser correspondida como (aaaaa) , (aaaa)(a) , (aaa)(aa) , (aaa)(a)(a) , (aa)(aaa) , (aa)(aa)(a) . .. Há um número exponencial de maneiras de combinar com a string.
Com o b no final, um mecanismo de backtracking tentará uma maneira de corresponder a a , percebe que não encontra o b , retrocede um passo, tenta de outra forma, percebe Não é possível encontrar o b , ... e leva muito tempo para esgotar todos os arranjos possíveis, após o que ele falha.
Existem textos muito melhores sobre esse assunto on-line do que eu jamais poderia escrever. Vá ler estes:
-
Expressões regulares em fuga: Backtracking catastrófico de Jan Goyvaerts descreve o problema e algumas maneiras de evitá-lo .
-
A correspondência de expressões regulares pode ser simples e rápida (mas ...) por Russ Cox também descreve a questão, assim como implementa regexes como autômatos finitos, sem usar retrocessos e, portanto, imune a esse problema. Também tem fotos.
Na prática, se puder, evite padrões que permitam várias maneiras de corresponder a uma string. O exemplo aqui, (a*)*c , é obviamente tolo, já que é equivalente a a*c , que não tem a repetição aninhada.