Sortierung umkehren

Ein Programmierer aus Indien zeigt deutlich Zig-Zag, Zig-Zig und Zig, die im SplaySort-Algorithmus verwendet werden:


In dieser Saison untersuchen wir eine Vielzahl von Haufen und wie sie zum Sortieren verwendet werden können . Diesmal werden wir uns jedoch vom Hauptthema entfernen. Die heutige Struktur - Spreizbaum - ist kein Haufen. Aber wir brauchen es, um uns mental auf das Studium des nĂ€chsten Stapels vorzubereiten - nĂ€chste Woche wird es einen Vortrag ĂŒber das Sortieren nach einem kartesischen Baum geben.
EDISON Software - Webentwicklung
Dieser Artikel wurde mit UnterstĂŒtzung von EDISON erstellt. Eine der Richtungen unserer TĂ€tigkeit ist die Automatisierung von Messungen und Expertensystemen , aufgrund derer wir Hightech-Projekte nach einem strengen wissenschaftlichen Ansatz durchfĂŒhren. Wir lieben Informatik! ;-);






Sortierung des binÀren Suchbaums


Der Splay-Baum ist ein verbesserter binĂ€rer Suchbaum. Erinnern wir uns zunĂ€chst daran, wie man mit dem ĂŒblichen "nicht verbesserten" BinĂ€rbaum sortiert.

Wie Sie wissen, ist in einem BinĂ€rbaum jedes linke Kind kleiner als das Elternteil, jedes rechte Kind ist nicht kleiner (d. H. GrĂ¶ĂŸer oder gleich) als das Elternteil.


Das Sortieren ist im Allgemeinen einfach:

  • Stufe 1. Erstellen Sie basierend auf dem Array einen binĂ€ren Suchbaum. Wir platzieren das erste Element des Arrays im Stammverzeichnis, vergleichen zuerst die verbleibenden Elemente mit dem Stammverzeichnis und bewegen dann je nach Vergleich den linken oder rechten Zweig nach unten (und vergleichen dabei das Array-Element mit den vorhandenen Baumknoten). Am Ende erreicht das nĂ€chste Element das Ende eines Zweigs und wird selbst zum Knoten.
  • 2. . , ( , ) . .

Das Erstellen eines Baums ist im Hinblick auf die algorithmische KomplexitĂ€t durchaus tolerierbar. Das EinfĂŒgen eines neuen Knotens kostet im Durchschnitt O (log n ) , sodass die zeitliche KomplexitĂ€t der ersten Stufe O ( n log n ) betrĂ€gt .

Aber in der zweiten Phase ist nicht alles so rosig. Ein rekursiver Baumspaziergang kann leicht zu einer langen Reise durch ein extrem gewundenes Labyrinth werden, und die zeitliche KomplexitÀt verschlechtert sich hÀufig zu O ( n 2 ) .

Splay-Baum


Um dieses Problem zu lösen, haben erst vor 35 bis 37 Jahren zwei Wissenschaftler, Robert Tarjan und Daniel Slitor, diese Baumstruktur entwickelt. Sie schlugen vor, bei allen Operationen (EinfĂŒgen, Suchen, Löschen) mit einem beliebigen Baumknoten den Baum sofort neu auszugleichen und den Knoten zur Wurzel der gesamten Struktur zu machen.


Auf dem linken Foto ist Robert Tarjan (erste Reihe, zweite rechts) in Begleitung des Matrix-Architekten und Erfinders von Pascal zu sehen. Auf dem rechten Foto ist Daniel Slitor.
Durch Klicken auf das Bild wird eine Vollformatversion geöffnet.


Auf Russisch blieb der erfolglose Name "expandierender Baum" seltener hĂ€ngen - "schrĂ€ger Baum". Wenn es einfach wörtlich ĂŒbersetzt wĂ€re, dann klingt der „erweiterte Baum“ (oder noch besser „gedreht“) gut und spiegelt die Essenz dieser algorithmischen Struktur genauer wider. Das ist aber.

Um den Knoten zur Wurzel zu schieben, werden spezielle einfache Operationen verwendet, die sogenannten Turns:

Zick drehen


Wenn sich das Element, fĂŒr das Sie eine Wurzel erstellen möchten, auf der zweiten Ebene der Verschachtelung befindet, ist alles Ă€ußerst einfach.

Wir bezeichnen dieses Element wie der X und dessen parent (die auch die Wurzel des Baumes ist) - als P .
A , B , C sind TeilbÀume. Wie viel in diesen TeilbÀumen enthalten ist, spielt keine Rolle, sondern interessiert nur die Wurzeln dieser TeilbÀume, die Beziehungen "Eltern-Kind" zu X und P haben . Wenn einer der TeilbÀume fehlt (dh wenn X und P keine Nachkommen haben), hat dies keinen Einfluss auf die Reihenfolge der Aktionen.


