Balikkan Sortir

Seorang programmer dari India jelas menunjukkan Zig-Zag, Zig-Zig dan Zig yang digunakan dalam algoritma SplaySort:


Musim ini kami sedang mengeksplorasi berbagai tumpukan dan bagaimana mereka dapat digunakan untuk menyortir . Namun, kali ini kita akan mengambil langkah menjauh dari tema utama. Struktur hari ini - pohon melebar - bukan banyak. Tetapi kita perlu mempersiapkan mental untuk mempelajari tumpukan berikutnya - minggu depan akan ada kuliah tentang penyortiran dengan pohon Cartesius.
Perangkat Lunak EDISON - pengembangan web
Artikel ini disiapkan dengan dukungan EDISON. Salah satu arahan kegiatan kami adalah otomatisasi pengukuran dan sistem pakar , yang dengannya kami melaksanakan proyek teknologi tinggi menggunakan pendekatan ilmiah yang ketat. Kami menyukai ilmu komputer! ;-)






Jenis pohon pencarian biner


Splay tree adalah pohon pencarian biner yang ditingkatkan. Pertama, mari kita ingat bagaimana cara menyortir menggunakan pohon biner "tidak diperbaiki" yang biasa.

Seperti yang Anda ketahui, di pohon biner, setiap anak kiri kurang dari induknya, anak kanan mana pun tidak kurang (mis. Lebih besar atau sama) dari induknya.


Penyortiran umumnya mudah:

  • Tahap 1. Berdasarkan pada array, buat pohon pencarian biner. Kami meletakkan elemen pertama array ke root, membandingkan elemen yang tersisa terlebih dahulu dengan root, kemudian, tergantung pada perbandingan, pindah ke cabang kiri atau kanan (dan sepanjang jalan kami membandingkan elemen array dengan node pohon yang ada). Pada akhirnya, elemen berikutnya mencapai ujung cabang dan menjadi simpul itu sendiri.
  • 2. . , ( , ) . .

Membangun pohon cukup dapat ditoleransi dalam hal kompleksitas algoritmik - rata-rata, memasukkan simpul baru berharga O (log n ) , jadi kompleksitas waktu tahap pertama adalah O ( n log n ) .

Namun pada tahap kedua, tidak semuanya begitu cerah. Berjalan pohon rekursif dapat dengan mudah menjadi perjalanan panjang melalui labirin yang sangat berliku, dan kompleksitas waktu sering menurun ke O ( n 2 ) .

Splay tree


Untuk mengatasi masalah ini, hanya sekitar 35-37 tahun yang lalu, dua ilmuwan Ilmuwan Robert Tarjan dan Daniel Slitor mengembangkan struktur pohon ini. Mereka menyarankan bahwa untuk operasi apa pun (menyisipkan, mencari, menghapus) dengan simpul pohon apa pun, segera menyeimbangkan kembali pohon tersebut, menjadikan simpul tersebut akar dari seluruh struktur.


Di foto kiri adalah Robert Tarjan (baris pertama, kanan kedua) di perusahaan Arsitek Matrix dan penemu Pascal. Di foto yang tepat adalah Daniel Slitor.
Mengklik pada gambar akan membuka versi format penuh.


Di Rusia, nama yang tidak berhasil "pohon yang mengembang" macet, lebih jarang - "pohon miring". Meskipun jika hanya diterjemahkan secara harfiah, maka "pohon diperluas" (atau, bahkan lebih baik, "berbalik") terdengar bagus dan lebih akurat mencerminkan esensi dari struktur algoritmik ini. Tapi begitulah.

Untuk mendorong node ke root, operasi sederhana khusus digunakan, yang disebut belokan:

Zig Turn


Jika elemen yang ingin Anda buat root ada di level kedua dari sarang, maka semuanya sangat sederhana.

Kami menunjukkan elemen ini seperti X , dan induknya (yang juga merupakan akar pohon) - sebagai P .
A , B , C adalah subpohon. Berapa banyak yang ada di sub pohon node ini tidak peduli, hanya tertarik pada akar sub pohon ini yang memiliki hubungan "orangtua-anak" dengan X dan P . Jika salah satu subtree hilang (yaitu, jika X dan P tidak memiliki keturunan), maka ini tidak mempengaruhi urutan tindakan.


