Giz | Tentacle a écrit :
J'ai peut-être trouvé une autre méthode, faudrait juste vérifier que c'est bien en O(n) (à priori oui si j'ai bien compris) :
Je me suis dit que si un élément est présent plus de la moitié de la taille du tableau, alors on peut dire qu'un élément sur 2 est cet élément (qu'on peut appelé A). Evidemment si on pioche un élément sur 2 dans le tableau, on ne trouvera pas forcemment A. Il faut pour cela que A soit régulièrement réparti dans le tableau et dans ce cas-là, en découpant le tableau en couple d'éléments, on trouvera A dans chacun des couples.
Comment répartir A ? Comme définition d'une répartition, je me suis fixé qu'il ne fallait jamais que 2 éléments consécutifs soient identiques. En fait on va balayé le tableau de départ et on va vérifié si on peut ajouter l'élément en cours dans le tableau d'arrivée:
si élément <> élémentprécédent alors
on ajoute l'élément dans le tableau résultat
sinon
on mets cet élément dans une pile à part
fin si |
on va en fait empiler les éléments identiques jusqu'à avoir un élément différent qu'on va mettre dans le tableau résultat. À ce moment là, on va pouvoir vider un élément de la pile et le mettre dans le résultat. Et on continue ...
A-t-on besoin d'une pile ? Ce que je veux dire, c'est qu'on peut remarquer que les éléments de la pile seront toujours identiques :
imaginez une suite : A A B B
arrivé au premier B, on a A dans la pile (et A dans le résultat) ... évidemment si on passe tout de suite au 2eme B, ca posera problème mais en fait on va ajouter le premier B dans le résultat et on va donc pouvoir dépiler le A et ensuite mettre le B donc tant qu'il y a aura des éléments dans la pile, on pourra séparer tout les doublons.
Il suffit donc d'avoir 2 variables : une pour avoir la valeur des éléments dans la pile, et l'autre pour avoir la taille de cette pile.
C'est bien joli, mais sur l'exemple : B C D D D, cet algo va renvoyer la même chose.
Bah alors on va faire l'algo dans les 2 sens ! et là ça donnera D C D B D .
Je sais pas trop comment démontrer que ça marche à tout les coups (quand c'est faisable évidemment ... car si ya 4 D, on ne peut pas répartir correctement), j'ai fait des tests avec 100000 tableaux tirés aléatoirement de 10 éléments et je n'ai pas eu de problème (c'est à dire que quand il n'a pas pu répartir correctement, donc il s'est retrouvé avec au moins 2 fois le même élément à la fin du tableau, c'était quand il y avait trop d'un élément). je suis d'accord c'est pas une preuve.
Pour faire un image, faut s'imaginer des pièces placées au hasard sur un tapis. On forme une vague avec le tapis allant dans un sens et ainsi on va regrouper toutes les pièces d'un côté. Ensuite on reforme une vague dans l'autre sens, et là ça répartit régulièrement.
On a donc maintenant répartir les éléments du tableau.
- Une première remarque, si cette répartition échoue, donc si on se retrouve avec plusieurs fois le même élément à la fin du tableau, c'est que cet élément est présent plus de N/2 fois.
- Si la répartition fonctionne, les éléments qui pourraient être présent plus de N/2 fois sont soit le premier élément, soit le dernier du tableau réparti. Exemple :
A B A C A D (nombre pair d'élément)
B A C A D A (nombre pair d'élément)
A B A C A (nombre impair d'élément) |
On remarque que dans le cas d'un nombre impair d'élément, l'élément présent plus de N/2 fois (s'il existe) est en première et dernière position. De même si la répartition ne fonctionne pas, et ce quelquesoit la parité de la taille du tableau, l'élément A sera aussi en première et dernière position (sisi ... le premier tri ne marchera pas, donc des A seront groupés à la fin du tableau, et il y a aura forcement un A en première position lors du tri suivant ... mais aussi en dernière car le second tri ne marchera pas mieux).
On regroupe donc 2 cas dans un test : premier élément == dernier élément .
Si ce test échoue, on compte le nombre de fois qu'apparaît le premier élément et le dernier élément du tableau trié et si l'un des 2 est présent plus de N/2 fois, c'est bon, sinon il n'y a aucune solution.
Remarque : pour être plus rapide, au lieu de compter le nombre d'itération du premier et du dernier élément, on peut tout simplement lire 1 élément sur 2 du tableau (les indices impairs dans un premier temps) et si lors d'un balayage on trouve une valeur différente des autres, c'est que cet élément n'est pas présent plus de N/2 fois, on peut donc arrêter le balayage et passer au suivant (indices pairs).
En ce qui concerne la complexité ... cet algo balaye au plus 4 fois la tableau, et ce quelquesoit la taille de ce dernier, donc il me semble que ça ne dépend que de N (si j'ai bien compris le principe de complexité)
Pour exemple, voici en PHP la fonction qui répartit les éléments :
Code :
- function DistributeValues ($Table) {
- $CachedValue = false; // Valeur dans la pile
- $CacheSize = 0; // Taille de la pile
- $LastValue = false; // Dernière valeur ajoutée
- $TableResult = array (); // le résultat
- foreach ($Table as $Value) {
- if (!($LastValue === $Value)) {
- array_push ($TableResult, $Value);
- $LastValue = $Value;
- // On vérifie si on peut dépiler un élément
- if (($CacheSize > 0) and ($CachedValue <> $LastValue)) {
- array_push ($TableResult, $CachedValue);
- $CacheSize --;
- $LastValue = $CachedValue;
- }
- } else {
- $CacheSize ++;
- $CachedValue = $Value;
- }
- }
- // On n'oublie pas les éléments de la pile pour le résultat
- for ($i = 1; $i <= $CacheSize; $i++) {
- array_push ($TableResult, $CachedValue);
- }
- return $TableResult;
- }
|
Ensuite pour trier un tableau, faut appeler cette fonction 2 fois en inversant le tableau entre temps :
Code :
- $Result = DistributeValues (array_reverse (DistributeValues($Table)));
|
Remarque : il me semble, dans la fonction de répartition, qu'on n'a pas besoin de vérifier si on peut dépiler un élément ou pas : si on arrive à ajouter un élément différent du précédent, il sera forcemment différent de la pile (sisi) et on pourra donc dépiler un élément. (biensûr faut tester si la pile n'est pas vide)
Cette solution est évidemment moins belle que celle proposée par tomlameche mais je la trouve plus abordable (peut-être parce que je vois plus facilement la démonstration) ... par contre en ce qui concerne de la pondre en 30 min (5pts de l'examen sur 2h) ...
Il doit y avoir une solution plus simple à trouver je pense !
|
algorithme: nb_occurences
donnees: tab_init: entier, tableau contenant les valeurs initiales
N: entier, taille du tableau initial
variables: i: entier, indice de parcours du tableau initial
j: entier, indice de parcours du tableau resultat
end: entier, indice de la fin de la pile (= nombre de valeurs empilees moins 1 !)
compteur: entier, compte le nombre d'occurences identiques
tab_res: entier, tableau resultat "trie" de taille N
tab_pile: entier, tableau utilise comme pile de taille N
constantes:
resultat: VRAI: booleen, le tableau initial contient au moins N/2 occurences egales
FAUX: booleen, le tableau initial ne contient pas d'occurences presentent au moins N/2 fois
1 Debut
2 | //initialisation des indices (numero de la case)
3 | j <- 1;
4 | end <- 1;
5 | //copie du tableau initial dans le tableau resultat, pas neccessaire, copier juste la 1ere case
6 | pour i <- 1 a N
7 | | tab_res[i] <- tab_init[i];
8 | finpour
9 | //parcours unique du tableau initial pour "trie" le tableau resultat
10 | pour i <- 2 a N faire
11 | | //on insere une valeur de tab_pile dans tab_res en priorite
12 | | si end > 1 ET tab_pile[end - 1] <> tab_res[j]
13 | | | tab_res[j + 1] <- tab_pile[end - 1];
14 | | | j <- j + 1;
15 | | | end <- end - 1;
16 | | | i <- i - 1;
17 | | sinon
18 | | | //on empeche d'inserer une valeur egale a celle presente dans la case d'avant dans le tableau resultat
19 | | | si tab_init[i] = tab_res[j] alors
20 | | | | //on empile la valeur
21 | | | | tab_pile[end] <- tab_init[i];
22 | | | | end <- end + 1;
23 | | | //on insere la valeur de tab_init dans le tableau resultat
24 | | | sinon
25 | | | | tab_res[j + 1] <- tab_init[i];
26 | | | | j <- j + 1;
27 | | | finsi
28 | | finsi
29 | finpour
30 |
31 | //on vide la pile en la copiant a la fin du tableau res
32 | tant que end > 1 faire
33 | | tab_res[j + 1] = tab_pile[end - 1];
34 | | end <- end - 1;
35 | | j <- j + 1;
35 | fintantque
36 |
37 | //verification de la valeur de la 1ere case si au moins presente N/2 fois
38 | j <- tab_res[1];
39 | compteur <- 0;
40 | pour i <- 1 a N faire
41 | | si tab_res[i] = j alors
42 | | | compteur <- compteur + 1;
43 | | finsi
44 | finpour
45 | //cas N impair
46 | si (N%2 == 1)
47 | | si compteur > N/2
48 | | | retourner VRAI;
49 | | finsi
50 | //cas N pair
51 | sinon
52 | | si compteur >= N/2
53 | | | retourner VRAI;
54 | | finsi
55 | finsi
56 |
57 | //verification de la valeur de la derniere case si au moins presente N/2 fois
58 | j <- tab_res[N];
59 | compteur <- 0;
60 | pour i <- 1 a N faire
61 | | si tab_res[i] = j alors
62 | | | compteur <- compteur + 1;
63 | | finsi
64 | finpour
65 | //cas N impair
66 | si (N%2 == 1)
67 | | si compteur > N/2
68 | | | retourner VRAI;
69 | | finsi
70 | //cas N pair
71 | sinon
72 | | si compteur >= N/2
73 | | | retourner VRAI;
74 | | finsi
75 | finsi
76 |
77 | retourner FAUX;
78 fin
|
Voici enfin une solution sensee que Mr Tentacle en personne a trouve ! felicitation Mr Tentacle
En fait je me suis permis de le "mettre au propre" avec un algorithme (langage generique ) clair et présentable.
Je vais expliquer à ma façon comment marche cet algo, même si Tentacle l'a déjà expliqué à sa façon
/* le principe général : */
Citation :
Je me suis dit que si un élément est présent plus de la moitié de la taille du tableau, alors on peut dire qu'un élément sur 2 est cet élément (qu'on peut appelé A). Evidemment si on pioche un élément sur 2 dans le tableau, on ne trouvera pas forcemment A. Il faut pour cela que A soit régulièrement réparti dans le tableau et dans ce cas-là, en découpant le tableau en couple d'éléments, on trouvera A dans chacun des couples.
Comment répartir A ? Comme définition d'une répartition, je me suis fixé qu'il ne fallait jamais que 2 éléments consécutifs soient identiques. En fait on va balayé le tableau de départ et on va vérifié si on peut ajouter l'élément en cours dans le tableau d'arrivée
|
/* le fonctionnement */
Bon la rien à reprendre c'est le principe même de l'algorithme. Pour répartir uniformément les valeurs, on a besoin de 2 tableaux temporaires :
-un tableau nommé "resultat" qui contiendra les valeurs du tableau initial triées. J'entends (comme Tentacle ) par trié le tableau contenant les valeurs reparties uniformément de façon à ce qu'on est JAMAIS 2 cases consécutives du tableau résultat ayant la meme valeur...et non pas un tri rapide !
Cependant cette propriété sera fausse à partir de la ligne 32 (cf algo).
-un tableau nommé "pile" qui, non initialisé, se comportera comme une pile : il s'agit juste d'une file LIFO.
Un seul parcours du tableau initial ("tab_init" ) sera nécessaire et prouvera donc la complexité en O(N). (1 parcours de N)
1ere etape : on copie le tableau initial dans le tableau resultat (en fait c'est pas nécessaire, mais bon tant pis)
2eme etape : On parcours le tableau initial. Pour chaque valeur (que je nomme X):
- si la case dernierement rempli dans tab_res (tableau resultat) a la meme valeur que X, on empile la valeur X dans le tableau tab_pile (la pile)
-sinon on regarde si la valeur a depiler est differente de la case dernierement rempli dans tab_res; si oui, on privilegiera de mettre la valeur a dépiler dans tab_res au lieu de la valeur X (et on restera sur cette valeur X pour la prochaine iteration). Si non, on met la valeur X dans tab_res (et on passe a la prochaine valeur X dans tab_init).
A l'issue de cette 2ème étape, on garanti que la valeur A (dont parle Tentacle) est uniformément répartie.
Par conséquent, si la valeur A est présente au moins N/2 fois, seuls (et oui SEULS) 3 cas de figure du tableau tab_res peuvent apparaitre :
Citation :
A B A C A D (nombre pair d'élément)
B A C A D A (nombre pair d'élément)
A B A C A (nombre impair d'élément)
|
On remarquera ceci : Si A est solution (presente au moins N/2 fois), soit A est presente en debut de tableau (dans le tableau resultat), soit en fin, soit en debut et en fin de tableau.
Cependant avec de tester ces valeurs, on doit proceder a l'etape suivante :
3eme etape : depiler la pile tant que possible et la copier a la fin du tableau resultat
4eme etape: On prend la 1ere valeur du tableau resultat et on regarde si elle est presente N/2 fois au moins.(2 parcours de N)
On procede de meme avec la derniere valeur du tableau resultat (3 parcours de N)
Et c'est fini ! si la 4eme etape n'a pas retourner VRAI alors on retourne FAUX. Finalement cet algo est court et simple a implementer en soit...meme si j'en aurais jamais eu l'idee (ce qui est le plus dur pour un algo )
Cet algo aurait une complexite de 3*O(N) (parcours du tableau initial + verif de la 1ere valeur du tableau resultat + verif de la derniere valeur du tableau resultat) et une complexite memoire de 3*O(N) (dans mon exemple)...tout ceci reste donc tout a fait acceptable.
/* optimisation possible */
-la copie du tableau initial dans le tableau resultat ne sert a rien : copier juste la 1ere case de tab_init ds tab_res (amelioration en temps)
-Gerer la pile par une liste chainee (amelioration en memoire)
-la verification de la 1ere et derniere valeur peut etre plus rapide : (amelioration en temps)
Citation :
Remarque : pour être plus rapide, au lieu de compter le nombre d'itération du premier et du dernier élément, on peut tout simplement lire 1 élément sur 2 du tableau (les indices impairs dans un premier temps) et si lors d'un balayage on trouve une valeur différente des autres, c'est que cet élément n'est pas présent plus de N/2 fois, on peut donc arrêter le balayage et passer au suivant (indices pairs).
|
bref avec une complexité en O(N), ce serai presque la complexité mémoire qui devient importante (au moins l'allocation d'un tableau énorme prend beaucoup de place et en plus est assez couteux en temps).
Ceci est donc une solution, meme si ce n'est pas la seule (enfin moi j'en vois pas d'autres ) , cet algo est le seul trouve jusqu'ici (qui m'a parfaitement convaincu )
Encore mes félicitations Mr Tentacle
PS : On vient de recevoir la note du DS : 7.6/20 de moyenne (promo de 68), j'ai eu 8.25/20...euh en fait non 8.25/10 (ben j'ai zapé cet exo, et un autre...qui etait qu'un pauvre tri rapide qu'en j'y pense ). Le major a eu quand meme "que" 14/20 (jsuis sur qu'il a perdu tout ses points a cet exo ...d'ailleurs a la fin de l'exam il était allé demande la solution a la prof )...Honnetement, vous auriez trouvé une telle solution (2 tableaux temporaires dont une pile) en 30 min ? :sarcastic (ca fait + d'un mois qu'on est sur ce topic ) Message édité par Giz le 27-01-2004 à 10:07:19
|