Um X zur Wurzel des Baums zu machen, mĂŒssen Sie die Eltern-Kind-Beziehung zwischen X und P umkehren und auch den B- Teilbaum ĂŒberwiegen - es war der rechte Nachkomme von X , wurde der linke Nachkomme von P ( Sie mĂŒssen mit A und C nichts tun). Und das ist alles! Infolge dieser einfachen Manipulationen blieb die Struktur, da es sich um einen binĂ€ren Suchbaum handelte, so - das Prinzip „Das linke Kind ist kleiner als das Elternteil, das rechte Kind ist grĂ¶ĂŸer oder gleich dem Elternteil“ wird nicht verletzt.

Das vorherige Bild X ist eine linke Nachkomme von P . Wenn X. war ein richtiger Nachkomme, mĂŒssen Sie nur die Situation widerspiegeln:


Wenn X nicht den zweiten, sondern den dritten Schwierigkeitsgrad hat, ist alles komplizierter, aber nicht viel.

Wenn X ein Elternteil P hat , das wiederum ein Elternteil G hat , dann haben wir nur zwei verschiedene Situationen:
1) wenn X ein Nachkomme fĂŒr P auf derselben Seite wie P fĂŒr G ist ;
2) wenn X und P sind vielseitig Nachkommen fĂŒr ihre Eltern.

Betrachten Sie zunÀchst den ersten Fall.

Zickzack drehen


Sei X der linke Nachkomme fĂŒr P und P der linke Nachkomme fĂŒr G (die Option, wenn X und P gleichzeitig rechte Nachkommen sind, wird auf Ă€hnliche Weise gelöst).


Wie Sie sehen können, ist es notwendig, die Beziehung zwischen X , P und G zu Ă€ndern und die TeilbĂ€ume B und C richtig zu ĂŒberwiegen . Und unser binĂ€rer Suchbaum wird darunter nicht leiden.

Zickzack drehen


Wenn X der rechte Nachkomme ist und P links ist (oder X links ist und P rechts ist, nicht die Essenz), dann hoffe ich, dass Sie bereits alles von diesem Bild verstehen:


Wenn sich X im Baum noch auf einer niedrigeren Verschachtelungsebene befindet, mĂŒssen Sie zum Anheben ZigZig oder ZigZag (falls erforderlich, mehrmals ausfĂŒhren) auf das ĂŒbergeordnete Element im Zweig anwenden, das sich auf der dritten Verschachtelungsebene befindet.

Invert Sort :: Splay sort


Eigentlich fĂŒhren wir hier die gleichen Punkte aus wie bei der Baumsortierung - zuerst erstellen wir einen binĂ€ren Sortierbaum, dann gehen wir darum herum. Aber jedes Mal, wenn wir einen neuen Knoten in den Baum einfĂŒgen - mit Graten und Zacken - machen wir ihn zur Wurzel.

In der ersten Phase des Gewinnens bedeutet dies nicht, dass das EinfĂŒgen eines Knotens (unter BerĂŒcksichtigung der Tatsache, dass Sie im Zickzack arbeiten mĂŒssen, um ihn zu einer Wurzel zu machen) im Durchschnitt O (log n ) kostet - und daher ist die KomplexitĂ€t dieser Phase O ( n log n ) .

Infolgedessen finden jedoch erstaunliche Transformationen mit dem Baum statt - er richtet sich auf und wird die ganze Zeit in einem so geraden Zustand gehalten. Und dies beschleunigt die zweite Stufe (Baumdurchquerung) entscheidend, da die resultierende endgĂŒltige Leiter mit O ( n ) -KomplexitĂ€t verarbeitet wird .

Somit ist die GesamtkomplexitÀt des Algorithmus (schlechtester, mittlerer, bester) in der Zeit O ( n log n ) .

In der Praxis ist diese Sortierung langsamer als MergeSort, QuickSort und andere HeapSort, zeigt jedoch deutlich, wie Sie VorgÀnge mit dem binÀren Suchbaum beschleunigen können.

Die Moral dieser Fabel lautet: Wenn Sie sich mit einem binĂ€ren Suchbaum befassen mĂŒssen - wenn möglich, machen Sie ihn selbstausgleichend. Als mögliche Option - arbeiten Sie damit wie mit einem Spreizbaum, d. H. Machen Sie diesen Knoten in einer Aktion mit einem beliebigen Knoten im Baum zur Wurzel. NatĂŒrlich gibt es auch andere alternative selbstausgleichende SuchbĂ€ume (rot-schwarzer Baum, AVL-Baum usw.), die möglicherweise vorzuziehen sind.

