Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Routine de trie pour une liste de structure.

39 réponses
Avatar
Zorro
Bonjour,

Je voudrais trier par ordre alphabethique( toto->name) une liste de
structure se présentant sous cette forme:

typedef struct _toto
{
..blabla..
char *name;
struct _toto *next;
}toto;

La mémoire de chaques structures a été alloué avec un malloc, je ne suis
donc pas dans le cas d'une liste de structure stocké dans un tableau. (
genre struct toto[xx];), de ce fait, je ne peux utiliser qsort().

Quelqu'un pourait-il me proposer une routine rapide pour ce traitement?

Merci d'avance.

Zorro
--
Webpage: http://zorro.arcadia-crew.com
Blog: http://zorro-arcadia.blogspot.com

9 réponses

1 2 3 4
Avatar
Emmanuel Delahaye
Antoine Leca wrote on 12/09/05 :
On mesure plutôt le nombre d'instructions.


... que tu définis comment ?


En gros ce qu'il y a entre 2 ';'. Avec quelques extras comme le 'comma
operator'.


--
Emmanuel
The C-FAQ: http://www.eskimo.com/~scs/C-faq/faq.html
The C-library: http://www.dinkumware.com/refxc.html

"It's specified. But anyone who writes code like that should be
transmogrified into earthworms and fed to ducks." -- Chris Dollin CLC


Avatar
Richard Delorme
Richard Delorme wrote on 12/09/05 :

Les lignes d'aération ne sont pas compté par les programmes qui
comptent les lignes de codes *significatives*.


La notion de ligne de code n'existe pas en C (C != Assembleur). On
mesure plutôt le nombre d'instructions.


http://www.google.fr/search?hl=fr&q=SLOC

--
Richard


Avatar
Charlie Gordon
"Richard Delorme" wrote in message
news:43255e40$0$304$
"Richard Delorme" wrote in message
news:4324794b$0$303$
Cela dépend énormément des données : ton algorithme a une complexité
quadratique, ce qui signifie qu'il prend un temps proportionnel au carré de
la


longueur de la liste, le cas le pire étant celui de la liste déjà triée (!).


Possible, mais le pire cas est excessivement rare.


Dans le cas particulier du contexte d'othello, mais quand tu écris une routine
de tri, qui sera peut-etre réutilisée ailleurs, voir mal utilisée (appelée
plusieurs fois) c'est une mauvaise idée d'avoir un comportement inefficace quand
il n'y a rien à faire (liste déjà triée).

En
revanche la complexité de qsort est n * log(n) ce qui est bien plus
favorable


pour des valeurs de n suffisamment grandes. Quelle est en moyenne la taille
de


tes listes ?


environ 10, au maximum (très rare) 32, au minimum 1. Le fait que qsort()
doit appeler une fonction de comparaison externe le ralentit beaucoup en
pratique.


Bonne remarque. De plus qsort est trop generale, elle est inefficace pour trier
des tableaux de pointeurs à cause des indirections inutiles, quant à trier des
tableaux de structures, c'est inefficace à cause des déplacements couteux qu'un
tableau de pointeurs permet d'éviter. Conclusion : ne pas utiliser qsort,
organiser ses données pour pouvoir utiliser pqsort, une version qui opère sur
des tableaux de pointeurs de structures, voire une version custom qui fait les
comparaisons inline pour des structures spécifiques. En C++ on peut utiliser
des templates pour ca, en C il faut être particulièrement hardi pour vouloir
reimplementer qsort pour des types spécifiques.

Mais c'est une approche possible, vu que tu connais la taille maximale du
tableau.

Note de plus que ton implémentation plante si la liste est vide.


Impossible, on a toujours un coup à jouer (passer son tour est un coup
légal dans mon implémentation).


Oui, meme remarque que ci-dessus, c'est une contrainte muette qui justifierait
un commentaire d'explications, et éviterait un problème au programmeur suivant
qui voudrait réutiliser ton code sans bien tout comprendre.

La boucle do/while n'a aucun intérêt : une boucle while ou for serait plus
efficace.
Enfin il est facile d'accélérer le cas pathologique de la liste déjà
partiellement ou totalement triée.

void movelist_sort(MoveList *movelist) {
Move *i, *pi, *j, *pj;

pi = movelist->move->next;
if (pi == NULL)
return;
for (; (i = pi->next) != NULL; pi = i) {
if (i->val >= pi->val)
continue;
for (pj = movelist->move; (j = pj->next) != i; pj = j) {
if (j->val < i->val) {
pj->next = i;
pi->next = i->next;
i->next = j;
i = pi;
break;
}
}
}
}

