C++ - Différence de performance étrange !

En ajoutant 1 ligne de code (presque) anodine, mon programme devient beaucoup beaucoup plus lent...

a marqué ce sujet comme résolu.

Tu avais parler des conteneurs associatifs. Peux-tu préciser et expliquer, s'il te plaît ?

florian6973

C'était une idée comme ça. Je n'ai pas trop réfléchi comment améliorer ton algo. Mais si je comprend bien, tu parcours tous les mots pour trouver ceux qui ont une "correspondance" avec un mot donné. Or, la plus part des mots ne correspondent pas. Si j'ai bien compris le problème, un mot peut correspondre s'il a moins d'une lettre de différence. Déjà avec ça, tu fais une map<int, mot> pour trier selon le nombre de lettre et tu simplifies pas mal ta boucle. Ou encore mieux, faire un set<string> avec string correspondant à un mot avec ses lettres triées. Ensuite, tu recherches les mots avec 1 lettre en plus (26 itérations), 1 lettre en moins (1 itération par lettre du mot) et 1 lettre changé (26 * nombre lettre du mot itérations)

Bref, il y a surement des choses à faire pour améliorer le temps d'analyse

+0 -0

Merci pour cette explication ! :)

J'ai donc remodifié le code avec un std::set. Les performances ont l'air de s'être encore légèrement améliorées, mais je ne peux pas mettre un temps fixe car plus on avance dans le set, plus les mots sont grands et c'est de plus en plus long… Je reposte donc le code, qui peut avoir quelques maladresses car je n'ai pas l'habitude d'utiliser des set.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
#include <QCoreApplication>
#include <QString>
#include <QStringBuilder>
#include <QFile>
#include <QResource>
#include <QRegExp>
#include <QIODevice>
#include <QList>
#include <QVariant>
#include <QDataStream>
#include <QSettings>
#include <QChar>

#include <iostream>
#include <string>
#include <chrono>
#include <vector>
#include <thread>
#include <algorithm>
#include <array>
#include <set>

//faire supprimer doublon avec liste apres graphe pour sauvegarde (éventuellement)
//faire serialisation + eventuellement multithreading + optimisation suppression mots déjà 'atteints' + pour recherche anagrammes => mot. + algorithme djikstra
//peut etre bug assign auto tableau
//reoptimise inline et fastcall
//http://www.qtcentre.org/wiki/index.php?title=Profiling_with_GNU_gprof
//http://ftp.traduc.org/doc-vf/gazette-linux/html/2004/100/lg100-L.html
//http://fr.wikipedia.org/wiki/Algorithme_de_tri

using namespace std;

typedef unsigned int uint;

enum TypeAnagramme { PlusLettre, MoinsLetttre, Modification };

namespace
{
const int Alphabet[26] = { 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 };
const int TailleMots = 336531;
const int MaxTailleMot = 25;
const int TailleAlphabet = 26;
const int TailleReserveAna = 351;

const QChar Ar = 'a';
const QString Aa = "âàä";
const QChar Er = 'e';
const QString Ea = "éèêë";
const QChar Ir = 'i';
const QString Ia = "îï";
const QChar Or = 'o';
const QString Oa = "ôö";
const QChar Ur = 'u';
const QString Ua = "ùûü";
const QChar Yr = 'y';
const QString Ya = "ÿ";

const QChar RetourLigne = '\n';

const string OptionSansTexte = "nocout";

bool AfficherLesInfos = true;
QString MotDeDepart = "";
QString MotAAtteindre = "";
}

void print(QString text, bool newline = true)
{
    if (AfficherLesInfos)
    {
        cout << text.toLatin1().constData();
        if (newline)
            cout << endl;
    }
}

QString ArrayToQString(vector<int> str)
{
    QString ret = "";
    ret.reserve(str.size());

    for (uint i = 0; i < str.size(); i++)
        ret.append((char)str[i]);

    return ret;
}

class InfoMot
{
public:
    vector<int> Mot;
    vector<int> Anagramme;