C-Code


nicht nervös aussehen
/*
 *------------------------------------------------------------
 *
 *      File..........: $RCSfile: splaysort.c,v $
 *      Revision......: $Revision: 1.2 $
 *      Author........: Gary Eddy (gary@cs.mu.OZ.AU)
 *			Alistair Moffat (alistair@cs.mu.OZ.AU)
 *      Date..........: $Date: 1995/09/07 06:19:17 $
 *
 *	Description:
 *		Sorts by repeated insertion into a splay tree.
 *		Insertions are done top down
 *
 *------------------------------------------------------------
 */

#include	<stdio.h>
#include	<stdlib.h>
#include	<malloc.h>
#include	<alloca.h>

char	*sort_name = "Splaysort";
char	*sort_version = "$Revision: 1.2 $";

/*
** Define DATAPTR for the 12 byte per record version of the program.
** Otherwise only 8 extra bytes per record are used and data
** references are done by indexing the data array.
** Different compiler/architecture combinations can cause wild
** variation in the ratio of speeds between these variations.
*/

#define DATAPTR

#ifdef DATAPTR
#define DATA(p) ((p)->data)
#else
#define DATA(p) (A+size*(p-T))
#endif

/*
** With the fast copy option enabled a more intelligent copy is used
** depending on the size of the items being copied.
** This approach adopted from the nqsort program of Bentley and McIlroy,
** see Software Practice and Experience, v23, n11, November 1993, 1249-65.
*/

#define FASTCOPY

#ifdef FASTCOPY
#define COPYCODE(TYPE, parmi, parmj, n) { \
        long i = (n) / sizeof (TYPE); \
        register TYPE *pi = (TYPE *) (parmi); \
        register TYPE *pj = (TYPE *) (parmj); \
        do { \
                *pi++ = *pj++;                    \
        } while (--i > 0);      \
}

void
copyfunc(char *d, char *s, int size, int copytype)
{
        if(copytype <= 1)
                COPYCODE(long, d, s, size)
        else
                COPYCODE(char, d, s, size)
	return;
}

#define COPY(d,s,size) \
        if (copytype == 0) { \
                *(long *)(d) = *(long *)(s); \
        } else \
                copyfunc(d, s, size, copytype)
#else
#define COPY(d,s,size)	memcpy(d,s,size)
#endif

typedef struct  node_rec node;

struct	node_rec {
	node	*left, *rght;
#ifdef DATAPTR
	char	*data;
#endif
};

/*
 *	sort():
 *		The entire sort code 
 *
 *	Accesses outside variables:	none
 *
 *	Return value...:		none
 */