A toi de voir si cela change les performances.


En effet. Sur une série de position de tests tirée d'une revue de la
fédération française d'Othello :
Avant :
fforum-20-39.scr : 138939725 nodes in 00:00:29.9, 4634809 N/s
Après, avec l'algorithme modifié de Chqrlie Gordon :
fforum-20-39.scr : 2400849827 nodes in 00:06:20.9, 6301574 N/s
C'est 13 fois plus lent...
Il y a une erreur dans ton exemple, il ne trie pas les coups... En
corrigeant le « if (i->val >= pi->val) » en « if (i->val <= pi->val) »
et en supprimant l'inutile test de vide liste, on obtient les mêmes
performances qu'avant.


Effectivement, honte sur moi, la liste est triée par ordre décroissant de val...
je n'avais pas de quoi tester : code non testé = bug probable.

On peut peut-être gagner quelques cycles en utilisant une variable locale pour
i->val (je vais supposer que son type est int):

void movelist_sort(MoveList *movelist) {
Move *i, *pi, *j, *pj;
int val;

pi = movelist->move->next;
if (pi == NULL)
return;
for (; (i = pi->next) != NULL; pi = i) {
if ((val = i->val) <= pi->val)
continue;
for (pj = movelist->move; (j = pj->next) != i; pj = j) {
if (j->val < val) {
pj->next = i;
pi->next = i->next;
i->next = j;
i = pi;
break;
}
}
}
}

Mais encore faudrait-il que la routine de tri représente une part significative
du temps de calcul, as-tu fait un peu de profiling ?

--
Chqrlie.


Avatar
Charlie Gordon
"Jean-Marc Bourguet" wrote in message
news:
"Charlie Gordon" writes:

Ca ressemble à un bubble sort, c'est un excellent algo de tri pour les
listes qui font 1 ou 2 éléments.




Mauvaise attribution, c'est Harpo qui a dit cela.

En pratique le bubble sort (ou le shaker sort qui est une variante)
est bien adapte aux cas ou les donnees sont quasiment triees: il peut
avoir un comportement en n sous certaines conditions alors qu'un quick
sort -- mal implemente -- peut meme exhiber un comportement
quadratique . Un bon professionnel -- puisque le terme a ete lance --
va donc l'utiliser dans certains cas.

Non, c'est effectivement un tri par insertion (complexité n^2),
nettement plus efficace que bubble sort (n^3 ?),


bubble sort est aussi du n^2 (facile a montrer: a chaque passe au
moins un element en plus est a sa place terminale)


Mais à un prix pplus important que le tri par insertion : plus d'échanges.

mais moins que qsort (n.log(n)),


qsort c'est difficile a dire, il n'y a pas de contrainte.
L'implementation classique est un quick sort qui a une complexite
quadratique egalement dans le pire cas (attention, suivant la maniere
de choisir le pivot, et les caracteristiques des donnees, le pire cas
peut arriver de maniere certaine dans des cas courants) mais n log n
en moyenne. J'ai deja vu decrite des implementations recourant au
heap sort (garanti n log n -- mais avec une constante relativement
mauvaise) si elles remarquent que la recursion a l'air de prendre un
mauvais bout.


Oui, la complexité de quick sort est un sujet difficile, et son implémentation
est une source de maux de tete considérable.
On peut ajouter au rang de ses "problèmes" la non stabilité du tri et la
propension à produire des résultats surprenants quand la fonction de comparaison
a de mauvaises proprietes : pas exemple strcasecmp n'est pas nécessairement
transitive.

--
Chqrlie.



Avatar
Charlie Gordon
"Antoine Leca" wrote in message
news:dg4gsv$l5p$
En <news:,
Emmanuel Delahaye va escriure:
La notion de ligne de code n'existe pas en C (C != Assembleur).


C'est bien connu que les projets sont mesurés en LOA...

On mesure plutôt le nombre d'instructions.


