Problème de voyageur de commerce

Soyez le premier à donner votre avis sur cette source.

Vue 13 548 fois - Téléchargée 3 481 fois

Description

Ce programme permet de résoudre le problème de voyageur de commerce. Il utilise pour ça un algorithme de brute force lorsque le nombre de villes est inférieur à 11, deux algorithme de recherche locale et un algorithme génétique. Cependant il s'agit ici des versions simplifiées qui vont être moins performantes que les versions utilisees en production.

Source / Exemple :


/******************************************

  • Le problème du "voyageur de commerce" *
  • Auteur : Meradi Rabah *
  • Version : 1.0 *
  • Licence : GPL *
                                                                                    • /
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #include <math.h> #include <time.h> #include "tsp.h" /*
  • Permet d'afficher une instance (nom, nombre de villes et la matrice des distances)
  • in s : l'instance à afficher
    • /
void affich_instance(instance s) { printf("Nom : %s\n",s.nom); printf("Nombre de villes : %d\n",s.nombre); printf("Cordonnées des villes :\n"); int i,j; for(i=0;i<s.nombre;++i) printf("%d\t%f\t%f\n",(int)s.cor[i][0],s.cor[i][1],s.cor[i][2]); for(i=0;i<s.nombre;i++){ for(j=0;j<s.nombre;++j){ printf("%f\t",distance(s,i+1,j+1)); } printf("\n"); } } /*
  • Retourne la position de la chaine ch dans le tableau tab si elle s'y trouve, -1 sinon
  • in tab : le tableau de chaines de caractères
  • in ch : la chaine de caratètre
  • in n : la taille du tableau
  • out : la position de ch dans tab si elle s'y trouve, -1 sinon
    • /
int Pos_Chaine(char **tab,char *ch,int n){ int i; for(i=0;i<n;++i){ if(strcmp(tab[i],ch)==0) return i; } return -1; } /*
  • Remplace tous les espaces en doublons par un seul espace
  • in out ch : la chaine de caractètre à traiter
    • /
void supprime_espace_double(char * ch){ int i=0; int j=0; bool espace = true; while(ch[i] != '\0'){ char car = ch[i]; if ((car != ' ') || (car == ' ' && !espace)) { ch[j] = car; j++; if(car == ' ') espace = true; else espace = false; } i++; } ch[j] = '\0'; } /*
  • Copie d'une chaine de caractères vers une autre à partir d'une position donnée
  • in ch1 : la chaine de caractère qu'on veut copier
  • out ch2 : la chaine de caractère qui recevra la copie
  • in debut : la position à partir de laquelle on va faire la copie
    • /
void enlever (char *ch1,char ch2[], unsigned int debut){ int i=0; int j=0; char c; bool espace = true; do { c = ch1[debut+i]; if(c != ' ' || !espace){ ch2[j] = c; espace = false; j++; } i++; }while(c != '\0'); } /*
  • Retourne vrai si la chaine du premier arguement commence par la chaine passer en second argument
  • in ch1
  • in ch2
    • /
bool commence_avec(char * ch1,char *ch2){ int i=0; if(strlen((ch2))>strlen(ch1)) return false; while(ch1[i] !='\0' && ch2[i] !='\0') { if(ch1[i] != ch2[i]) return false; i++; } return true; } void decoupe (char *ch,char c,char tab[MAX][MAX],int *k){ int j=0;
  • k =1;
int i=0; supprime_espace_double(ch); while(ch[i] !='\0'){ char car = ch[i]; if(c == car){ tab[*k-1][j] = '\0'; j=0;
  • k = *k+1;
} else{ tab[*k-1][j] = car; j++; } i++; } tab[*k-1][j] = '\0'; } /*
  • Calcule les distances entre toutes les villes
  • in : cor les cordonnées des villes
  • in : le nombre de villes
  • out : tab le tableau contenant les distances entre toutes les villes
  • */
void calcule_distances (float cor[MAX][3],float distances[MAX],int n){ int i,j,k; k=0; for(i=0;i<n-1;++i){ for(j=i+1;j<n;++j){ distances[k] = sqrt(((cor[i][1]-cor[j][1])*(cor[i][1]-cor[j][1]))+((cor[i][2]-cor[j][2])*(cor[i][2]-cor[j][2]))); k++; } } } /*
  • Retourne une instance en chargeant son contenu depuis un fichier
  • in : f le pointeur vers le fichier qui contient les données de l'instance
  • out : l'instance lu depuis le fichier
  • */
