17.11.2013 Aufrufe

Ein Doppel-Axel in C (1) -- qsort, bsearch, lsearch

Ein Doppel-Axel in C (1) -- qsort, bsearch, lsearch

Ein Doppel-Axel in C (1) -- qsort, bsearch, lsearch

MEHR ANZEIGEN
WENIGER ANZEIGEN

Erfolgreiche ePaper selbst erstellen

Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.

<strong>E<strong>in</strong></strong> <strong>Doppel</strong>-<strong>Axel</strong> <strong>in</strong> C (1) — <strong>qsort</strong>,<br />

<strong>bsearch</strong>, <strong>lsearch</strong><br />

<strong>Axel</strong>-Tobias Schre<strong>in</strong>er, Universität Ulm<br />

Kernighan und Ritchie’s fünftes Kapitel 1 ist für jeden C Programmierer natürlich<br />

Pflicht. Die Kür besteht bei System V offensichtlich dar<strong>in</strong>, Suchfunktionen nicht<br />

mehr selbst zu programmieren, sondern die verschiedenen Lösungen zu diesem<br />

Thema aus der Standard-Bücherei korrekt e<strong>in</strong>setzen zu können. In dieser und der<br />

nächsten Ausgabe der Sprechstunde möchte ich Beispiele für die Verwendung der<br />

Funktionen qsor t(), <strong>bsearch</strong>(), hsearch(), <strong>lsearch</strong>() und tsearch() zeigen und e<strong>in</strong><br />

bißchen ihre Stärken und Schwächen diskutieren.<br />

Das Problem wird durch die System V Interface Def<strong>in</strong>ition nicht unbed<strong>in</strong>gt<br />

vere<strong>in</strong>facht: zu den Suchfunktionen gibt es Beispiele, aber die s<strong>in</strong>d nicht immer<br />

korrekt programmiert. Bei ähnlicher Funktionalität ist außerdem die Benutzung der<br />

verschiedenen Funktionen ziemlich verschieden, und die Beschreibung gibt ke<strong>in</strong>en<br />

H<strong>in</strong>weis, welche Lösung und Problemstellung am besten harmonieren. Die<br />

eigentliche Schwierigkeit liegt aber dar<strong>in</strong>, daß die Funktionen zur Manipulation<br />

nahezu beliebiger Information e<strong>in</strong>gerichtet s<strong>in</strong>d, und daß dies natürlich mit Hilfe e<strong>in</strong>er<br />

recht ‘‘flexiblen’’ Interpretation von Zeigern erreicht wird.<br />

1 ...über Zeiger und Vektoren.


Sor tieren — ‘‘qsor t()’’<br />

qsor t() gab es ‘‘schon immer’’: mit dieser Funktion kann man die Elemente e<strong>in</strong>es<br />

beliebigen Vektors nach beliebigen Kriterien sortieren. Zu beachten ist, daß die<br />

Elemente selbst vertauscht werden (je nach Größe ist das aufwendig, denn es<br />

f<strong>in</strong>den nicht nur die m<strong>in</strong>imal notwendige Anzahl Tauschoperationen statt), und daß<br />

die Sortierung nicht stabil ist, das heißt, daß die ursprüngliche Reihenfolge von zwei<br />

gleichen Elementen nicht notwendigerweise erhalten bleibt — auch gleiche<br />

Elemente werden möglicherweise vertauscht. qsor t() hat folgende Deklaration:<br />

void <strong>qsort</strong>(base, nel, width, compar)<br />

char * base; /* Anfang des Vektors */<br />

unsigned nel, width; /* Anzahl Elemente, Groesse */<br />

<strong>in</strong>t (* compar)(); /* Vergleichsfunktion */<br />

Oft ist base der Name e<strong>in</strong>es Vektors — praktisch nie mit Elementen vom Typ char<br />

— und width ist die Elementgröße. <strong>E<strong>in</strong></strong>e e<strong>in</strong>fachere Schnittstelle, die auch die<br />

(unnötigen) Umwandlungen erzw<strong>in</strong>gt, könnte man zum Beispiel so def<strong>in</strong>ieren:<br />

void <strong>qsort</strong>();<br />

#def<strong>in</strong>e Qsort(base, nel, compar) \<br />

(<strong>qsort</strong>((char *) (base), /* Anfang der Tabelle */ \<br />

(unsigned) (nel), /* Anzahl Elemente */ \<br />

(unsigned) sizeof *(base), /* Elementgroesse */ \<br />

(<strong>in</strong>t (*)()) (compar))) /* Vergleichsfunktion */<br />

Bei e<strong>in</strong>em Vektor könnte man zwar die Anzahl der Elemente vom C Compiler<br />

berechnen lassen und so auf den Parameter nel auch noch verzichten, aber es wird<br />

sich bei unseren Beispielen zeigen, daß wir oft nur teilweise gefüllte oder mit Hilfe<br />

von Zeigern dynamisch angelegte Vektoren sortieren, bei denen der Compiler die


tatsächliche Anzahl der Elemente nicht feststellen kann.<br />

Im Makro Qsor t() wird die Elementgröße vom Compiler mit sizeof aus dem Typ<br />