... que tu définis comment ?
Pour faire simple, est-ce que cela donne le même nombre de celui de
fonctions (puisque en C, chaque instruction est imbriquée dans une
instruction comme par exemple un bloc, et tout ceci remonte au niveau de la
fonction qui n'admet comme « instruction » que le type « bloc ».


sloccount ?

--
Chqrlie.


Avatar
Charlie Gordon
"Harpo" wrote in message
news:432574be$0$1711$
Antoine Leca wrote:

un vrai chef (de cuisine).


Je vous conseille 'l'algorithmique du goût' de Brillat-Savarin.


Brillat Savarin a écrit "La Physiologie du Goût". Mais ta proposition me plait
bien, m'autorises tu à en faire le titre d'un ouvrage à paraître sur la
programmation considérée comme un art ?

Les professionnels ne donnent que des avis valables en général, par
manque d'informations sur le contexte.


Ëtre pro, ce serait reconnaître ses limites ?


Absolument. En ce qui concerne le langage C, acquérir cette sagesse demande
énormément de temps et d'efforts.

--
Chqrlie.


Avatar
Richard Delorme

Cela dépend énormément des données : ton algorithme a une complexité
quadratique, ce qui signifie qu'il prend un temps proportionnel au carré de
la longueur de la liste, le cas le pire étant celui de la liste déjà triée (!).


Possible, mais le pire cas est excessivement rare.


Dans le cas particulier du contexte d'othello, mais quand tu écris une routine
de tri, qui sera peut-etre réutilisée ailleurs, voir mal utilisée (appelée
plusieurs fois) c'est une mauvaise idée d'avoir un comportement inefficace quand
il n'y a rien à faire (liste déjà triée).


Disons que c'est une routine qui ne doit pas être utilisée ailleurs...
Le problème est que, si le cas pathologique est suffisament rare, le
traiter systématiquement a un coût plus important que l'ignorer.

Note de plus que ton implémentation plante si la liste est vide.


Impossible, on a toujours un coup à jouer (passer son tour est un coup
légal dans mon implémentation).


Oui, meme remarque que ci-dessus, c'est une contrainte muette qui justifierait
un commentaire d'explications, et éviterait un problème au programmeur suivant
qui voudrait réutiliser ton code sans bien tout comprendre.


C'est écrit quelque part, mais mon exemple ne reprenait pas les
commentaires.

La boucle do/while n'a aucun intérêt : une boucle while ou for serait plus
efficace.




Je n'ai pas tiqué là dessus la première fois, mais j'ai du mal à
comprendre comment un do/while peut-être moins efficace qu'un while ou
un for, alors qu'il fait une comparaison en moins...

On peut peut-être gagner quelques cycles en utilisant une variable locale pour
i->val (je vais supposer que son type est int):


A priori non. Le couple compilateur/CPU doit-être suffisament efficace.

Mais encore faudrait-il que la routine de tri représente une part significative
du temps de calcul, as-tu fait un peu de profiling ?


Actuellement, la part de cette fonction est d'environ 1.5% d'après
gprof, mais :
1) gprof marche mal. Entre deux implémentations, l'une rendant le
programme globalement 1% plus rapide que l'autre, gprof trouve la même
part d'utilisation de 1,5%.
2) j'en suis aux micro-optimisations depuis un moment. Pourcent par
pourcent, j'ai gagné un facteur 5 en vitesse depuis 5 ans.
3) le taux d'utilisation de cette fonction pourrait être supérieur si
elle était plus efficace.

--
Richard



Avatar
Charlie Gordon
"Richard Delorme" wrote in message
news:432840e4$0$303$

La boucle do/while n'a aucun intérêt : une boucle while ou for serait plus
efficace.




Je n'ai pas tiqué là dessus la première fois, mais j'ai du mal à
comprendre comment un do/while peut-être moins efficace qu'un while ou
un for, alors qu'il fait une comparaison en moins...


Elle fait une comparaison en moins, mais le corps de la boucle est executé une
fois de plus avec le do/while dans le cas ou l'élément à insérer est déjà à sa
place en fin de liste.

On peut peut-être gagner quelques cycles en utilisant une variable locale
pour


i->val (je vais supposer que son type est int):


A priori non. Le couple compilateur/CPU doit-être suffisament efficace.


Ca peut se verifier en examinant le code produit, mais de nos jours il est très
difficile d'évaluer la performance d'un bout d'assembleur à l'oeil nu. Seule
l'analyse de performance sur les cas concrets est pertinente (profiling).

Mais encore faudrait-il que la routine de tri représente une part
significative


du temps de calcul, as-tu fait un peu de profiling ?


Actuellement, la part de cette fonction est d'environ 1.5% d'après
gprof, mais :


cela semble indiquer que tu concentres tes efforts sur le mauvais cheval.

1) gprof marche mal. Entre deux implémentations, l'une rendant le
programme globalement 1% plus rapide que l'autre, gprof trouve la même
part d'utilisation de 1,5%.


C'est vrai, mais la difference de perf pourrait aussi s'expliquer par les effets
de bord que la modif de cette routine peut avoir sur le reste du code : par
exemple changements d'alignement dans d'autres fonctions deont le code pourtant
identique a ete décalé à cause des modifs...