void
sort(void *A, size_t n, size_t size, int (*cmp)(const void *, const void *))
{
	register node *next, *curr, *prnt;
	char 	*item;
	node	*l, *r, *ch;
	node	root, *T, *new, *end, *move;
#ifndef DATAPTR
	char	*p;
#endif

/*
** Determine which copying method will be used.
*/
#ifdef FASTCOPY
	int	copytype=((char *)A - (char *)0) % sizeof(long) ||
		size % sizeof(long) ? 2 : size == sizeof(long) ? 0 : 1;
#endif


	if(n < 2)
		return;
	if((T = new = (node *) malloc(sizeof(*T)*n)) == NULL) {
		fprintf(stderr, "Couldn't allocate space for structure\n");
	}
	/* 
	** Do the first insertion manually to avoid the empty tree
	** case in the main loop.
	*/
	curr = new++;
	item = A;
	curr->left = curr->rght = NULL;
#ifdef DATAPTR
	curr->data = item;
#endif
	item += size;
	end = T+n;
	/*
	** For each item move down the tree dividing it into
	** two subtrees, one containing items less than the new
	** element and the other those which are greater.
	** The pointers l and r refer to the greatest element in the
	** smaller subtree and the smallest element in the large
	** subtree respectively. During the splitting of the tree
	** l and r retain children that may be incorrect in the final
	** tree but these false links are cut at the end of the
	** insertion.
	*/

	for( ; new<end; ) {
		l = r = &root;
		while(curr != NULL) {
			if(cmp(item, DATA(curr)) < 0) {
				/* Left root case */
				if((ch = curr->left) == NULL) {
					r = r->left = curr;
					break;
				}
				/* Left zig-zig */
				else if(cmp(item, DATA(ch)) < 0) {
					curr->left = ch->rght;
					ch->rght = curr;
					r = r->left = ch;
					curr = ch->left;
				}
				/* Left zig-zag */
				else {
					r = r->left = curr;
					l = l->rght = ch;
					curr = ch->rght;
				}
			}
			else {
				/* Right root case */
				if((ch = curr->rght) == NULL) {
					l = l->rght = curr;
					break;
				}
				/* Right zig-zag */
				else if (cmp(item, DATA(ch)) < 0) {
					l = l->rght = curr;
					r = r->left = ch;
					curr = ch->left;
				}
				/* Right zig-zig */
				else {
					curr->rght = ch->left;
					ch->left = curr;
					l = l->rght = ch;
					curr = ch->rght;
				}
			}
		}
		/* Cut false links */
		r->left = l->rght = NULL;
		new->rght = root.left;
		new->left = root.rght;
#ifdef DATAPTR
		new->data = item;
#endif
		curr = new++;
		item += size;
	}
	/* Now copy all of the data back into the input array.
	** Uses an iterative destructive inorder traversal.
	** Last item inserted is the current root.
	*/
	prnt = NULL;
	move = T;
	while (1) {
		if ((next = curr->left) != NULL) {
			/* left subtree exists */
			curr->left = prnt;
			prnt = curr;
			curr = next;
		}
		else {
			next = curr->rght;
			curr->rght = move++;
			if (next != NULL) {
				/* and arrange for a visit */
				if((curr = next->left) != NULL) {
					next->left = prnt;
					prnt = next;
					continue;
				}
				else {
					curr = next;
					continue;
				}
			}
			/* no right subtree either, turn around*/
			if (prnt != NULL) {
				curr = prnt;
				prnt = prnt->left;
				curr->left = NULL;
				continue;
			}
			/* nothing left to be done */
			break;
		}
	}
#ifndef DATAPTR
	/*
	** Change the goes-to array in rght to a comes_from in left.
	** Note the kludge on pointers, where left points into the 
	** character array containing the elements.
	*/
	for(next = T, p = A; next < end; p += size, next++)
		next->rght->left = (node *)p;
	/* and use the `comes from' array to unscramble the permutation */
	item = (char *)malloc(size);
        for (next=T; next<end; next++) {
                char *datacurr, *dataleftcurr, *final;
                if (next->left == NULL)
                        continue;
                curr = next;
                final = datacurr = DATA(curr);
                COPY(item, datacurr, size);
                while ((char *)(curr->left) != final) {
                        dataleftcurr = (char *)(curr->left);
                        COPY(datacurr, dataleftcurr, size);
                        prnt = curr;
                        curr = T + (((char *)(curr->left)-A)/size);
                        prnt->left = NULL;
                        datacurr = dataleftcurr;
                }
                COPY(datacurr, item, size);
                curr->left = NULL;
        }
#else
	/* Change the goes-to array in rght to a comes_from in left */
	for(next = T; next < end; next++)
		next->rght->left = next;
	/* and use the `comes from' array to unscramble the permutation */
	/*
	** This 12 byte version uses the presence of the data pointer
	** by making it the flag for already placed items. This means
	** that left pointers can point to nodes, eliminating the
	** calculation to find the next node.
	*/
	item = (char *)malloc(size);
	for (next=T; next<end; next++) {
		char *datacurr, *dataleftcurr, *final;
		/* second condition guarantees at least one iteration
		** of while loop.
		*/
		if (DATA(next) == (char *) NULL || next->left == next)
			continue;
		final = datacurr = DATA(next);
		curr = next->left;
		COPY(item, datacurr, size);
		while ((dataleftcurr = DATA(curr)) != final) {
			COPY(datacurr, dataleftcurr, size);
			prnt = curr;
			curr = curr->left;
			DATA(prnt) = (char *) NULL;
			datacurr = dataleftcurr;
		}
		COPY(datacurr, item, size);
		DATA(curr) = (char *) NULL;
	}
#endif
	free(item);
	free(T);
} /* sort() */

Trailer der nÀchsten Serie


Und wir kehren zu den Haufen zurĂŒck. Das nĂ€chste kleine Ding wird interessanter sein als das, das wir heute untersucht haben. Dieser Hybrid ist eine baumartige Datenstruktur, die sowohl ein Heap- als auch ein binĂ€rer Suchbaum ist.



Verweise


BinÀrer Suchbaum , Spreizbaum

SpreizbÀume

Serienartikel:



Sortierung zu AlgoLab hinzugefĂŒgt. Wenn Sie also die Excel-Datei mit Makros aktualisieren, können Sie den binĂ€ren Suchbaum persönlich stören.

All Articles