instance lire (FILE * f) { instance c; char ch[MAX]; while(!feof(f)) { fgets(ch,MAX,f); if(commence_avec(ch,"NAME:")){ enlever(ch,c.nom,5); c.nom[strlen(c.nom)-1] = '\0'; } if(commence_avec(ch,"DIMENSION:")){ char ch2[MAX]; enlever(ch,ch2,10); c.nombre = atoi(ch2); } if((strcmp("DISPLAY_DATA_SECTION\n",ch)==0) || (strcmp("NODE_COORD_SECTION\n",ch)==0)){ int i; for(i=0;i<c.nombre;++i){ fgets(ch,MAX,f); char tab[MAX][MAX]; int k; decoupe(ch,' ',tab,&k); int j; for(j=0;j<3;++j){ c.cor[i][j] = atof(tab[j]); } } } } fclose(f); calcule_distances(c.cor,c.distances,c.nombre); return c; } /*
  • Génère la permutation suivante à celle donnée en arguement
  • in : tab la permutation actuelle
  • in : la taille de la permutation
  • out : tab la permutation suivante
  • out : retourne faux s'il la permutation passée en paramèetre
  • est la dernière permutation. Sinon il retourne vrai
    • /
bool next_permutation(int tab[MAX],int taille){ int k=taille -2; while(k>=0){ if(tab[k] < tab[k+1]) break; k--; } if(k<0) return false; int l = taille -1; while(l>0){ if(tab[l] > tab[k]) break; l--; } int c = tab[k]; tab[k] = tab[l]; tab[l] = c; l = taille -1; k++; while(l>k){ c=tab[k]; tab[k]=tab[l]; tab[l]=c; l--; k++; } return true; } /*
  • Retourne la distance entre deux villes
  • in : s l'instance
  • in : i l'indice de la première ville
  • in : j l'indice de la deuxième ville
  • out : la distance entre les deux villes
    • /
float distance (instance s, int i, int j){ if (i==j) return 0; if(i>j){ int t =i; i = j; j = t; } return s.distances[(i-i*i-2*s.nombre+2*i*s.nombre)/2+j-i-1]; } /*
  • Calcule la longeur d'une tournée
  • in : t la tournée pour laquelle il faut calculer la longeur
  • in : s l'instance
  • out : t la tournée avc maintenant la variable longeur est modifiée
    • /
void longeur_tournee(tournee * t,instance s){ int i,j,k; float l=0; for(i=0;i<s.nombre;++i){ j = t->villes[i]; k = t->villes[(i +1) % s.nombre]; l= l+ distance(s,j,k); // printf("%d\t%d\t%f\n",j,k,l); } t->longeur = l; } /*
  • Il génère une tournée selon le "random walk"
  • in : s l'instance
  • out : la tournée générée aléatoirement
  • */
tournee genere_tournee(instance s){ int i; tournee t; srand(time(NULL)); for(i=0;i<s.nombre;++i){ t.villes[i] = i+1; } for(i=0;i<s.nombre;++i){ int a = rand()%s.nombre; int temp = t.villes[i]; t.villes[i] = t.villes[a]; t.villes[a] = temp; } t.nb_villes = s.nombre; longeur_tournee(&t,s); return t; } /*
  • Affiche une tournée. Il affiche :
  • - Le nombre de villes
  • - La longeur de la tournée
  • - La liste des villes composant la tournée
  • */
void affich_tournee(tournee t) { int i; printf("Nombre de villes : %d\n",t.nb_villes); printf("Longeur : %f\n",t.longeur); printf("Tournée : "); for(i=0;i<t.nb_villes;++i) printf("%d\t",t.villes[i]); printf("\n"); } /*
  • Affiche la solution selon l'algorithme brute force
  • in : s l'instance à résoudre
  • */