der Anfangsadresse bestimmt. Die expliziten Umwandlungen provozieren zum<br />

Beispiel e<strong>in</strong>e Fehlermeldung, wenn weder e<strong>in</strong> Vektorname noch e<strong>in</strong> Zeiger als erstes<br />

Argument übergeben wird, oder wenn ke<strong>in</strong> Funktionsname oder Zeiger als drittes<br />

Argument steht. Sollte die Anzahl der Elemente versehentlich als long-Wert<br />

angegeben se<strong>in</strong>, wird sie stillschweigend umgewandelt. (Ähnliche Effekte werden<br />

<strong>in</strong> Zukunft die Funktions-Prototypen von ANSI-Standard-C erzielen.)<br />

Betrachten wir sicherheitshalber e<strong>in</strong> e<strong>in</strong>faches Beispiel auch für qsor t(): e<strong>in</strong>e<br />

kle<strong>in</strong>e Tabelle aus Namen und Zahlen soll je nach Wunsch sortiert werden. Hier ist<br />

das Hauptprogramm:<br />

#<strong>in</strong>clude <br />

#<strong>in</strong>clude "ma<strong>in</strong>.h" /* unix/mail 3/84 */<br />

#def<strong>in</strong>e DIM(x) (sizeof (x) / sizeof *(x)) /* Elemente <strong>in</strong> Vektor */<br />

typedef struct { /* Beispiel fuer Objekt */<br />

char str<strong>in</strong>g[30]; /* Name */<br />

<strong>in</strong>t number; /* Zahl */<br />

<strong>in</strong>t <strong>in</strong>fo; /* e<strong>in</strong>deutige, andere Information */<br />

} Object;<br />

/*** hierher gehoeren die Vergleichsfunktionen —— siehe unten ***/<br />

MAIN<br />

{ Object table[100]; register <strong>in</strong>t t = 0, i;<br />

while (scanf("%s %d", table[t].str<strong>in</strong>g, & table[t].number) == 2)<br />

{ table[t].<strong>in</strong>fo = t;<br />

if (++ t >= DIM(table))<br />

break;<br />

}


Mit scanf() lesen wir e<strong>in</strong>fach abwechselnd e<strong>in</strong>en Namen und e<strong>in</strong>e Zahl jeweils <strong>in</strong> e<strong>in</strong><br />

Tabellenelement e<strong>in</strong>. Läuft die Tabelle über, oder f<strong>in</strong>den wir ke<strong>in</strong> geeignetes Paar<br />

mehr, wird der Lesevorgang beendet. In der .<strong>in</strong>fo-Komponente speichern wir noch<br />

die <strong>E<strong>in</strong></strong>gabeposition des Tabellenelements, damit wir anschließend gewisse Effekte<br />

beim Sortieren demonstrieren können.<br />

Worte wie MAIN wurden <strong>in</strong> der ersten Sprechstunde <strong>in</strong> unix/mail 3/1984 def<strong>in</strong>iert.<br />

Unser Hauptprogramm soll mit der Option -n die .number-Komponenten, mit der<br />

Option -s die .str<strong>in</strong>g-Komponenten und mit der Option -= beide Komponenten<br />

sortieren:<br />

}<br />

OPT<br />

ARG ’n’: Qsort(table, t, cmpn); /* —n .number sortieren */<br />

ARG ’s’: Qsort(table, t, cmps); /* —s .str<strong>in</strong>g sortieren */<br />

ARG ’=’: Qsort(table, t, cmp); /* —= komb<strong>in</strong>iert sortieren */<br />

OTHER fputs("q [—ns=] < testdata\n", stderr), exit(1);<br />

ENDOPT<br />

for (i = 0; i < t; ++ i)<br />

pr<strong>in</strong>tf("%d %s %d\n",<br />

table[i].<strong>in</strong>fo, table[i].str<strong>in</strong>g, table[i].number);<br />

table[] ist unsere Tabelle, e<strong>in</strong> Vektor von Object-Strukturen, und <strong>in</strong> t haben wir die<br />

e<strong>in</strong>gelesenen Elemente gezählt — dieser Wert kann Null se<strong>in</strong>. Die verschiedenen<br />

Aufrufe von qsor t() unterscheiden sich nur <strong>in</strong> den Vergleichsfunktionen: cmpn() und<br />

cmps() sortieren <strong>in</strong> bezug auf e<strong>in</strong>e Komponente, cmp() <strong>in</strong> bezug auf zwei. Aus der<br />

Beschreibung von qsor t() weiß man, daß die Vergleichsfunktion jeweils mit zwei<br />

Zeigern auf Tabellenelemente aufgerufen wird und sich dann wie strcmp() verhalten<br />

muß. Man kann es sich auch so merken: jeder der beiden Parameter der<br />

Vergleichsfunktion hat den Typ, den auch das erste Argument — der Zeiger auf den


Tabellenanfang — von qsor t() hat. Hier s<strong>in</strong>d die Vergleichsfunktionen für unser<br />

Beispiel:<br />

<strong>in</strong>t cmps(a, b) /* .str<strong>in</strong>g vergleichen */<br />

register Object * a, * b;<br />

{<br />

return strcmp(a—>str<strong>in</strong>g, b—>str<strong>in</strong>g);<br />

}<br />