    bool operator < (InfoMot r) const
    {
        if (this->Mot.size() < r.Mot.size())
            return true;
        else if (this->Mot.size() == r.Mot.size())
        {
            if (*this == r)
                return false;
            else
            {
                for (uint i = 0; i < this->Mot.size(); i++)
                    if (this->Anagramme[i] < r.Anagramme[i])
                        return true;
                    else if (r.Anagramme[i] < this->Anagramme[i])
                        return false;
                return false;
            }
        }
        else
            return false;
    }

    bool operator ==(InfoMot r) const
    {
        return this->Anagramme == r.Anagramme;
    }
};

class Dictionnaire
{
public:
    static QString LireDictionnaire(QString dicochemin)
    {
        QFile filedico(dicochemin);
        filedico.open(QIODevice::ReadOnly);

        QByteArray bytes = filedico.readAll();

        filedico.close();
        return ((QString)bytes.constData());
    }

    static set<InfoMot> TraiterMots(QString& mots)
    {
        set<InfoMot> Mots;

        QString tempmotqs;
        tempmotqs.reserve(MaxTailleMot);

        int itempmot = 0;
        QChar lettert;

        for (int i = 0; i < TailleMots; i++)
        {
            do
            {
                if (itempmot < mots.length())
                {
                    lettert = mots.at(itempmot);
                    itempmot++;

                    DeleteAccents(Aa, Ar, lettert);
                    DeleteAccents(Ea, Er, lettert);
                    DeleteAccents(Ia, Ir, lettert);
                    DeleteAccents(Oa, Or, lettert);
                    DeleteAccents(Ua, Ur, lettert);
                    DeleteAccents(Ya, Yr, lettert);

                    tempmotqs.append(lettert);
                }
                else
                    break;
            }
            while ((itempmot < mots.length()) && (mots.at(itempmot) != RetourLigne));
            itempmot++;

            InfoMot tempmot = InfoMot();
            tempmot.Mot = QStringToAInt(tempmotqs);

            tempmot.Anagramme = tempmot.Mot;
            sort(tempmot.Anagramme.begin(), tempmot.Anagramme.end());

            Mots.insert(tempmot);

            tempmotqs.clear();
        }
        return Mots;
    }
private:
    static void DeleteAccents(const QString& acc, const QChar& remp, QChar& letter)
    {
        for (int i = 0; i < acc.size(); i++)
            if (acc.at(i) == letter)
                letter = remp;
    }

    static vector<int> QStringToAInt(QString& mot)
    {
        vector<int> valinint;
        valinint.reserve(mot.length());

        for (int i = 0; i < mot.length(); i++)
            valinint.push_back((int)(mot.at(i).toLatin1()));

        return valinint;
    }
};

class Graphe
{
public:
    Graphe(set<InfoMot>& motstraites)
    {
        this->Mots = motstraites;
        this->DeterminerIndexs();
    }

    void GenererGraphe()
    {
        vector<vector<int>> anagrammes;
        anagrammes.reserve(TailleReserveAna);

        set<InfoMot>::iterator sit (this->Mots.begin()), send(this->Mots.end());
        int i = 0;

        for (; sit != send; ++sit)
        {
            print("Numero du mot : " + QString::number((i + 1)) + " ; mot : " + ArrayToQString((*sit).Mot) + " !");

            this->Liens.push_back(vector<int>());

            this->FabriquerAnagrammesEtComparer(sit, anagrammes, i);

            anagrammes.clear();
            i++;
        }
    }
private:
    set<InfoMot> Mots;
    vector<vector<int>> Liens;
    map<int, vector<int>> Indexs;

    void DeterminerIndexs()
    {
        set<InfoMot>::iterator sit (this->Mots.begin()), send(this->Mots.end());

        int i = 0;
        uint ilength = 0;
        bool deb = true;

        for (; sit != send; ++sit)
        {
            if ((*sit).Anagramme.size() != ilength)
            {
                if (!deb)
                    Indexs[ilength].push_back(i);
                else
                    deb = false;

                ilength = (*sit).Anagramme.size();

                Indexs[ilength] = vector<int>();
                Indexs[ilength].push_back(i);
            }
            i++;
        }
    }

