Inverter classificação

Um programador da Índia mostra claramente o Zig-Zag, Zig-Zig e Zig usado no algoritmo SplaySort:


Nesta temporada, estamos explorando uma variedade de pilhas e como elas podem ser usadas para classificação . No entanto, desta vez, daremos um passo para longe do tema principal. A estrutura de hoje - espalhar árvore - não é um monte. Mas precisamos que se prepare mentalmente para o estudo da próxima pilha - na próxima semana haverá uma palestra sobre a classificação por uma árvore cartesiana.
EDISON Software - desenvolvimento web
Este artigo foi preparado com o apoio da EDISON. Uma das direções de nossa atividade é a automação de medições e sistemas especializados , por meio dos quais realizamos projetos de alta tecnologia usando uma abordagem científica estrita. Nós amamos ciência da computação! ;-)






Classificação da árvore de pesquisa binária


Splay tree é uma árvore de pesquisa binária aprimorada. Primeiro, vamos lembrar como classificar usando a árvore binária "não melhorada" usual.

Como você bem sabe, em uma árvore binária, qualquer filho esquerdo é menor que o pai, qualquer filho direito não é menor (ou seja, maior ou igual) que o pai.


A classificação é geralmente simples:

  • Etapa 1. Com base na matriz, construa uma árvore de pesquisa binária. Colocamos o primeiro elemento da matriz na raiz, primeiro comparamos os elementos restantes com a raiz e, dependendo da comparação, movemos os ramos esquerdo ou direito para baixo (e, ao longo do caminho, comparamos o elemento da matriz com os nós existentes da árvore). No final, o próximo elemento chega ao final de uma ramificação e se torna um nó.
  • 2. . , ( , ) . .

Construir uma árvore é bastante tolerável em termos de complexidade algorítmica - em média, inserir um novo nó custa O (log n ) ; portanto, a complexidade de tempo do primeiro estágio é O ( n log n ) .

Mas no segundo estágio, nem tudo é tão róseo. Uma caminhada recursiva nas árvores pode facilmente se tornar uma longa jornada através de um labirinto extremamente sinuoso, e a complexidade do tempo geralmente diminui para O ( n 2 ) .

Splay tree


Para resolver esse problema, há apenas 35-37 anos, dois cientistas cientistas Robert Tarjan e Daniel Slitor desenvolveram essa estrutura em árvore. Eles sugeriram que, para qualquer operação (inserir, pesquisar, excluir) com qualquer nó da árvore, reequilibre imediatamente a árvore, tornando o nó a raiz de toda a estrutura.


Na foto da esquerda está Robert Tarjan (primeira linha, segunda à direita) na companhia do arquiteto de matrizes e inventor de Pascal. Na foto à direita está Daniel Slitor.
Clicar na imagem abrirá uma versão em formato completo.


Em russo, o nome sem sucesso "árvore em expansão" ficou preso, com menos frequência - "árvore oblíqua". Embora se fosse simplesmente traduzida literalmente, a "árvore expandida" (ou, melhor ainda, "transformada") soa bem e reflete com mais precisão a essência dessa estrutura algorítmica. Mas é isso.

Para enviar o nó à raiz, operações simples especiais são usadas, as chamadas rotações:

Zig Turn


Se o elemento que você deseja criar uma raiz estiver no segundo nível de aninhamento, tudo será extremamente simples.

Nós denotar este elemento como o X , e a sua mãe (o qual também é a raiz da árvore) - como o P .
A , B , C são subárvores. Quanto há nestes nodos subárvores não importa, apenas interessados nas raízes dessas sub-árvores que têm relações "pai-filho" com X e P . Se alguma das subárvores estiver ausente (ou seja, se X e P não tiverem descendentes), isso não afetará a ordem das ações.


Para tornar X a raiz da árvore, você precisa reverter a relação pai-filho entre X e P e também superar a subárvore B - era o descendente certo de X , tornou-se o descendente esquerdo de P ( você não precisa fazer nada com A e C ). E é tudo! Como resultado dessas manipulações simples, a estrutura, como era uma árvore de pesquisa binária, permaneceu assim - o princípio "o filho esquerdo é menor que o pai, o filho direito é maior ou igual que o pai" não será violado.

A imagem anterior X é um descendente esquerda de P . Se X era um descendente certo, você só precisa refletir a situação:


Se X não tiver o segundo nível de dificuldade, mas o terceiro, tudo será mais complicado, mas não muito.

Se X tem um pai P , que por sua vez tem um pai G , então temos apenas duas situações diferentes:
1) se X é um descendente de P no mesmo lado que P para G ;
2) se X e P são descendentes versáteis para seus pais.

Primeiro, considere o primeiro caso.

ZigZig Turn


Seja X o descendente esquerdo de P e P o descendente esquerdo de G (a opção quando X e P são simultaneamente descendentes direitos é resolvida da mesma forma).


Como se pode ver, é necessário alterar a relação entre o X , P e G , bem como compensar adequadamente sub-árvores B e o C . E nossa árvore de pesquisa binária não sofrerá com isso.

ZigZag Turn


Se X é o descendente direito e P é deixado (ou X é deixado e P é certo, não a essência), espero que você já entenda tudo nesta figura:


Se o X na árvore ainda estiver em um nível mais baixo de aninhamento, para aumentá-lo, você precisará aplicar o ZigZig ou o ZigZag (se necessário, faça isso várias vezes) para esse pai no ramo, que está no terceiro nível de aninhamento.

Classificação invertida: classificação Splay


Na verdade, aqui realizamos os mesmos pontos da classificação em árvore - primeiro construímos uma árvore de classificação binária e depois a contornamos. Mas toda vez que inserimos um novo nó na árvore - usando sulcos e zags, fazemos dela a raiz.

No primeiro estágio da vitória, isso não ocorre, ao inserir um nó (levando em consideração que você precisa ziguezague para transformá-lo em raiz) custa em média O (log n ) - e, portanto, a complexidade desse estágio é O ( n log n ) .

No entanto, como resultado, transformações surpreendentes ocorrem com a árvore - ela se endireita e é mantida em um estado tão endireitado o tempo todo. E isso acelera decisivamente o segundo estágio (passagem da árvore), uma vez que a escada final resultante é processada com complexidade O ( n ).

Assim, a complexidade total do algoritmo (pior, médio, melhor) no tempo é O ( n log n ) .

Na prática, essa classificação é mais lenta que MergeSort, QuickSort e outros HeapSort, mas demonstra claramente como você pode acelerar as operações com a árvore de pesquisa binária.

A moral dessa fábula é a seguinte: se você tiver que lidar com uma árvore de pesquisa binária - se possível, faça com que ela se equilibre. Como uma opção possível - trabalhe com ele como em uma árvore de espalhamento, ou seja, em qualquer ação com qualquer nó da árvore, torne esse nó a raiz. Obviamente, existem outras árvores de pesquisa alternativas de auto-equilíbrio (árvore vermelho-preta, árvore AVL etc.), que podem ser mais preferíveis.

Código C


para não parecer nervoso
/*
 *------------------------------------------------------------
 *
 *      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 da próxima série


E voltamos aos montões. A próxima coisinha será mais interessante do que a que examinamos hoje. Este híbrido é uma estrutura de dados semelhante a uma árvore que é uma pilha de heap e uma árvore de pesquisa binária.



Referências


Árvore de pesquisa binária , árvore

Splay Árvores Splay

Artigos da série:



Classificação adicionada ao AlgoLab. Portanto, se você atualizar o arquivo do Excel com macros, poderá alterar pessoalmente a árvore de pesquisa binária.

All Articles