反转排序

来自印度的程序员清楚地显示了SplaySort算法中使用的Zig-Zag,Zig-Zig和Zig:


这个季节,我们正在探索各种堆以及如何将它们用于分类但是,这次我们将远离主题。如今的结构-八角树-并非一堆。但是我们需要它来为下一个堆的研究做好心理准备-下周将举行有关按笛卡尔树排序的讲座。
EDISON软件-网络开发
本文是在EDISON的支持下编写的。 我们活动的方向之一是测量和专家系统的自动化,因此,我们使用严格的科学方法执行高科技项目。 我们热爱计算机科学!;-)






二叉搜索树排序


Splay树是改进的二进制搜索树。首先,让我们记住如何使用通常的“未经改进”的二叉树进行排序。

众所周知,在二叉树中,任何左子项均小于父项,任何右子项均不小于(即大于或等于)父项。


排序通常很简单:

  • 阶段1.基于数组,构建一个二分搜索树。我们将数组的第一个元素放在根上,首先将其余元素与根进行比较,然后根据比较结果,向左或向右分支移动(并在此过程中将数组元素与现有树节点进行比较)。最后,下一个元素到达分支的末尾并成为节点本身。
  • 2. . , ( , ) . .

就算法复杂度而言,构建树是可以容忍的-平均而言,插入新节点的成本为O(log n,因此第一阶段的时间复杂度为O(n log n

但是在第二阶段,并不是所有事情都那么乐观。在极度蜿蜒的迷宫中,递归的树走很容易成为漫长的旅程,并且时间复杂度通常降低到O(n 2

八叉树


为了解决这个问题,大约在35-37年前,两位科学家Robert Robert和Daniel Slitor开发了这种树形结构。他们建议对于任何对树节点的操作(插入,搜索,删除),请立即重新平衡树,使该节点成为整个结构的根。


左图是Matrix Architects和Pascal的发明者的陪伴下的Robert Tarjan(第一行,第二右)。右图是Daniel Slitor。
单击图像将打开完整格式的版本。


在俄语中,失败的名称“ expanding tree”卡住了,很少出现-“倾斜的树”。尽管只翻译了字面意思,但“扩展树”(甚至更好的是“变成”)听起来不错,并且更准确地反映了这种算法结构的本质。但是那是。

为了将节点推到根,使用了特殊的简单操作,即所谓的转弯:

曲折转向


如果要作为根的元素位于第二层嵌套中,则一切都非常简单。

我们表示该元素等在X,和它的父(其也是树的根) -作为P
ABC是子树。这些子树节点中有多少并不重要,只关心与XP具有“父子”关系的这些子树的根。如果缺少任何子树(即,如果XP没有任何后代),则这不会影响操作顺序。


要使X成为根,您需要反转XP之间的父子关系,并且要超过子树B-它是X的右后代,成为P的左后代您无需对AC进行任何操作)。这就是全部!通过这些简单的操作,该结构(就像是二叉搜索树一样)保持不变-原则“左子小于父,右子大于或等于父”在任何地方都不会受到侵犯。

前一个图像XP的左后代如果X 是正确的后代,您只需要反映以下情况:


如果X的难度不是第二级,而是第三级,那么一切都比较复杂,但难度不大。

如果X具有父P,这反过来又具有父ģ,然后我们只有两个不同的情况:
1)如果X是一个后代P 上的相同侧作为Pģ ;
2)如果XP其父母的全能后代

首先,考虑第一种情况。

ZigZig转


X是左后裔PP中的左侧为后代g ^(该选项时,XP是正确的同时后人也同样解决)。


正如可以看到,有必要改变之间的关系的XP对于g,以及适当地大于子树与c而且我们的二叉搜索树不会因此受到影响。

曲折转向


如果X是右后代,P左(或者X左,P是右,不是本质),那么我希望您已经了解了这张图片中的所有内容:


如果树中的X仍处于较低的嵌套级别,则要提高它,您需要将ZigZig或ZigZag(如有必要,多次执行)应用于分支的父级(嵌套的第三层)。

反转排序:: Splay排序


实际上,这里我们执行与树排序相同的点-首先,我们构建一个二叉排序树,然后再进行遍历。但是,每当我们在树上插入一个新的结时-使用山脊和锯齿,就将其作为根。

在获胜的第一阶段,这样做是不可行的,插入一个节点(考虑到必须锯齿形才能成为根节点)的平均成本为O(log n -因此,此阶段的复杂度为O(n log n

但是,结果是,树发生了惊人的变化-它变直并一直保持这种变直状态。这将决定性地加快第二阶段(树遍历)的速度,因为最终的梯形图的复杂度为O(n)。

因此,算法的总复杂度(最差,中等,最佳)为O(n log n

实际上,这种排序要比MergeSort,QuickSort和其他HeapSort慢,但是它清楚地说明了如何使用二叉搜索树加快操作速度。

这个寓言的寓意是这样的:如果您必须处理二进制搜索树-如果可能的话,使其自平衡。作为一种可能的选择-与八字树一起使用它,即 对树中任何节点执行任何操作时,请将该节点设为根。当然,还有其他可替代的自平衡搜索树(红黑树,AVL树等),可能会更好。

C代码


不要看起来紧张
/*
 *------------------------------------------------------------
 *
 *      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() */

下一个系列预告片


然后我们回到堆里。下一件小事情要比我们今天检查的更有趣。这种混合体是树状的数据结构,既是堆又是二叉搜索树。



参考文献


二叉搜索树伸展树

伸展树

系列文章:



排序已添加到AlgoLab。因此,如果使用宏更新Excel文件,则可以亲自破坏二进制搜索树。

All Articles