    void Comparer(int size, vector<vector<int>>& anagrammes, int indexp)
    {
        if (this->Indexs.find(size) != this->Indexs.end())
        {
            set<InfoMot>::iterator itp = std::next(this->Mots.begin(), this->Indexs[size][0]);
            set<InfoMot>::iterator itm = std::next(this->Mots.begin(), this->Indexs[size][1]);
            int ip = this->Indexs[size][0];

            for (; itp != itm; itp++)
            {
                if (ip != indexp)
                    for (uint i = 0; i < anagrammes.size(); i++)
                        if (anagrammes[i] == (*itp).Anagramme)
                            this->Liens[indexp].push_back(ip);

                ip++;
            }
        }
    }

    void FabriquerAnagrammesEtComparer(set<InfoMot>::iterator& sit, vector<vector<int>>& anagrammes, int indexp)
    {
        if ((*sit).Anagramme.size() < MaxTailleMot)
        {
            for (int j = 0; j < TailleAlphabet; j++)
                anagrammes.push_back(this->CreateAnagramme(TypeAnagramme::PlusLettre, (*sit).Anagramme, j));

            this->Comparer((*sit).Anagramme.size() + 1, anagrammes, indexp);
        }
        anagrammes.clear();

        if ((*sit).Anagramme.size() > 1)
        {
            for (uint j = 0; j < (*sit).Anagramme.size(); j++)
                anagrammes.push_back(this->CreateAnagramme(TypeAnagramme::MoinsLetttre, (*sit).Anagramme, j));

            this->Comparer((*sit).Anagramme.size() - 1, anagrammes, indexp);
        }
        anagrammes.clear();

        anagrammes.push_back((*sit).Anagramme);
        for (uint j = 0; j < (*sit).Anagramme.size(); j++)
            for (int k = 0; k < TailleAlphabet; k++)
                anagrammes.push_back(this->CreateAnagramme(TypeAnagramme::Modification, (*sit).Anagramme, j, k));

        this->Comparer((*sit).Anagramme.size(), anagrammes, indexp);
    }

    vector<int> CreateAnagramme(TypeAnagramme type, vector<int> mot, uint j, uint k = 0)
    {
        vector<int> motmodifie = mot;

        switch (type)
        {
        case TypeAnagramme::PlusLettre:
            motmodifie.push_back(Alphabet[j]);
            break;

        case TypeAnagramme::MoinsLetttre:
            motmodifie.erase(motmodifie.begin() + j);
            break;

        case TypeAnagramme::Modification:
            motmodifie[j] = Alphabet[k];
            break;
        }

        sort(motmodifie.begin(), motmodifie.end());

        return motmodifie;
    }
};

namespace General
{
void ChargerMot(int argc, char *argv[])
{
    if (argc >= 3)
    {
        MotDeDepart = argv[1];
        MotAAtteindre = argv[2];

        if (argc == 4)
            if (((string)argv[3]) == OptionSansTexte)
                AfficherLesInfos = false;
    }
    else
    {
        print("Entrer le mot de départ + ; + le mot d'arrivé : ", false);
        string ep = "";
        cin >> ep;

        int eps = ep.find(';');
        MotDeDepart = ep.substr(0, eps).c_str();
        MotAAtteindre = ep.substr((eps + 1), ep.size()).c_str();
    }
}

void VerifierEtCreerGraphe()
{
    if (!QFile("dic.graph").exists())
    {
        print("Chargement du dictionnaire et suppression des accents !");

        QString mots = Dictionnaire::LireDictionnaire(":/motsfr/words.txt");
        set<InfoMot> motstraites = Dictionnaire::TraiterMots(mots);

        Graphe graphe = Graphe(motstraites);

        print("Creation du graphe et des anagrammes !");
        graphe.GenererGraphe();

        //TODO (faire serialisation)
    }
}
}