<strong>in</strong>t cmpn(a, b) /* .number vergleichen */<br />

register Object * a, * b;<br />

{<br />

return a—>number < b—>number ? —1 : a—>number > b—>number;<br />

}<br />

<strong>in</strong>t cmp(a, b) /* komb<strong>in</strong>ierter Vergleich */<br />

register Object * a, * b;<br />

{ register <strong>in</strong>t cond;<br />

return (cond = cmps(a, b)) ? cond : cmpn(a, b);<br />

}<br />

Str<strong>in</strong>gs vergleicht man natürlich mit strcmp(). Bei Zahlen muß man aufpassen: nicht<br />

e<strong>in</strong>mal für zwei <strong>in</strong>t-Werte sollte man die Differenz als Resultat des Vergleichs<br />

liefern! Auf e<strong>in</strong>er 16-Bit Masch<strong>in</strong>e hat 32767 - (-2) angeblich den Wert -32767, und<br />

der repräsentiert nicht gerade die Tatsache, daß 32767 größer als -2 ist. cmp()<br />

vergleicht zuerst die .str<strong>in</strong>g-Komponenten; s<strong>in</strong>d sie gleich, hängt das Resultat von<br />

den .number-Komponenten ab.<br />

Das Beispiel sollte vor allem demonstrieren, daß man mit qsor t() beliebige<br />

Vektoren sortieren kann. Außer den Sortierschlüsseln können die Vektorelemente<br />

auch andere Information enthalten — das Sortierkriterium ist völlig unter Kontrolle<br />

der Vergleichsfunktion. Betrachten wir noch e<strong>in</strong> paar Probeläufe:


$ q < data $ q -s < data $ q -n < data<br />

0 str<strong>in</strong>g 10 5 Str<strong>in</strong>g 20 0 str<strong>in</strong>g 10<br />

1 str<strong>in</strong>g 20 4 Str<strong>in</strong>g 10 2 str<strong>in</strong>g 10<br />

2 str<strong>in</strong>g 10 0 str<strong>in</strong>g 10 4 Str<strong>in</strong>g 10<br />

3 str<strong>in</strong>g 20 3 str<strong>in</strong>g 20 3 str<strong>in</strong>g 20<br />

4 Str<strong>in</strong>g 10 1 str<strong>in</strong>g 20 5 Str<strong>in</strong>g 20<br />

5 Str<strong>in</strong>g 20 2 str<strong>in</strong>g 10 1 str<strong>in</strong>g 20<br />

L<strong>in</strong>ks wird die <strong>E<strong>in</strong></strong>gabe unsortiert ausgegeben. Rechts wird numerisch, <strong>in</strong> der Mitte<br />

werden die Namen sortiert. Man sieht <strong>in</strong> beiden Beispielen, daß die<br />

<strong>E<strong>in</strong></strong>gabereihenfolge ‘‘gleicher’’ Elemente zerstört werden kann.<br />

$ q -= < data $ q -sn < data $ q -nn < data<br />

4 Str<strong>in</strong>g 10 0 str<strong>in</strong>g 10 0 str<strong>in</strong>g 10<br />

5 Str<strong>in</strong>g 20 4 Str<strong>in</strong>g 10 2 str<strong>in</strong>g 10<br />

2 str<strong>in</strong>g 10 2 str<strong>in</strong>g 10 4 Str<strong>in</strong>g 10<br />

0 str<strong>in</strong>g 10 3 str<strong>in</strong>g 20 3 str<strong>in</strong>g 20<br />

1 str<strong>in</strong>g 20 5 Str<strong>in</strong>g 20 1 str<strong>in</strong>g 20<br />

3 str<strong>in</strong>g 20 1 str<strong>in</strong>g 20 5 Str<strong>in</strong>g 20<br />

L<strong>in</strong>ks wird mit cmp() nach Namen und sekundär nach Zahlen sortiert. Das ist völlig<br />

anders, als wenn (wie <strong>in</strong> der Mitte) erst e<strong>in</strong>mal ganz nach Namen und dann das<br />

Resultat nach Zahlen sortiert wird. Im mittleren Beispiel wird eigentlich die mittlere<br />

Ausgabe von vorher nochmals neu nach der .number-Komponente sortiert. Man<br />

sieht auch hier sehr deutlich, daß qsor t() <strong>in</strong>stabil ist.<br />

Im rechten Beispiel wird zweimal numerisch sortiert, also die vorhergehende<br />

rechte Ausgabe nochmals neu nach dem gleichen Sortierkriterium. Man sieht, daß<br />

sogar bei e<strong>in</strong>er ursprünglich sortierten Tabelle noch zwei <strong>E<strong>in</strong></strong>träge vertauscht


werden!<br />

Tausch-Puzzle — sortieren mit Index-Vektor<br />

In dieser Ausgabe der unix/mail habe ich auch über e<strong>in</strong> SpreadSheet berichtet, das<br />

e<strong>in</strong>e Sortierfunktion besitzt. Im Spreadsheet kann das Vertauschen von Teilzeilen<br />

oder -spalten e<strong>in</strong>e sehr teure Operation se<strong>in</strong>. Me<strong>in</strong>e Lösung besteht dar<strong>in</strong>,<br />