2) j'en suis aux micro-optimisations depuis un moment. Pourcent par
pourcent, j'ai gagné un facteur 5 en vitesse depuis 5 ans.


A conditions constantes je suppose? parce que c'est le rapport moyen de
performances obtenu en suivant betement l'evolution du hard à logiciel constant.
Donc en changeant de machine (pour un prix modique d'ailleurs) tu devrais avoir
un facteur 25.

3) le taux d'utilisation de cette fonction pourrait être supérieur si
elle était plus efficace.


Ca depend de ce que tu entends par là :
- tu ferais plus usage de cette fonction, dans d'autres parties de l'algorithme,
ou plus souvent au cours de l'analyse des coups. Alors oui, l'optimisation
paierait, mais vu qu'elle ne represente que 1.5% aujourd'hui, tu pourrais deja
mettre cette demarche en pratique.
- en revanche, l'optimisation lui permet de prendre moins de temps, et par
consequent d'avoir un taux *moins* important. C'est le dilemme de l'optimisation
: plus tu optimises un truc, moins il reste à gratter dessus :-(

--
Chqrlie




Avatar
Richard Delorme


[...] j'ai du mal à
comprendre comment un do/while peut-être moins efficace qu'un while
ou un for, alors qu'il fait une comparaison en moins...


Elle fait une comparaison en moins, mais le corps de la boucle est
executé une fois de plus avec le do/while dans le cas ou l'élément à
insérer est déjà à sa place en fin de liste.


Dans le morceau de code que j'ai fourni, le corps de la boucle est
toujours effectué une fois, même si je place le while en tête. C'est
pour ça que je l'ai mis à la fin. On est dans un cas similaire à ça :

int i, j, n;
n = 42;
for (i = 2; i < n; ++i) {
for (j = 1; j < i; ++j) {
/* corps */
}
}

Comme la valeur de i va de 2 à n, quand j vaut 1, il est nécessairement
inférieur à i, et la première comparaison est inutile. Le do/while
suivant constitue donc une optimisation possible :

for (i = 2; i < n; ++i) {
j = 1;
do {
/* corps */
++j;
} while (j < i);
}

Aujourd'hui, il est possible que les compilateurs savent traduire un for
en do/while quand il le faut, mais sans doute pas à l'époque où j'ai
écrit ce code.


Mais encore faudrait-il que la routine de tri représente une part
significative du temps de calcul, as-tu fait un peu de profiling ?


Actuellement, la part de cette fonction est d'environ 1.5% d'après
gprof, mais :


cela semble indiquer que tu concentres tes efforts sur le mauvais
cheval.


Je ne me concentre plus tellement là dessus. Et le 1.5%, c'est après
avoir écarter les solutions plus lentes...


1) gprof marche mal. Entre deux implémentations, l'une rendant le
programme globalement 1% plus rapide que l'autre, gprof trouve la
même part d'utilisation de 1,5%.


C'est vrai, mais la difference de perf pourrait aussi s'expliquer par
les effets de bord que la modif de cette routine peut avoir sur le
reste du code : par exemple changements d'alignement dans d'autres
fonctions deont le code pourtant identique a ete décalé à cause des
modifs...


Le code profilé n'étant pas le même que le code normalement exécuté, les
effets de bord sont omniprésents. Beaucoup trouvent que gprof marche
mal, et utilise des alternatives home-made, p.ex :
http://www.squid-cache.org/mail-archive/squid-dev/200009/0101.html


2) j'en suis aux micro-optimisations depuis un moment. Pourcent par
pourcent, j'ai gagné un facteur 5 en vitesse depuis 5 ans.


A conditions constantes je suppose?


Bien sûr.


parce que c'est le rapport moyen
de performances obtenu en suivant betement l'evolution du hard à
logiciel constant. Donc en changeant de machine (pour un prix modique
d'ailleurs) tu devrais avoir un facteur 25.


En gros oui.


3) le taux d'utilisation de cette fonction pourrait être supérieur
si elle était plus efficace.


Ca depend de ce que tu entends par là : - tu ferais plus usage de
cette fonction, dans d'autres parties de l'algorithme, ou plus
souvent au cours de l'analyse des coups.


Oui.


Alors oui, l'optimisation
paierait, mais vu qu'elle ne represente que 1.5% aujourd'hui, tu
pourrais deja mettre cette demarche en pratique.


C'est difficile. Dans le tri complet, il y a aussi le calcul des valeurs
à trier, qui est assez long. Le taux d'utilisation est à peu près
optimal aujourd'hui.

--
Richard



1 2 3 4