int main(int argc, char *argv[])
{
    print("Bonjour !\n");
    try
    {
        General::ChargerMot(argc, argv);
        print("Mot de depart : " + MotDeDepart + " ; Mot d'arrive : " + MotAAtteindre + " !\n");

        auto start = std::chrono::high_resolution_clock::now();

        General::VerifierEtCreerGraphe();

        print("Chargement du graphe ! TODO");
        //TODO (chargement du graphe sérialisé)

        print("Calcul... TODO");
        //TODO (dijkstra's algorithm)

        std::cout << "Temps : " << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()-start).count() << " ms !" << std::endl;
    }
    catch (...)
    {
        print("ERREUR inconnue !");
        return -1;
    }
    print("\nAu revoir !");
    return 0;
}

Je vais réfléchir comment améliorer encore l'algorithme. Il existe peut-être sur internet des algorithmes pour ce type de recherche ?.

Je reste à votre disposition si vous avez des questions ou autre… Encore merci pour vos idées et vos remarques !

+0 -0

Justement, le problème est de parcourir tous les mots (que ce soit dans un set ou dans un vector). Le but de set (ou map ou n'importe quel conteneur associatif) est justement d'éviter de parcourir tous les mots. Si ce n'est pas le cas, c'est que la solution n'est pas correcte. Il ne faut surtout pas parcourir tous les mots

+0 -0

Dans le code que j'ai reposté, je ne parcoure qu'une partie des mots. Cependant, c'est toujours assez lent…

+0 -0

Calcule la complexité algorithmique de chaque fonction ou fais un profiling pour savoir combien de fois sont appelé tes fonctions problématiques (en particulier compare).

1
set<InfoMot>::iterator sit (this->Mots.begin()), send(this->Mots.end());
  1. déclare tes variables uniquement au moment où tu en as besoin
  2. ça ne sert à rien de créer une variable comme send
+0 -0

Salut,

Le problème ici est clairement algorithmique ; il faut revoir l'idée avant de pouvoir espérer avoir des performances "convenables" en changeant aléatoirement deux lignes de code. La fonction Comparer de ton dernier code a une complexité assez mauvaise ; si je comprends bien, ton idée est, une fois que tu as généré tous les successeurs de ton mot, de prendre tous les mots de la même taille du dico, de les comparer un par un… Même si ce n'est qu'une "partie" du dico : vu que les mots se répartissent sur 26 lettres, tu divises au mieux ton nombre de vérifications par 26, non ?

Il faudrait donc revoir ton idée et trouver une manière plus efficace de faire cette construction de graphe… Vraiment, si je peux te donner un conseil, c'est de prendre un papier et un crayon, et de réfléchir clairement à ton algo (fais au moins des calculs grossiers pour avoir un ordre de grandeur du nombre d'opérations que tu vas devoir faire). Il y a vraiment moyen de faire cette partie sans utiliser aucune structure de données plus compliquée qu'un tableau et aucun algo plus évolué qu'un tri. On peut éventuellement en discuter ici si tu as du mal, mais ne t'embarque dans des modifications manuelles en changeant un inline ou un parcours de set par-ci par-là ; ce serait clairement contre-productif !

Bon courage ! :)

Merci pour ces conseils !

Je vais faire comme tu le dis, et si j'ai du mal ou si j'ai des questions, je les poserai. En tous cas, encore merci pour tes conseils et ton aide ! :)