stellvertretend für den SpreadSheet-Ausschnitt e<strong>in</strong>en Vektor p[] mit Indizes der<br />

betroffenen SpreadSheet-Zeilen oder -Spalten von qsor t() sortieren zu lassen:<br />

<strong>in</strong>t n; /* Anzahl zu sortierende Objekte */<br />

<strong>in</strong>t low; /* Index des ersten Objekts */<br />

<strong>in</strong>t (* cmp)(); /* zustaendige Vergleichsfunktion */<br />

short * p; /* dynamisch angelegter Vektor der Indizes */<br />

<strong>in</strong>t i, j, k;<br />

if (! (p = (short *) malloc(n * sizeof(short))))<br />

fatal("no room");<br />

for (j = 0; j < n; ++ j)<br />

p[j] = j+low;<br />

<strong>qsort</strong>(p, n, sizeof(short), cmp);<br />

Zuerst werden n, low und cmp aus dem Aufruf im SpreadSheet decodiert. Der<br />

Vektor p[] wird so <strong>in</strong>itialisiert, daß p[j] der Index (Zeilen- oder Spaltennummer) des<br />

Objekts im SpreadSheet ist, das sozusagen auf Platz j <strong>in</strong> der zu sortierenden Tabelle<br />

steht. Der durch cmp festgelegten Vergleichsfunktion werden Zeiger auf zwei<br />

solche Indexwerte angeboten, und die Funktion kann damit im SpreadSheet die<br />

Objekte f<strong>in</strong>den und vergleichen.<br />

Ist qsor t() fertig, wurden die Indexwerte <strong>in</strong> p[] so vertauscht, daß <strong>in</strong> p[j] jetzt der<br />

Index des Objekts im SpreadSheet steht, das eigentlich <strong>in</strong> Indexposition low+j im


SpreadSheet stehen sollte um das Sortierkriterium zu erfüllen. Gilt (wie das vor<br />

Aufruf von qsor t() ja der Fall war) p[j] == low+j, sitzt das Objekt schon richtig; gilt<br />

das nicht, müssen wir Objekte tauschen. Das Puzzle besteht dar<strong>in</strong>, wie man<br />

Ordnung mit möglichst wenig Tauschoperationen erreicht:<br />

for (i = 0; i < n; ++ i)<br />

if (p[i] != i+low)<br />

for (j = i; k = j, j = p[k]—low, p[k] = k+low, p[j] != j+low; )<br />

/* Objekte j+low, k+low tauschen */<br />

Zugegeben, Thomas Mandry und ich haben Tage gebraucht, um diese Lösung zu<br />

erf<strong>in</strong>den und zu formulieren, aber sie ist dafür doch auch sehr leicht zu verstehen —<br />

oder? 2<br />

B<strong>in</strong>är suchen — ‘‘<strong>bsearch</strong>()’’<br />

Muß man e<strong>in</strong> Element <strong>in</strong> e<strong>in</strong>em sortierten Vektor f<strong>in</strong>den, kann man <strong>bsearch</strong>()<br />

verwenden. Diese Funktion vergleicht e<strong>in</strong> Suchobjekt mit dem mittleren Element<br />

e<strong>in</strong>es Vektorbereichs, und setzt je nach Resultat die Suche <strong>in</strong> der l<strong>in</strong>ken oder rechten<br />

Hälfte fort. Mit höchstens zehn Vergleichen kann man so e<strong>in</strong>en Wert unter tausend<br />

f<strong>in</strong>den. <strong>bsearch</strong>() verwendet Argumente, die an qsor t() er<strong>in</strong>nern, und liefert als<br />

Resultat e<strong>in</strong>en Zeiger auf das gesuchte Objekt <strong>in</strong> der Tabelle oder e<strong>in</strong>en Nullzeiger:<br />

char * <strong>bsearch</strong>();<br />

#def<strong>in</strong>e Bsearch(key, base, nel, compar) \<br />

2 Fans sei soviel verraten: Die äußere Schleife behandelt jedes Element <strong>in</strong> p[]<br />

e<strong>in</strong>mal. Wenn etwas nicht stimmt, löst die <strong>in</strong>nere Schleife e<strong>in</strong>en der<br />

Permutationszyklen auf, aus denen p[] besteht.


((Object *) <strong>bsearch</strong>((char *) (key), /* gesuchtes Object */ \<br />

(char *) (base), /* Anfang der Tabelle */ \<br />

(unsigned) (nel), /* Anzahl Elemente */ \<br />

(unsigned) sizeof *(base), /* Elementgroesse */ \<br />

(<strong>in</strong>t (*)()) (compar))) /* Vergleichsfunktion */<br />

Als erstes Argument erwartet <strong>bsearch</strong>() e<strong>in</strong>en Zeiger auf das Suchobjekt; die<br />

restlichen vier Argumente s<strong>in</strong>d exakt die gleichen wie bei qsor t(). Der Makro<br />

Bsearch() vermeidet wieder die Angabe der Größe e<strong>in</strong>es Elements und sorgt mit<br />

expliziten Umwandlungen für e<strong>in</strong>e gewisse Typenprüfung. Als Resultat liefert<br />