Untuk menjadikan X sebagai akar pohon, Anda perlu membalik hubungan orangtua-anak antara X dan P , dan juga melebihi subtree B - itu adalah turunan kanan X , menjadi turunan kiri dari P ( Anda tidak perlu melakukan apa pun dengan A dan C ). Dan itu semua! Sebagai hasil dari manipulasi sederhana ini, struktur, karena itu adalah pohon pencarian biner, tetap sama - prinsip "anak kiri kurang dari induknya, anak kanan lebih besar atau sama dengan induknya" tidak akan dilanggar di mana pun.

Sebelumnya gambar X adalah keturunan kiri P . Jika X adalah keturunan yang tepat, Anda hanya perlu mencerminkan situasi:


Jika X tidak memiliki tingkat kesulitan kedua, tetapi yang ketiga, maka semuanya lebih rumit, tetapi tidak terlalu banyak.

Jika X memiliki induk P , yang pada gilirannya memiliki induk G , maka kita hanya memiliki dua situasi yang berbeda:
1) jika X adalah turunan untuk P di sisi yang sama dengan P untuk G ;
2) jika X dan P adalah keturunan serbaguna untuk orang tua mereka.

Pertama, pertimbangkan kasus pertama.

Putar ZigZig


Biarkan X menjadi turunan kiri untuk P , dan P turunan kiri untuk G (opsi ketika X dan P secara bersamaan turunan kanan diselesaikan dengan cara yang sama).


Seperti yang Anda lihat, perlu untuk mengubah hubungan antara X , P dan G , serta benar lebih besar daripada sub pohon B dan C . Dan pohon pencarian biner kami tidak akan menderita karenanya.

Putar ZigZag


Jika X adalah keturunan yang benar, dan P adalah kiri (atau X adalah kiri, dan P benar, bukan esensi), maka saya harap Anda sudah memahami semuanya dari gambar ini:


Jika X di pohon masih di tingkat yang lebih rendah dari sarang, maka untuk menaikkannya, Anda perlu menerapkan ZigZig atau ZigZag (jika perlu, lakukan beberapa kali) ke induk di cabang, yang berada di tingkat ketiga sarang.

Balikkan Urut :: Urut rentang


Sebenarnya, di sini kita melakukan poin yang sama seperti dalam pemilahan pohon - pertama kita membangun pohon jenis biner, kemudian kita mengitarinya. Tetapi setiap kali kita memasukkan simpul baru ke pohon - menggunakan punggungan dan zag, kita menjadikannya root.

Pada tahap pertama kemenangan, ini tidak memberikan, menyisipkan satu simpul (dengan mempertimbangkan bahwa Anda harus zigzag untuk menjadikannya root) biayanya rata-rata O (log n ) - dan dengan demikian kompleksitas tahap ini adalah O ( n log n ) .

Namun, sebagai akibatnya, transformasi yang luar biasa terjadi dengan pohon - itu meluruskan dan disimpan dalam keadaan diluruskan sepanjang waktu. Dan ini dengan tegas mempercepat tahap kedua (pohon traversal), karena tangga akhir yang dihasilkan diproses dengan kompleksitas O ( n ).

Dengan demikian, kompleksitas total dari algoritma (terburuk, sedang, terbaik) dalam waktu adalah O ( n log n ) .

Dalam praktiknya, pengurutan ini lebih lambat dari MergeSort, QuickSort, dan HeapSort lainnya, tetapi jelas menunjukkan bagaimana Anda dapat mempercepat operasi dengan pohon pencarian biner.

Moral dari dongeng ini adalah ini: jika Anda harus berurusan dengan pohon pencarian biner - jika mungkin, buatlah itu bisa seimbang sendiri. Sebagai opsi yang memungkinkan - bekerja dengannya seperti dengan splay tree, mis. dalam aksi apa pun dengan simpul apa pun di pohon, jadikan simpul ini root. Tentu saja, ada pohon pencarian self-balancing alternatif lain (pohon merah-hitam, pohon AVL, dll.), Yang mungkin lebih disukai.

Kode C


tidak terlihat gugup
/*
 *------------------------------------------------------------
 *
 *      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 Seri Berikutnya


Dan kita kembali ke tumpukan. Hal kecil berikutnya akan lebih menarik daripada yang kita periksa hari ini. Hibrida ini adalah struktur data mirip pohon yang merupakan heap dan pohon pencarian biner.



Referensi


Pohon pencarian biner , pohon

Splay pohon Splay

Artikel Seri:



Sortir ditambahkan ke AlgoLab. Jadi, jika Anda memperbarui file Excel dengan makro, Anda secara pribadi dapat merusak pohon pencarian biner.

All Articles