(par contre, ces jours-ci, je ne sais pas si j'aurai beaucoup de temps à consacrer à cela => je le ferai dès que possible).

+0 -0

Après quelques recherches, j'ai trouvé pleins d'algorithmes sur les graphes (http://fr.wikipedia.org/wiki/Liste_des_algorithmes_de_la_th%C3%A9orie_des_graphes). Cependant, je ne sais pas vers quoi m'orienter, approfondir mes recherches pour optimiser la génération du graphe. Pourriez-vous, s'il vous plaît, me guider ou m'indiquer une piste ?

+0 -0

Merci pour ta réponse ! :)

Je cherche à construire le graphe des 336531 mots du dictionnaire, qui seraient connectés selon les règles du jeu des anagrammes vivants. Les nœuds contiendraient les mots, les arêtes relieraient les nœuds selon les anagrammes générés des mots.

+0 -0

Ok, donc disons que tu as 300K nœuds. (Ligne 329 de ton dernier code, tu tries ton mot, ce qui fait que les nœuds de ton graphe ont la particularité de correspondre à des mots sous une forme triée.)

Dans le pire des cas, combien d'arcs ton graphe contient-il ?

Je pense environ :

  • le plus grand mot de la langue française comporte 25 lettres.
  • il y a 1 + 0 + 25 + 26 * 25 = 676 anagrammes possibles pour un mot.
  • il y a donc 676 * 300000 = 202 800 000 connexions possibles.

On peut dire qu'il y a au pire 202 800 000 arcs. Lors de tests, les mots ont entre 1 et 150 anagrammes correspondants à d'autres mots, ce qui fait beaucoup moins (environ 30 000 000).

+0 -0

Par « anagramme » tu entends quelque chose comme « mot atteignable en un coup via les règles du jeu » ? Parce que pour moi, c'est plutôt une permutation des lettres.

il y a 1 + 0 + 25 + 26 * 25 = 676 anagrammes possibles pour un mot.

Je ne sais pas exactement d'où sortent le 1 et le 0 ; il me semble qu'on a plutôt 25 lettres à supprimer, 26 lettres à ajouter et 26x25 lettres à modifier, ce qui fait un poil plus de transitions. Mais comme 26x25 domine, l'ordre de grandeur est là.

Bon du coup, ce graphe là n'est pas trop gros, mais il l'est assez pour qu'on évite de faire des opérations conséquentes pour chaque arc. Donc voilà, généralement quand on veut résoudre un problème en le modélisant par un graphe, c'est souvent une bonne idée de commencer par décrire clairement ses nœuds, ses arcs et l'ordre de grandeur de ceux-ci, afin de bien cerner sur quoi on travaille. Et là on peut conclure qu'avec ce graphe, on peut pas se permettre une construction naïve.

Bon ensuite, prochaine étape : envisageons la construction bourrine. Comment tu décrirais, avec un pseudo-code (de haut niveau hein, on recode rien du tout ! :p ), la génération de tous les successeurs d'un nœud ?

Merci encore pour ta réponse ! :)

Tu as tout compris pour 'par anagramme'. Cependant, pour calculer les mots atteignables, je calcule les anagrammes possibles et les trie pour les comparer avec les autres mots triés.

Pour la construction bourrine/naïve de la génération naïve de tous les successeurs :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
boucle (touslesmots) //parcours de tous les mots
{
    mottrie = noeudactuel().tri //le mot trie sera le premier anagramme

    list anagrammestriesdumot //liste qui stockera tous les anagrammes possibles du mot
    anagrammestriesdumot.add(mottrie) // ajout donc du mot
    anagrammestriesdumot.addAvec+1Lettre // ajout de tous les anagrammes avec une lettre en plus
    anagrammestriesdumot.addAvec+-Lettre // ajout de tous les anagrammes avec une lettre en moins
    anagrammestriesdumot.addLettreModifiee // ajout de tous les anagrammes avec une lettre du mot modifiee

    boucle (touslesmots) //comparaison avec les mots pour voir les anagrammes qui 'fonctionnent'
    {
        mottemptrie = motactueldelaboucle // mot trie de la boucle à comparer

        boucle (touslesanagrammes) // parcours de tous les anagrammes
        {
            if (mottemptrie == motactueldelaboucle) // comparaison du mot trie de la boucle et d'un des anagramme trie
                graphe.ajout(mottrie, mottemptrie) // ajout au graphe si ok
        }
    }
}

Voilà l'algo en mode bourrin ; la complexité est très mauvaise… :)

+0 -0

C'est pas un problème que la complexité soit très mauvaise, c'est un bourrin. Mais évidemment, une fois qu'on l'a, il faut chercher à l'améliorer. Ici, la boucle qui pose problème, c'est celle de la ligne 11 : on peut pas partir sur du quadratique en la taille du dico.

Dès lors, on peut reposer le sous-problème qu'on cherche à résoudre à partir de la ligne 11. Quel est-il ? (On peut le décrire au moyen de ce qu'on a en entrée, et ce qu'on veut en sortie.) Ensuite, un truc qui marche bien pour avoir des idées sur le « comment faire mieux », c'est de simplifier le problème. Comment tu ferais si l'« entrée » que tu as définie était réduite à un élément, par exemple. :)