<strong>bsearch</strong>() e<strong>in</strong>en Zeigerwert; er hat den gleichen Typ wie das erste Argument — der<br />

Zeiger auf das Suchobjekt — und das zweite Argument — der Zeiger auf den<br />

Tabellenanfang — und wie die Zeiger, die an die Vergleichsfunktion übergeben<br />

werden.<br />

In unserem früheren Beispiel können wir das Hauptprogramm so abändern, daß<br />

bei jeder Option noch e<strong>in</strong> Wert übergeben wird, der dann entsprechend gesucht<br />

wird:<br />

Object table[100]; <strong>in</strong>t t; /* Beispiel e<strong>in</strong>er Tabelle */<br />

static look(str, num, cmp)<br />

char * str; <strong>in</strong>t num; /* Suchargument */<br />

<strong>in</strong>t (* cmp)(); /* Vergleichsfunktion */<br />

{ Object key, * kp; /* Suchobjekt, Resultat */<br />

Qsort(table, t, cmp);<br />

strcpy(key.str<strong>in</strong>g, str), key.number = num;<br />

if (kp = Bsearch(& key, table, t, cmp))<br />

pr<strong>in</strong>tf("%d %s %d\n", kp—><strong>in</strong>fo, kp—>str<strong>in</strong>g, kp—>number);<br />

}<br />

MAIN<br />

{ char * str;


}<br />

/*** e<strong>in</strong>lesen, wie im <strong>qsort</strong>()—Beispiel ***/<br />

OPT<br />

ARG ’n’: PARM look("", atoi(*argv), cmpn); NEXTOPT<br />

ARG ’s’: PARM look(*argv, 0, cmps); NEXTOPT<br />

ARG ’=’: PARM str = *argv; NEXTOPT<br />

PARM look(str, atoi(*argv), cmp); NEXTOPT<br />

OTHER fputs("b [—n num] [—s str] [—= str num] < testdata\n",<br />

stderr), exit(1);<br />

ENDOPT<br />

Object und die drei Vergleichsfunktionen stammen aus dem ersten Abschnitt.<br />

Diesmal muß table[] global def<strong>in</strong>iert werden, damit die Tabelle auch für look()<br />

implizit zur Verfügung steht. look() erhält die zu suchende Information und die<br />

nötige Vergleichsfunktion. Zuerst wird die Tabelle mit qsor t() entsprechend sortiert,<br />

dann wird <strong>bsearch</strong>() verwendet.<br />

Unsere Tabellenelemente enthalten wieder mehr Information als nur das<br />

Suchargument. Man sieht aber, daß an <strong>bsearch</strong>() als Suchgegenstand nicht etwa<br />

nur der gesuchte Name oder die Zahl übergeben wird, sondern e<strong>in</strong> Zeiger auf e<strong>in</strong><br />

ganzes Tabellenelement. In dem Tabellenelement muß natürlich nur der Teil<br />

<strong>in</strong>itialisiert se<strong>in</strong>, den die Vergleichsfunktion tatsächlich betrachtet. Wir <strong>in</strong>itialisieren<br />

nur deshalb immer die .str<strong>in</strong>g- und die .number-Komponente, da wir look() für alle<br />

drei möglichen Suchen verwenden.<br />

$ b < data -n 10; $ b < data -n 20 $ b < data -n 10 -n 20<br />

4 Str<strong>in</strong>g 10 4 Str<strong>in</strong>g 10<br />

5 Str<strong>in</strong>g 20 1 str<strong>in</strong>g 20<br />

Bei diesen Beispielen werden die gleichen Daten wie für die Beispiele zu qsor t()<br />

verwendet. L<strong>in</strong>ks wird jeweils e<strong>in</strong>mal numerisch sortiert und dann gesucht.


Vergleicht man mit der entsprechenden früheren Ausgabe, sieht man, daß <strong>bsearch</strong>()<br />

von mehreren ‘‘gleichen’’ Tabellenelementen eben das liefert, das durch die<br />

fortgesetzte Halbierung zuerst erreicht wird; das ist nicht unbed<strong>in</strong>gt das erste oder<br />

letzte e<strong>in</strong>er Folge gleicher Elemente. Rechts wird für 20 positionell das gleiche<br />

Tabellenelement wie im l<strong>in</strong>ken Beispiel gefunden; look() hat aber dann zweimal<br />

numerisch sortiert und, wie bei qsor t() schon demonstriert, hat damit 1 an die Stelle<br />

von 5 gebracht.<br />

Die eigentliche Falle besteht bei <strong>bsearch</strong>() wohl dar<strong>in</strong>, daß man die falschen<br />

Zeigertypen <strong>in</strong> der Vergleichsfunktion erwartet. Hier ist noch e<strong>in</strong> Beispiel, bei dem<br />

Str<strong>in</strong>gs, nämlich die Argumente des Kommandos, verwendet werden:<br />

typedef char * Object; /* Objekte s<strong>in</strong>d Str<strong>in</strong>gs */<br />

static <strong>in</strong>t cmp(a, b) Object * a, * b; /* Zeiger auf Objekte,... */<br />