void brut_force(instance s){ if(s.nombre <11){ tournee t1; tournee t2; t1.nb_villes = s.nombre; t2.nb_villes = s.nombre; int i; for(i=0;i<s.nombre;++i){ t1.villes[i] = i+1; t2.villes[i] = i+1; } longeur_tournee(&t1,s); if(verbose) affich_tournee(t1); while(next_permutation(t2.villes,s.nombre)){ longeur_tournee(&t2,s); if(verbose) affich_tournee(t2); if(t2.longeur<t1.longeur){ t1.longeur=t2.longeur; for(i=0;i<s.nombre;++i) t1.villes[i]=t2.villes[i]; } } printf("Meilleur résultat selon la méthode de brute force: \n"); affich_tournee(t1); } else printf("Erreur : Nombre de villes est supérieur à 10!"); } /*
  • Affiche la solution selon l'algorithme de recherche locale
  • in : param : le nombre d'essais
  • in : s l'instance à résoudre
  • */
void lsr(int param,instance s){ tournee t1 = genere_tournee(s); if(verbose) affich_tournee(t1); tournee t2; int i; for(i=0;i<param;++i){ t2 = op2(t1,s,-1,-1); if(verbose) affich_tournee(t2); if(t2.longeur<t1.longeur) { t1.longeur = t2.longeur; for(i=0;i<t1.nb_villes;++i) t1.villes[i] = t2.villes[i]; } } printf("La solution avec l'algorithme de recherche locale est :\n"); affich_tournee(t1); } /*
  • Affiche la solution selon l'algorithme de recherche systématiquee
  • in : param : le nombre d'essais
  • in : s l'instance à résoudre
  • */
void lsnr(int param,instance s){ tournee t1 = genere_tournee(s); tournee t2 = t1; if(verbose) affich_tournee(t1); int i,j,k; for(i=0;i<param;++i){ for(j=0;j<t1.nb_villes;++j) { for(k=0;k<t1.nb_villes;++k) { if(j==k || k==(j-1+t2.nb_villes)%t2.nb_villes || k ==(j+1)%t2.nb_villes) continue; tournee t3 = op2(t1,s,j,k); if(verbose) affich_tournee(t3); if(t3.longeur<t2.longeur) t2 = t3; } } t1 = t2; } printf("la solution avec l'algorithme de recherche séstymatique est : \n"); affich_tournee(t1); } /*
  • Retourne une tournée en faisant une 2-optimisation de la tournée passée en paramètre
  • int : t la tournée à modifier
  • in : a le début du premier trajet à éliminer
  • in : b le début du deuxième trajet à éliminer
  • out : la tournée résultante après la 2-optimisation
  • */
tournee op2(tournee t,instance s,int a,int b){ srand(time(NULL)); tournee t1; t1.nb_villes = t.nb_villes; int i,j; if(a==-1 || b==-1){ a = rand()%s.nombre; do { b = rand()%s.nombre; } while(b==a || b==(a-1+s.nombre)%s.nombre || b ==(a+1)%s.nombre); } t1.villes[0] = t.villes[a]; t1.villes[1] = t.villes[b]; j = 2; for(i=b-1;(i+s.nombre)%s.nombre!=a;--i){ t1.villes[j] = t.villes[(i+s.nombre)%s.nombre]; j++; } for(i=b+1;i%s.nombre !=a;++i){ t1.villes[j] = t.villes[i%s.nombre]; j++; } longeur_tournee(&t1,s); return t1; } /*
  • Permet de générer un ensemble de tournées
  • in out : tab le tableau qui contiendra les tournées générées
  • in n : le nombre de tournées à générer
  • */
void genere_tournees(tournee tab[MAX], instance s, int n){ int i; for(i=0;i<n;){ tournee t = genere_tournee(s); if(!existe(t,tab,i-1)){ tab[i] = t; i++; } } } /*
  • compare deux tournées données et retourne vrai si les deux sont egaux
  • in : t1 la première tournée
  • in : t2 la deuxième tournée
  • */
bool egal(tournee t1,tournee t2) { if(t1.longeur != t2.longeur || t1.nb_villes != t2.nb_villes) return false; else { int j; for(j=0;j<t1.nb_villes;++j){ if(t1.villes[j]!=t2.villes[j]) return false; } } return true; } /*
  • Retourne vrai si la tournée t existe dans le tableau tab
  • in : t la tournée
  • in : tab le tableau qui contient l'ensemble des tournées
  • in : n le taille de tableau tableau
  • */
bool existe(tournee t,tournee tab[MAX],int n){ int i; for(i=0;i<n;++i){ if(egal(t,tab[i])) return true; } return false; } bool chemin_existe(int depart,int arrive,tournee t) { int i; for(i=0;i<t.nb_villes-1;++i) { if((t.villes[i]==depart && t.villes[i+1]==arrive) || (t.villes[i]==arrive && t.villes[i+1]==depart)) return true; } return false; } /*
  • Retourne la tournée fille des deux tournée parents selon l'algorithme DPX
  • in : parent_1 le parent 1
  • in : parent_2 le parent 2*
  • in s : l'instance
  • */
tournee tournee_fille(tournee parent_1,tournee parent_2, instance s) { int j=0; int i; tournee t; t.nb_villes = parent_1.nb_villes; for(i=0;i<parent_1.nb_villes-1;i++){ if(chemin_existe(parent_1.villes[i],parent_1.villes[i+1],parent_2)) { t.villes[j] = parent_1.villes[i]; j++; } else { t.villes[j] = parent_1.villes[i]; j++; t.villes[j] = t.nb_villes+1; j++; } } t.villes[j] = parent_1.villes[t.nb_villes-1]; t.nb_villes = j+1; t = reconecte_tournee(t,parent_1,parent_2,s); //printf("T1"); //printf("%d\n",t.nb_villes); //affich_tournee(t); longeur_tournee(&t,s); return t; } /*
  • Permet de supprimer un entier (en le remplaçant par 0) d'un tableau donnée
  • in out : tab le tableau des entiers
  • in : taille la taille de tableau
  • in : a l'entier à supprimer
  • */
void supprimer(int tab[MAX],int taille, int a) { int i; for(i=0;i<taille;++i) { if(tab[i]==a) tab[i] = 0; } } /*
  • Permet des reconcter les villes d'une tournée en éliminant les trajets détruits
  • in : fille la tournée initiale
  • in : parent_1 le parent 1
  • in : parent_2 le parent 2
  • in : s l'instance
  • Il retourne une tournée après avoir remplacer les trajets détruits
  • */
tournee reconecte_tournee(tournee fille,tournee parent_1,tournee parent_2, instance s) { tournee temp; int i; temp.nb_villes = parent_1.nb_villes; int extrimites[MAX]; int taille = temp.nb_villes+1; calcul_extrimite(extrimites,fille,&taille); int j=0; bool debut = true; for(i=0;i<temp.nb_villes;++i) { if(j<fille.nb_villes){ if(fille.villes[j] != temp.nb_villes +1 ){ temp.villes[i] = fille.villes[j]; supprimer(extrimites,taille,temp.villes[i]); if(debut) ++j; else --j; continue; } } if(j>=fille.nb_villes || fille.villes[j] == temp.nb_villes +1) { int k = plus_proche_voisin(temp.villes[i-1],extrimites,parent_1,parent_2,s,taille); j = rechercher(k,fille); if(j == -1) { for(k=0;k<taille;++k) { if(extrimites[k] != 0) { j = rechercher(extrimites[k],fille); break; } } } --i; if(j==fille.nb_villes-1) { debut=false; continue; } if(fille.villes[j+1] !=temp.nb_villes+1) debut = true; else debut = false; } } return temp; } /*
  • Retourne la ville la plus proche d'une ville donnée parmi l'ensemble de villes passé en paramèetre.
  • in : ville la ville initiale
  • in : villes l'ensemble des villes
  • in : t2
  • in : t2
  • in : s l'instance
  • in : taille la taille de tableau
  • */
int plus_proche_voisin(int ville,int villes[MAX],tournee t1,tournee t2,instance s,int taille) { int i; int voisin = 0; for(i=0;i<taille;++i) { if(villes[voisin] == 0 || chemin_existe(ville,villes[voisin],t1) || chemin_existe(ville,villes[voisin],t2) || ville == villes[voisin]) voisin = i; else{ if(!chemin_existe(ville,villes[i],t1) && !chemin_existe(ville,villes[i],t2) && ville != villes[i] && villes[i] !=0) { if(distance(s,ville,villes[i]) < distance(s,voisin,ville)) voisin = i; } } } i = voisin; voisin = villes[voisin]; villes[i] = 0; return voisin; } /*
  • Retourne l'indice où se trouve la ville "ville" dans la tournée t
  • in : ville la ville pour laquelle il faut chercher la position
  • in : t la tournée
  • */
int rechercher(int ville,tournee t) { int i; for(i=0;i<t.nb_villes;++i) { if(t.villes[i] ==ville) return i; } return -1; } /*
  • Calcule les début et la fin des trajets détruits
  • in : tab le tableau qui contiendra les villes
  • in : t la tournée
  • out : n la taille de tableau
  • */
void calcul_extrimite(int tab[MAX],tournee t,int * n) { int i; int j =0; bool trouv =false; int a = *n; for(i=0;;++i) { if(a==1) break; if(t.villes[i]==*n) { if(trouv) { if(tab[j-1] != t.villes[i-1]){ tab[j] = t.villes[i-1]; j++; } tab[j] = t.villes[i+1]; j++; } else { tab[j] = t.villes[i+1]; j++; trouv = true; } } else { a--; } } if(tab[j-1] != t.villes[i-1]){ tab[j] = t.villes[i-1]; j++; }
  • n =j;
} /*
  • Permet de génégrer deux entiers différents et inférieurs à un nombre maximum donnée
  • out : a le premier nombre
  • out : b le deuxième nombre
  • in : maxi la borne supérieure
  • */
void generer_enitiers(int *a, int *b,int maxi) { srand(time(NULL));
  • a = rand()%maxi;
do {
  • b = rand()%maxi;
}while(*a==*b); } /*
  • L'algorithme de croisement DPX
  • in : s l'instance
  • in out : tab le tableau des tournées
  • in : nb_tournees le nombre de tournées
  • in : prob la probabilité de mutation
  • in ; nb_croisements le nombre de fois il faut le croisement
  • */
void DPX(instance s, tournee tab[MAX], int nb_tournees, float prob,int nb_croisements){ int i,j; while(nb_croisements !=0) { generer_enitiers(&i,&j,nb_tournees); tournee fille = tournee_fille(tab[i],tab[j],s); if((float)(rand()%100)/100 < prob) fille = muter(fille,s); tournee t = moins_performant(tab,nb_tournees); remplace_tournee(tab,nb_tournees,t,fille); nb_croisements--; } } /*
  • affiche une solution trouvée selon un algorithme génétique
  • in s : l'instance
  • in : le nombre de tournées qu'il faut prendre
  • in : nb_generations le nombre de générations
  • in : nb_croisements le nombre de croisement par génération
  • */
void ga(instance s, int nb_tournees, float prob,int nb_generations,int nb_croisements) { srand(time(NULL)); tournee tab[MAX]; genere_tournees(tab,s,nb_tournees); do { DPX(s,tab,nb_tournees,prob,nb_croisements); nb_generations--; } while(nb_generations != 0); printf("Meilleur résulat avec l'algorithme génitique est : \n"); affich_tournee(meilleur_tournee(tab,nb_tournees)); } /*
  • permet de muter une tournée en appliquant une 2-optimisation
  • in : t la tournée à muter
  • in : s l'instance
  • */
tournee muter(tournee t,instance s) { return op2(t,s,-1,-1); } /*
  • Retourne la tournée qui a la plus petite longeur
  • in : tab le tableau des tournées
  • in : taille la taille de tableau
  • */
tournee meilleur_tournee(tournee tab[MAX],int taille) { int i; tournee t = tab[0]; for(i=1;i<taille;++i) { if(tab[i].longeur < t.longeur) t = tab[i]; } return t; } /*
  • Retourne la tournée qui la plus grande longeur
  • in : tab le tableau des tournées
  • in : la taille de tableau
    • /
tournee moins_performant(tournee tab[MAX],int taille) { int i; tournee t2 = tab[0]; for(i=0;i<taille;++i) { if(tab[i].longeur > t2.longeur) t2 = tab[i]; } return t2; } /*
  • Remplace une tournée par une autre
  • in out : tab le tableau des tournées
  • in : t1 la tournée à remplacer
  • in : t2 la tournée de remplaçement
  • */
void remplace_tournee(tournee tab[MAX],int taille,tournee t1,tournee t2) { int i; for(i=0;i<taille;++i) { if(egal(tab[i],t1)) { tab[i] = t2; break; } } } /*
  • Programme principal
  • */
int main(int argc, char *argv[]) { int i; if( (i = Pos_Chaine(argv,"-f",argc)) != -1){ if(i+1>=argc) printf("Error : fichier manquant!\n"); else{ f = fopen(argv[i+1],"r"); if(f==NULL){ printf("Le fichier %s ne peut être lu!",argv[i+1]); return 1; } } } else{ printf("Error : balise -f non trouvée\n"); return 1; } if(Pos_Chaine(argv,"-v",argc)!=-1) verbose = true; if(Pos_Chaine(argv,"-bf",argc) != -1){ instance s = lire(f); brut_force(s); } if((i = Pos_Chaine(argv,"-lsr",argc)) !=-1){ if(i+1<argc){ int param = atoi(argv[i+1]); if(param == 0) { printf("Error : -lsr paramèetre non valide"); return 2; } instance s = lire(f); lsr(param,s); } else{ printf("Error : -lsr paramèetre non valide"); return 2; } } if((i = Pos_Chaine(argv,"-lsnr",argc)) !=-1){ if(i+1<argc){ int param = atoi(argv[i+1]); if(param == 0) { printf("Error : -lsnr paramèetre non valide"); return 2; } instance s = lire(f); lsnr(param,s); } else{ printf("Error : -lsnr paramèetre non valide"); return 2; } } if((i = Pos_Chaine(argv,"-ga",argc)) !=-1){ if(i+1<argc){ int nb_generation = atoi(argv[i+1]); if(nb_generation == 0) { printf("Error : -ga paramèetre non valide"); return 2; } float prob = 0.3; int nb_tournee = 20; if(i+2 < argc) { prob = atof(argv[i+2]); if(prob ==0) prob = 0.3; else { if(i+3 < argc) { nb_tournee = atoi(argv[i+3]); if(nb_tournee ==0) nb_tournee = 20; } } } instance s = lire(f); ga(s,nb_tournee,prob,nb_generation,1); } else{ printf("Error : -lsnr paramèetre non valide"); return 2; } } return 0; } //tsp.h #ifndef TSP_H_INCLUDED #define TSP_H_INCLUDED #define MAX 200 typedef struct{ char nom[MAX]; int nombre; float cor[MAX][3]; float distances[MAX*MAX]; } instance; typedef struct{ int nb_villes; float longeur; int villes[MAX]; } tournee; bool verbose = false; FILE * f; float distance (instance s, int i, int j); tournee op2(tournee t,instance s,int a,int b); void affich_instance(instance s); int Pos_Chaine(char **tab,char *ch,int n); void supprime_espace_double(char * ch); bool egal(tournee t1,tournee t2); bool existe(tournee t,tournee tab[MAX],int n); void genere_tournees(tournee tab[MAX], instance s, int n); void enlever (char *ch1,char ch2[], unsigned int debut); void decoupe (char *ch,char c,char tab[MAX][MAX],int *k); void calcule_distances (float cor[MAX][3],float distances[MAX],int n); instance lire (FILE * f); bool next_permutation(int tab[MAX],int taille); float distance (instance s, int i, int j); void longeur_tournee(tournee * t,instance s); tournee genere_tournee(instance s); void affich_tournee(tournee t); void brut_force(instance s); void lsr(int param,instance s); void lsnr(int param,instance s); tournee op2(tournee t,instance s,int a,int b); tournee reconecte_tournee(tournee fille,tournee parent_1,tournee parent_2, instance s); void calcul_extrimite(int tab[MAX],tournee t,int * n); int rechercher(int ville,tournee t); int plus_proche_voisin(int ville,int villes[MAX],tournee t1,tournee t2,instance s,int taille); void remplace_tournee(tournee tab[MAX],int taille,tournee t1,tournee t2); tournee moins_performant(tournee tab[MAX],int taille); tournee meilleur_tournee(tournee tab[MAX],int taille); tournee muter(tournee t,instance s); #endif // TSP_H_INCLUDED

Codes Sources

A voir également

Ajouter un commentaire

Commentaire

Messages postés
69
Date d'inscription
mardi 11 avril 2006
Statut
Membre
Dernière intervention
23 juillet 2013

Bonjour !
Je suis débutant !
Merci pour la source.

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.