Merci pour ta réponse très claire et explicative ! :)

Le sous problème est le suivant : connaître les anagrammes qui se connectent à d'autres mots existants de la langue française et créer l'arête correspondante si un mot se connecte.

  • entrée : les anagrammes d'un mot
  • sortie : le graphe modifié par les anagrammes correspondants à des mots

Est-ce bien défini ?

Alors, pour simplifier le problème… En réduisant l'entrée à un seul anagramme… Je n'ai pas trop d'idée :/… Aurai-tu une piste/un indice ?

+0 -0

Le sous problème est le suivant : connaître les anagrammes qui se connectent à d'autres mots existants de la langue française et créer l'arête correspondante si un mot se connecte.

Le problème intéressant réside dans ce « connecte ». Ici, tu disposes d'un dico et tu veux savoir quels mots de ta liste sont dedans. Du coup, on peut même s'abstraire de l'aspect « graphe » ici : en reformulant encore plus simplement, t'as une liste de chaînes en entrée, et tu veux calculer la liste des chaînes qui sont dans le dico en sortie.

Alors, pour simplifier le problème… En réduisant l'entrée à un seul anagramme… Je n'ai pas trop d'idée :/… Aurai-tu une piste/un indice ?

Généralement, les simplifications qu'on n'arrive pas à faire, c'est génial ! :p Parce que tant que je ne sais pas résoudre le cas n = 1, ça ne sert à rien que j'essaye avec n arbitraire.

Donc ici, tu as pleins de requêtes de la forme : une chaîne s. Tu voudrais faire mieux qu'une recherche linéaire (ie. pour chaque chaîne du dico t, si s = t alors retourner présent, etc.). Est-ce que tu peux proposer un algo de meilleure complexité pour répondre rapidement à ce genre de requêtes (tu as le droit de faire subir un pré-traitement au dictionnaire, par exemple) ?

Je crois avoir trouvé quelque chose : trier le dictionnaire dans l'ordre alphabétique selon les anagrammes des mots, puis utiliser la recherche dichotomique à la place de la recherche linéaire. Il y a-t-il encore autre chose de plus performant ?

+0 -0

Je crois avoir trouvé quelque chose : trier le dictionnaire dans l'ordre alphabétique selon les anagrammes des mots, puis utiliser la recherche dichotomique à la place de la recherche linéaire. Il y a-t-il encore autre chose de plus performant ?

florian6973

Il y a toujours plus performant, mais il faut aussi faire le compromis avec la simplicité. Quand on peut, on choisit la solution la plus simple dont les performances correspondent à ce qu'on attend.

Ici déjà, la dichotomie enlève un énorme fardeau. Bon, maintenant que tu as cette idée sur la version « simplifiée » de la grosse boucle, tu remarques aisément qu'elle se généralise facilement avec une liste de taille arbitraire (on peut répéter n fois la dichotomie).

Tu peux donc déjà réécrire le pseudo-code de la construction de graphes et recalculer un ordre de grandeur du nombre d'« opérations » que tu vas faire de cette manière. Et itérativement, si ce nombre est trop grand, on peut réappliquer la méthode de simplification en sous-problèmes (généralement, tu pourras faire très grosso modo 10^7 opérations en une seconde, c'est évidemment une approximation honteuse parce qu'on omet une infinité de facteurs, et de toute manière une « opération », ça ne veut rien dire !).

Encore merci pour ces explications ! :)

Voici donc le nouveau pseudo-code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
quicksortanagrammes(dictionnaire) //tri du dictionnaire selon les anagrammes des mots