{<br />

return strcmp(*a, *b); /* ...erst *a ist e<strong>in</strong> Str<strong>in</strong>g! */<br />

}<br />

ma<strong>in</strong>(argc, argv) Object argv[]; /* (Object[]) ist (char **) */<br />

{ char buf[100];<br />

Object key = buf;<br />

Qsort(argv, argc, cmp);<br />

while (gets(buf))<br />

if (! Bsearch(& key, argv, argc, cmp))<br />

puts("not found");<br />

}<br />

argv[] ist der Vektor, der sortiert wird, und <strong>in</strong> dem gesucht wird. Das erste<br />

Argument zu <strong>bsearch</strong>() muß den gleichen Typ besitzen wie das zweite Argument,<br />

argv, und dieser Typ wird auch an cmp() geliefert. buf[] muß wirklich e<strong>in</strong> Vektor<br />

se<strong>in</strong>, denn gets() h<strong>in</strong>terlegt dort die <strong>E<strong>in</strong></strong>gabezeichen. buf ist zwar e<strong>in</strong> Zeigerwert,


kann aber nicht erstes Argument für <strong>bsearch</strong>() se<strong>in</strong>, denn buf hat den Typ char *<br />

und argv ist char **. Fehlt e<strong>in</strong> Sternchen, kann man pr<strong>in</strong>zipiell mit & e<strong>in</strong>s erzeugen:<br />

& buf müßte nach diesem Rezept den richtigen Typ, e<strong>in</strong> Sternchen mehr, also char<br />

**, besitzen — als Vektorname ist buf aber e<strong>in</strong>e Adreßkonstante, und von der kann<br />

und darf man ke<strong>in</strong>e Adresse bilden. Die korrekte Lösung besteht, wie gezeigt, <strong>in</strong> der<br />

Verwendung von key, e<strong>in</strong>er Variablen, <strong>in</strong> der die konstante Adresse buf h<strong>in</strong>terlegt ist,<br />

und deren Adresse wieder an <strong>bsearch</strong>() übergeben werden kann.<br />

L<strong>in</strong>ear suchen — ‘‘<strong>lsearch</strong>()’’ und ‘‘lf<strong>in</strong>d()’’<br />

lf<strong>in</strong>d() hat fast die gleichen Argumente wie <strong>bsearch</strong>() und exakt den gleichen Effekt:<br />

<strong>E<strong>in</strong></strong> Objekt wird <strong>in</strong> e<strong>in</strong>em Vektor unter Kontrolle e<strong>in</strong>er Vergleichsfunktion gesucht. Ist<br />

das Objekt vorhanden, liefert lf<strong>in</strong>d() e<strong>in</strong>en Zeiger als Resultat, ist’s nicht da, gibt’s<br />

e<strong>in</strong>en Nullzeiger. Der kle<strong>in</strong>e Unterschied ist, daß <strong>bsearch</strong>() die Anzahl der<br />

Tabellenelemente als drittes Argument erhält, lf<strong>in</strong>d() aber e<strong>in</strong>en Zeiger auf diese<br />

Anzahl. Der große Unterschied ist, daß lf<strong>in</strong>d() l<strong>in</strong>ear sucht, also vom ersten<br />

Tabellenelement sequentiell bis zum letzten. Das kann extrem lang dauern, aber<br />

dafür braucht die Tabelle nicht sortiert zu se<strong>in</strong>.<br />

Außer lf<strong>in</strong>d() gibt’s <strong>in</strong> der Bücherei auch noch <strong>lsearch</strong>(), mit den gleichen<br />

Argumenten, der gleichen Suchtechnik, und fast dem gleichen Resultat:<br />

char * <strong>lsearch</strong>(), * lf<strong>in</strong>d();<br />

#def<strong>in</strong>e Lsearch(key, base, nelp, compar) \<br />

((Object *) <strong>lsearch</strong>((char *) (key), /* gesuchtes Object */ \<br />

(char *) (base), /* Anfang der Tabelle */ \<br />

(unsigned *) (nelp), /* Anzahl Elemente */ \<br />

(unsigned) sizeof *(base), /* Elementgroesse */ \<br />

(<strong>in</strong>t (*)()) (compar))) /* Vergleichsfunktion */


#def<strong>in</strong>e Lf<strong>in</strong>d(key, base, nelp, compar) \<br />

((Object *) lf<strong>in</strong>d((char *) (key), /* gesuchtes Object */ \<br />

(char *) (base), /* Anfang der Tabelle */ \<br />

(unsigned *) (nelp), /* Anzahl Elemente */ \<br />

(unsigned) sizeof *(base), /* Elementgroesse */ \<br />

(<strong>in</strong>t (*)()) (compar))) /* Vergleichsfunktion */<br />

Leider wurden die Funktionen nicht so arg logisch benannt: lf<strong>in</strong>d() sucht, muß aber<br />

nicht unbed<strong>in</strong>gt f<strong>in</strong>den, und liefert unter Umständen e<strong>in</strong>en Nullzeiger. <strong>lsearch</strong>()<br />

sucht, f<strong>in</strong>det bestimmt, und liefert nie e<strong>in</strong>en Nullzeiger: ist das gesuchte Objekt nicht<br />

<strong>in</strong> der Tabelle, trägt es <strong>lsearch</strong>() am Ende des Vektors e<strong>in</strong>, korrigiert die Anzahl der<br />

Elemente entsprechend (deshalb muß e<strong>in</strong> Zeiger auf diese Anzahl übergeben<br />

werden!), und liefert schließlich e<strong>in</strong>en Zeiger auf das gefundene — neu e<strong>in</strong>getragene<br />

— Objekt. Das Wortspiel zum ‘‘gefundenen Fressen’’ liegt nahe: <strong>lsearch</strong>() hat nicht<br />

genügend Information um das verfügbare Ende der Tabelle zu erkennen. Es bleibt<br />

dem Progammierer überlassen, <strong>lsearch</strong>() nur dann aufzurufen, wenn wenigstens<br />

noch e<strong>in</strong> Element <strong>in</strong> den angebotenen Vektor paßt.<br />

<strong>lsearch</strong>() eignet sich auch nicht zum <strong>E<strong>in</strong></strong>satz <strong>in</strong> kle<strong>in</strong>en Compilern: zur<br />

Implementierung e<strong>in</strong>er Blockstruktur für den Geltungsbereich von Namen fügt man<br />

neue Namen jeweils zu e<strong>in</strong>er l<strong>in</strong>earen Liste h<strong>in</strong>zu, die aber wie e<strong>in</strong> Stack rückwärts<br />

durchsucht werden muß, damit man neue (<strong>in</strong>nere) Namen zuerst f<strong>in</strong>det. <strong>lsearch</strong>()<br />

sucht vorwärts...


B<strong>in</strong>är e<strong>in</strong>fügen — ‘‘b<strong>in</strong>ary()’’<br />

<strong>lsearch</strong>() hat gegenüber <strong>bsearch</strong>() den Vorteil, daß e<strong>in</strong> nicht vorhandenes Element <strong>in</strong><br />

die Tabelle e<strong>in</strong>gefügt wird. Die l<strong>in</strong>eare Suche ist jedoch e<strong>in</strong> gravierender Nachteil:<br />

<strong>bsearch</strong>() merkt nach 10 Vergleichen bei 1000 Elementen, daß e<strong>in</strong> gesuchtes<br />

Element fehlt; <strong>lsearch</strong>() merkt das erst nach 1000 Vergleichen. Die folgende<br />

Funktion b<strong>in</strong>ary() komb<strong>in</strong>iert die Vorteile der beiden anderen Funktionen: sie sucht<br />

b<strong>in</strong>är und fügt bei Bedarf das gesuchte Element so e<strong>in</strong>, daß die Tabelle sortiert<br />

bleibt:<br />

char * b<strong>in</strong>ary(key, base, nelp, width, compar)<br />

char * key, * base; unsigned * nelp, width; <strong>in</strong>t (* compar)();<br />

{ unsigned nel = * nelp; <strong>in</strong>t c;<br />

char * lim = base + nel * width, * mid, * high;<br />

for (high = lim — width; base >= 1)<br />

{ mid = base + (nel >> 1) * width;<br />

if ((c = (* compar)(key, mid)) < 0)<br />

high = mid — width;<br />

else if (c > 0)<br />

base = mid + width, —— nel;<br />

else<br />

return mid;<br />

}<br />

for (high = lim, lim += width; high > base; )<br />

*——lim = *——high;<br />

for (c = width; c——; )<br />

base[c] = key[c];<br />

++ *nelp;<br />

return base;<br />

}


lim wird auf den Punkt unmittelbar nach den vorhandenen Elementen e<strong>in</strong>gestellt.<br />

Die erste for-Schleife realisiert die konventionelle b<strong>in</strong>äre Suche. Die e<strong>in</strong>zige<br />

Schwierigkeit liegt dar<strong>in</strong>, daß mid nicht nur auf den Mittelpunkt zwischen base und<br />

high — also dem ersten und letzten Element im aktuellen Vektorbereich — zeigen<br />

kann, sondern möglichst genau dort auf e<strong>in</strong> Element zeigen muß. nel ist die Anzahl<br />

der Elemente im Vektorbereich. Ist sie gerade, zeigt mid auf das Element, das am<br />

Mittelpunkt beg<strong>in</strong>nt, und nach dem Element bei mid folgen weniger Elemente als<br />

davor. Ist die Anzahl der Elemente ungerade, zeigt mid auf das Element, das das<br />

Byte am Mittelpunkt enthält, und davor und danach s<strong>in</strong>d gleich viele Elemente.<br />

Wechselt man <strong>in</strong> die rechte ‘‘Hälfte’’ über, muß nel sorgfältig korrigiert werden,<br />

sonst zeigt man am Schluß möglicherweise <strong>in</strong>s Leere!<br />

Geht die erste for-Schleife erfolglos zu Ende, zeigt base gerade auf den Punkt, wo<br />

das gesuchte Element se<strong>in</strong> sollte. Die zweite for-Schleife geht von rechts nach l<strong>in</strong>ks<br />

und schiebt den Rest des Vektors nach rechts, und die dritte for-Schleife kopiert die<br />

gesuchte Information an die richtige Stelle <strong>in</strong> der nach wie vor sortierten Tabelle.<br />

Messen — ‘‘bench()’’<br />

<strong>E<strong>in</strong></strong> typisches Problem zum Vergleichen von Suchfunktionen ist die<br />

Häufigkeitsanalyse von Worten <strong>in</strong> e<strong>in</strong>em Text. Als Treiber für die nachfolgenden<br />

Vergleiche verwenden wir folgendes Programm:<br />

<strong>in</strong>t <strong>in</strong>c; /* Parameter fuer Speicherverwaltung */<br />

MAIN<br />

{ char buf[100]; register char * cp;<br />

OPT<br />

ARG ’i’: PARM <strong>in</strong>c = atoi(*argv); NEXTOPT


}<br />

OTHER fputs("[—i <strong>in</strong>c]\n", stderr), exit(1);<br />

ENDOPT<br />

bench("");<br />

while (scanf("%s", buf) == 1)<br />

if (cp = word(buf))<br />

count(cp);<br />

bench("count");<br />

alpha();<br />

bench("alpha");<br />

frequency();<br />

bench("frequency");<br />

scanf() extrahiert Worte, also Zeichenfolgen, die nicht aus Zwischenraumzeichen<br />

bestehen, aus der Standard-<strong>E<strong>in</strong></strong>gabe. word() kann dazu dienen, zum Beispiel nur<br />

alphanumerische Worte oder nur genügend lange Worte zu selektieren. count()<br />

muß e<strong>in</strong> Wort mit Hilfe e<strong>in</strong>er der Suchfunktionen f<strong>in</strong>den und zählen; neue Worte<br />

müssen jeweils dynamisch gespeichert werden. alpha() soll zum Schluß alle Worte<br />

sortiert ausgeben, frequency() soll sie nach Häufigkeit sortiert ausgeben. bench()<br />

dient dazu, die Ausführungszeit der verschiedenen Phasen des Tests und den<br />

tatsächlichen dynamischen Speicherverbrauch zu messen:<br />

#<strong>in</strong>clude <br />

#<strong>in</strong>clude <br />

#<strong>in</strong>clude <br />

bench(s) register char * s; /* Zeitstempel ausgeben */<br />

{ static struct tms told; static long old; /* letzte Marke */<br />

struct tms tnew; long new, times(); /* neue Marke */<br />

static char * bold; char * bnew, * sbrk(); /* Speicher */<br />

new = times(& tnew); bnew = sbrk(0);


}<br />

if (old)<br />

fpr<strong>in</strong>tf(stderr,<br />

"%s: %d/%d/%d (user/sys/real <strong>in</strong> ticks), %u (heap bytes)\n",<br />

s, (<strong>in</strong>t) (tnew.tms_utime — told.tms_utime),<br />

(<strong>in</strong>t) (tnew.tms_stime — told.tms_stime),<br />

(<strong>in</strong>t) (new — old), (unsigned) bnew — bold);<br />

bold = bnew; old = times(& told);<br />

times() ist e<strong>in</strong> Systemaufruf, der als Resultat jeweils e<strong>in</strong>e Uhrzeit liefert. In struct<br />

tms werden außerdem die CPU-Zeiten h<strong>in</strong>terlegt, die der aufrufende Prozeß (mit<br />

etwa vorhandenen Abkömml<strong>in</strong>gen) bisher selbst und durch Systemaufrufe<br />

verbraucht hat. sbrk(0) zeigt die Adresse, die der Systemkern bei der nächsten<br />

3<br />

Anforderung von dynamischem Speicherplatz liefern würde. Verwendet man für<br />

count(), alpha() und frequency() leere Funktionen, liefert unser Programm etwa<br />

folgendes:<br />

$ f0 < spreadsheet.ms<br />

count: 87/6/135 (user/sys/real <strong>in</strong> ticks), 0 (heap bytes)<br />

alpha: 0/0/0 (user/sys/real <strong>in</strong> ticks), 0 (heap bytes)<br />

frequency: 0/0/0 (user/sys/real <strong>in</strong> ticks), 0 (heap bytes)<br />

spreadsheet.ms ist dabei die Quelle zu me<strong>in</strong>em SpreadSheet-Artikel <strong>in</strong> diesem Heft:<br />

word() selektiert 966 verschiedene Worte mit etwa 8 KB Zeichen; der Text enthält<br />

<strong>in</strong>sgesamt etwa 2750 ‘‘Worte’’ mit 18 KB Text.<br />

Die Bedeutung der clock ticks bleibt eigentlich undef<strong>in</strong>iert. Bei XENIX enthält die<br />

Environment-Variable HZ die Anzahl der clock ticks pro Sekunde; auf dem hier<br />

3 Das ist nicht unbed<strong>in</strong>gt das nächste Resultat von malloc(), denn diese Funktion<br />

verwaltet e<strong>in</strong>e freie Liste und fordert außerdem Speicher bei sbrk() nur <strong>in</strong> größeren<br />

Blöcken an.


verwendeten System s<strong>in</strong>d das 50. Der Systemkern mißt auch die CPU-Zeiten nicht<br />

vollkommen exakt; die gezeigten Zeiten sollten also nur als qualitative Aussage<br />

betrachtet werden.<br />

(Fortsetzung folgt)

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!