boucle (touslesmots) //parcours de tous les mots
{
    mottrie = noeudactuel().tri //le mot trie sera le premier anagramme

    list anagrammestriesdumot //liste qui stockera tous les anagrammes possibles du mot
    anagrammestriesdumot.add(mottrie) // ajout donc du mot
    anagrammestriesdumot.addAvec+1Lettre // ajout de tous les anagrammes avec une lettre en plus
    anagrammestriesdumot.addAvec-1Lettre // ajout de tous les anagrammes avec une lettre en moins
    anagrammestriesdumot.addLettreModifiee // ajout de tous les anagrammes avec une lettre du mot modifiee

    boucle (touslesanagrammes) //parcours de tous les anagrammes générés
    {
        indextemp = 0, tailledicotemp = 336531 - 1, milieu = divisioneuclidienne((indextemp + tailledicotemp), 2) // initialisation des variables pour le tri dichotomique
        tantque (indextemp < tailledicotemp) // tri dichotomique
        {
            si (dicotrie[milieu] == anagrammeactueldelaboucle)
            {
                graphe.ajout(mottrie, dicotrie[milieu]) // ajout au graphe
                fin_boucle // sortie de la boucle, passage à l'anagramme suivant
            }
            sinon_si (dicotrie[milieu] > anagrammeactueldelaboucle)
                tailledicotemp = milieu - 1
            sinon
                indextemp = milieu + 1
            milieu = divisioneuclidienne((indextemp + tailledicotemp), 2)
        }
    }
}

Voici le calcul du nombre de grandeur du nombre d'opérations de la boucle :

  • génération des anagrammes : environ (26 + 25 + 25 * 26) modification du mot et de quicksort ;
  • recherche dichotomique : environ au pire 18 * 3 opérations ;
  • donc au total ((26 + 25 + 25 * 26) * 2 (=pour la modification puis le tri) + (18 * 3) * 650 (=nombre au pires d'anagrammes)) * 300 000 (nombres du mot du dictionnaire) = 10 950 600 000 opérations ! Mais après ça ne veut pas dire grand chose si on ne compare pas avec le pseudo code d'avant : ((26 + 25 + 25 * 26) * 2 + 650 * 300000) * 300000 = 5.8500421*10^13 opérations ! C'est déjà beaucoup mieux ! Merci ! :)

Que puis-je faire maintenant ?

+0 -0

Pour comparer les deux, il suffit de voir qu'on a remplacé du n par du log n, donc ça doit faire un gain de 10 000. Bon je suis pas allé dans les détails des calculs (c'est pas le but), mais avec ça bien codé, on devrait pouvoir espérer atteindre un ordre de grandeur de la minute dans la pratique (sachant qu'on a tout bien majoré, il n'y a pas que des anticonstitutionnellement dans le dico). Et de toute manière, mon algo est guère mieux (il fait du 40s dans le pire des cas), donc je te laisse rechercher par toi-même : tu as toutes les clés en main. :p

Pour diviser le temps de manière constante, il y a encore quelques trucs pertinents à faire, comme gérer les addAvec plus intelligemment, ou faire la génération du graphe « à la volée », lors du parcours (même si ça ne change rien dans le pire des cas). Mais on peut déjà voir ce que ça donne en pratique, c'est pourquoi tu peux essayer de coder ça (BTW, les dichotomies étant fortement buguables, tu peux sûrement éviter de réinventer la roue avec des std::lower_bound ou équivalents).

J'attire quand même ton attention sur la méthodologie qu'on a appliqué : après tout, c'est ça qui est important et qui peut re-servir plus tard, que ce soit pour améliorer encore ton algo pour ce problème ou pour aborder des exos encore plus durs. Rétrospectivement, donc :

  • Penser bourrin. C'est lent et lourd, mais c'est une base pour partir, parce qu'on sait (?) qu'il est correct.
  • Trouver le « goulot d'étranglement » dans ce bourrin. Ça sert à rien d'essayer d'optimiser les boucles secondaires, mieux vaut se concentrer sur ce qui coûte cher à l'algo.
  • Essayer de l'améliorer. Quand on ne trouve pas : essayer de simplifier, de reformuler, de manière à se retrouver avec des problèmes qu'on sait résoudre (sans oublier qu'on aura à généraliser ces solutions après).
  • S'arrêter quand on remplit son cahier des charges avec la solution la plus simple possible.

Parce qu'on m'accuserait de plagiat si je revendiquais l'origine de ces merveilleuses méthodes, je te renvoie à une source bien utile. C'est la méthode qu'on utilise pour les olympiades d'info (avec plus ou moins de succès !). Ça mériterait presque un article sur ZdS, tiens.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte