数据结构和算法(8):搜索树(二叉搜索树和AVL树)

查找

所谓的查找或搜索,指从一组数据对象中找出符合特定条件者,这是构建算法的一种基本而重要的操作。其中的数据对象,统一地表示和实现为 词条(entry) 的形式;不同词条之间,依照各自的 关键码(key) 彼此区分。

循关键码访问: 查找的过程与结果,仅仅取决于目标对象的关键码。

词条

template <typename K, typename V> struct Entry { //词条模板类
	K key; V value; //关键码、数值
	Entry ( K k = K(), V v = V() ) : key ( k ), value ( v ) {}; //默认构造函数
	Entry ( Entry<K, V> const& e ) : key ( e.key ), value ( e.value ) {}; //基于克隆的构造函数
	bool operator< ( Entry<K, V> const& e ) { return key < e.key; } //比较器:小于
	bool operator> ( Entry<K, V> const& e ) { return key > e.key; } //比较器:大于
	bool operator== ( Entry<K, V> const& e ) { return key == e.key; } //判等器:等于
	bool operator!= ( Entry<K, V> const& e ) { return key != e.key; } //判等器:不等于
}; //得益于比较器和判等器,从此往后,不必严格区分词条及其对应的关键码

词条对象拥有成员变量keyvalue。前者作为特征,是词条之间比对和比较的依据;后者为实际的数据。

任意词条之间可相互比较大小,是有序向量得以定义,以及二分查找算法赖以成立的基本前提

二叉搜索树

二叉搜索树中,处处都满足顺序性:任一 节点r的 左(右)子树中 ,所有节点( 若存在)均不大于(不小于)r
假定所有节点互不相等 ,回避边界情况:任一 节点r 的 左(右)子树中 ,所有 节点( 若 存在)均小于(大于)r
在实际应用中,对相等元素的禁止既不自然也不必要。

包含不同的 n n n 个节点可以构成的不同二叉搜索树有 C a t a l a n ( n ) = ( 2 n ) ! / [ ( n + 1 ) ! n ! ] Catalan(n) = (2n)!/[(n+1)!n!] Catalan(n)=(2n)!/[(n+1)!n!] 棵。

任何 一棵二叉树是二叉搜索树,当且仅当其中序遍历序列单调非降。
数据结构和算法(8):搜索树(二叉搜索树和AVL树)_第1张图片

BST 模板类

#include "../BinTree/BinTree.h"	//引入BinTree

template <typename T> class BST : public BinTree<T>{ //由BinTree派生BST模板类
protected:
	BinNodePosi(T)_hot; //“命中”节点的父亲
	BinNodePosi(T) connect34 (//按照“3+4”结构,联接3个节点及四棵子树
		BinNodePosi(T)BinNodePosi(T)BinNodePosi(T),
		BinNodePosi(T)BinNodePosi(T)BinNodePosi(T)BinNodePosi(T));
	BinNodePosi(T) rotateAt ( BinNodePosi(T) x );//对x及其父亲、祖父做统一旋转调整
public: //基本接口︰以virtual修饰,强制要求所有派生类(BST变种)根据各自的规则对其重写
	virtual BinNodePosi(T) &search ( const T&e );//查找
	virtual BinNodePosi(T) insert ( const T&e );//插入
	virtual bool remove ( const T&e );//删除
};

BST:查找

采用了减而治之的思路与策略,其执行过程可描述为:从树根出发 ,逐步地缩小查找范围,直到发现目标(成功)或缩小至空树(失败)

查找过程中,一旦发现当前节点为NULL,即说明查找范围已经缩小至空,查找失败;否则,视关键码比较结果,向左(更小)或向右(更大)深入,或者报告成功(相等)。

template <typename T> //在以v为根的(AVL、SPLAY、rbTree等)BST子树中查找关键码e
static BinNodePosi(T) & searchIn ( BinNodePosi(T) & v, const T& e, BinNodePosi(T) & hot ) {
	if ( !v || ( e == v->data ) ) return v; //逑弻基:在节点v(或假想的通配节点)处命中
	hot = v; //一般情况:先记下当前节点,然后再
	return searchIn ( ( ( e < v->data ) ? v->lc : v->rc ), e, hot ); //深入一层,递归查找
} //返回时,返回值指向命中节点(或假想的通配哨兵),hot指向其父亲(退化时为初始值NULL)
template <typename T> BinNodePosi(T) & BST<T>::search ( const T& e ) //在BST中查找关键码e
{ return searchIn ( _root, e, _hot = NULL ); } //返回目标节点位置的引用,以便后续插入、删除操作

若查找成功,则searchIn()以及search()的返回值都将如图(a)所示,指向一个关键码为e且真实存在的节点;若查找失败,则返回值的数值虽然为NULL,但是它作为引用将如图(b)所示,指向最后一次试图转向的空节点。
数据结构和算法(8):搜索树(二叉搜索树和AVL树)_第2张图片
在调用searchIn()算法之前,search()接口首先将内部变量_hot初始化为NULL,然后作为引用型参数hot传递给searchIn()。在整个查找的过程中,hot变量始终指向当前节点的父亲。因此在算法返回时,按照如上定义,_hot亦将统一指向“命中节点”的父亲。

_hot节点是否拥有另一个孩子,与查找成功与否无关。查找成功时,节点e可能是叶子,也可能是内部节点;查找失败时,假想的哨兵e等效于叶节点,但可能有兄弟。

复杂度
总体所需时间应线性正比于查找路径的长度,或最终返回节点的深度:最好为O(1),最坏为O(n).
若要控制单次查找在最坏情况下的运行时间,须从控制二叉搜索树的高度入手。

BST:插入

为了在二叉搜索树中插入一个节点,首先需要利用查找算法search()确定插入的位置及方向,然后才能将新节点作为叶子插入。

template <typename T> BinNodePosi(T) BST<T>::insert ( const T& e ) { //将关键码插入BST树中
	BinNodePosi(T) & x = search ( e ); if ( x ) return x; //确认目标不存在(留意对_hot癿设置)
	x = new BinNode<T> ( e, _hot ); //创建新节点x:以e为关键码,以_hot为父
	_size++; //更新全树规模
	updateHeightAbove ( x ); //更新x及其历代祖先的高度
	return x; //新插入的节点,必为叶子
} //无论e是否存在于原树中,返回时总有x->data == e

首先调用search()查找e。若返回位置x非空,则说明已有雷同节点,插入操作失败。否则,x必是_hot节点的某一空孩子,于是创建这个孩子并存入e。此后,更新全树的规模记录,并调用updateHeightAbove()更新x及其历代祖先的高度。
按照以上实现方式,无论插入操作成功与否,都会返回一个非空位置,且该处的节点与拟插入的节点相等。如此可以确保一致性,以简化后续的操作。

复杂度
节点插入操作所需的时间,主要消耗于对算法search()updateHeightAbove()的调用。后者与前者一样,在每一层次至多涉及一个节点,仅消耗O(1)时间,故其时间复杂度也同样取决于新节点的深度,在最坏情况下不超过全树的高度。

BST:删除

为从二叉搜索树中删除节点,首先也需要调用算法BST::search(),判断目标节点是否的确存在于树中。若存在,则需返回其位置,然后方能相应地具体实施删除操作。

单分支

数据结构和算法(8):搜索树(二叉搜索树和AVL树)_第3张图片
若欲删除节点69,需首先通过search(69)定位待删除节点(69)。因该节点的右子树为空,故只需如图(b)所示,将其替换为左孩子(64),则拓扑意义上的节点删除即告完成。
当然,为保持二叉搜索树作为数据结构的完整性和一致性,还需更新全树的规模记录,释放被摘除的节点(69),并自下而上地逐个更新替代节点(64)历代祖先的高度。注意,首个需要更新高度的祖先(58),恰好由_hot指示。

双分支

设拟再删除二度节点36。如图(b)所示,首先调用BinNode::succ()算法,找到该节点的直接后继(40)。然后,只需如图©所示交换二者的数据项,则可将后继节点等效地视作待删除的目标节点。不难验证,该后继节点必无左孩子,从而相当于转化为此前相对简单的情况。于是最后可如图(d)所示,将新的目标节点(36)替换为其右孩子(46)。
请注意,在中途互换数据项之后,这一局部如图( c )所示曾经一度并不满足顺序性。但这并不要紧–不难验证,在按照上述方法完成整个删除操作之后,全树的顺序性必然又将恢复。
同样地,除了更新全树规模记录和释放被摘除节点,此时也要更新一系列祖先节点的高度。不难验证,此时首个需要更新高度的祖先(53),依然恰好由_hot指示。

remove() 删除关键码

1 template <typename T> bool BST<T>::remove ( const T& e ) { //从BST树中删除关键码e
2 BinNodePosi(T) & x = search ( e ); if ( !x ) return false; //确荏目标存在(留意_hot癿设置)
3 removeAt ( x, _hot ); _size--; //实施删除
4 updateHeightAbove ( _hot ); //更新_hot及其历代祖先的高度
5 return true;
6 } //删除成功与否,由返回值指示

首先调用search()查找e。若返回位置x为空,则说明树中不含目标节点,故删除操作随即可以失败返回。否则,调用removeAt()删除目标节点x。同样,此后还需更新全树的规模,并调用函数updateHeightAbove(_hot),更新被删除节点历代祖先的高度。

/*******************************************容*********容************************容***********
* BST节点删除算法︰删除位置x所指的节点(全局静态模板函数,适用于AVL、Splay、RedBlack等各种BST )
*目标x在此前经查找定位,并确认非NULL,故必删除成功;与searchIn不同,调用之前不必将hot置空
*返回值指向实际被删除节点的接替者,hot指向实际被删除节点的父亲——二者均有可能是NULL
*************************************牢*容**牢****水*容容容***水水水容容容*容容容容容容容容容容容容容容容容容字米林容容容容容**/
template <typename T>
static BinNodePosi(T) removeAt ( BinNodePosi(T) & x,BinNodePosi(T) & hot ) {
	BinNodePosi(T) w = x;	//实际被摘除的节点,初值同x
	BinNodePosi(T) succ = NULL;//实际被删除节点的接替者
	if (!HasLChild ( *x ) )	//若*x的左子树为空,则可
		succ = x = x->rc; //直接将*x替换为其右子树
	else if ( ! HasRChild (*x ) )	//若右子树为空,则可
		succ = x = x->lc; //对称地处理——注意∶此时succ != NULL
	else {	//若左右子树均存在,则选择x的直接后继作为实际被摘除节点,为此需要
		w = w->succ(); //(在右子树中)找到*x的直接后继*w
		swap ( x->data,w->data );//交换*x和*w的数据元素
		BinNodePosi(T) u = w->parent;
		( ( u == x ) ? u->rc : u->lc ) = succ = w->rc;//隔离节点*w
		}
	hot = w->parent;//记录实际被删除节点的父亲
	if ( succ ) succ->parent = hot; //并将被删除节点的接替者与hot相联
	release ( w->data ); release ( w ); return succ;//释放被摘除节点,返回接替者
}

复杂度
删除操作所需的时间,主要消耗于对search()succ()updateHeightAbove()的调用。在树中的任一高度,它们至多消耗O(1)时间。故总体的渐进时间复杂度,亦不超过全树的高度。

平衡

search()insert()remove()等主要接口的运行时间,均线性正比于二叉搜索树的高度。
在最坏情况下,二叉搜索树可能彻底地退化为列表,此时的查找效率甚至会降至O(n),线性正比于数据集的规模。
因此,需要控制树高。

任意的n个互异关键码,都可以构成n!种全排列。若各排列作为输入序列的概率均等,则只要将它们各自所生成二叉搜索树的平均查找长度进行平均,即可在一定程度上反映二叉搜索树的平均查找性能。可以证明,在这一随机意义下,二叉搜索树的平均高度为O(logn)
假定n个互异节点同时给定,然后在遵守顺序性的前提下,随机确定它们之间的拓扑联接。如此,称二叉搜索树由这组节点“随机组成”。n个互异节点组成的二叉搜索树,总共可能有(2n)!/n!/(n + 1)!。若这些树出现的概率均等,则通过对其高度做平均可知 ,平均查找长度为 O ( n ) O(\sqrt n) O(n )

理想平衡与适度平衡

既然二叉搜索树的性能主要取决于高度,故在节点数目固定的前提下,应该尽可能降低高度。相应地,应尽可能使兄弟子树的高度彼此接近,即全树尽可能地平衡。
包含n个节点的二叉树,高度不可能小于 [ log ⁡ 2 n ] [\log_2n] [log2n]。若高度恰好为 [ log ⁡ 2 n ] [\log_2n] [log2n],则称作理想平衡树

在渐进意义下适当放松标准之后的平衡性,称作适度平衡

等价交换

若两颗二叉搜索树的中序遍历序列相同,则称它们彼此等价;反之亦然。
虽然等价二叉搜索树中各节点的垂直高度可能有所不同,但水平次序完全一致。

平衡二叉搜索树的适度平衡性,都是通过对树中每一局部增加某种限制条件来保证的。具有以下局部性:
1)经过单次动态修改操作后,至多只有O(1) 处局部不再满足限制条件
2)总可在O(log n)时间内,使这O(1)处局部(以至全树)重新满足限制条件。
意味着刚刚失去平衡的二叉搜索树,必然可以迅速转换为一棵等价的平衡二叉搜索树。等价二叉搜索树之间的上述转换过程,也称作等价变换

旋转调整

最基本的修复手段,就是通过围绕特定节点的旋转,实现等价前提下的局部拓扑调整。

数据结构和算法(8):搜索树(二叉搜索树和AVL树)_第4张图片
zig 旋转(顺时针旋转):将X和v作为c的左子树、右孩子,Y和Z分别作为v的左、右子树。

数据结构和算法(8):搜索树(二叉搜索树和AVL树)_第5张图片
zag旋转(逆时针旋转):将v和Z作为c的左孩子、右子树,X和Y分别作为v的左、右子树。

zig和zag旋转均属局部操作,均属于等价操作,仅涉及常数个节点及其之间的联接关系,故均可在常数时间内完成。就与树相关的指标而言,经一次 zig 或 zag 旋转之后,节点v的深度加一,节点c的深度减一;这一局部子树(乃至全树)的高度可能发生变化,但上、下幅度均不超过一层。

AVL树

通过合理设定适度平衡的标准,并借助等价变换,AVL树(AVL tree)可以实现近乎理想的平衡
在渐进意义下,AVL树可始终将其高度控制在O(logn)以内,从而保证每次查找、插入或删除操作,均可在O(logn)的时间内完成。

AVL树:重平衡

任一节点v平衡因子定义为“其左、右子树的高度差”,即:balFac(v) = height(lc(v)) - height(rc(v))
所谓AVL树,即平衡因子受限的二叉搜索树—其中各节点平衡因子的绝对值均不超过1

AVL 模板类

#include "../BST/BST.h”	//基于BST实现AVL树
template <typename T> class AVL : public BST<T>{ //由BST派生AVL树模板类
public:
	BinNodePosi(T) insert ( const T& e );	//插入(重写)
	bool remove ( const T& e );	//删除(重写)
//BST::search()等其余接口可直接沿用
};

#define Balanced(x) ( stature( (x).lc ) == stature( (x).rc ) )//理想平衡条件
#define BalFac(x) ( stature( (x).lc ) - stature( (x).rc ) )//平衡因子
#define AvlBalanced(x)( ( -2<BalFac(x) ) && ( BalFac(x)<2 ) )//AVL平衡条件

在完全二叉树中各节点的平衡因子非01,故完全二叉树必是AVL树。

结论:高度为h的AVL树至少包含fib(h + 3) - 1个节点(数学归纳所得)
包含n个节点的AVL树的高度应为O(logn)

失衡与重平衡

AVL 树经过插入、删除等动态修改操作之后,节点的高度可能发生变化,以致于不再满足AVL树的条件。
因节点x的插入或删除而暂时失衡的节点,构成失衡节点集,记作UT(x)。请注意,若x为被摘除的节点,则UT(x)仅含单个节点;但若x为被引入的节点,则UT(x)可能包含多个节点。

AVL树:插入

新引入节点x后,UT(x)中的节点都是x的祖先,且高度不低于x的祖父。以下,将其中的最深者记作g(x)。在xg(x)之间的通路上,设pg(x)的孩子,vp的孩子。注意,既然g(x)不低于x的祖父,则p必是x的真祖先。
g(x)是因x的引入而失衡,则pv的高度均不会低于其各自的兄弟。

借助如下代码所示的宏tallerChild(),即可反过来由g(x)找到pv

/**************************************************************************************
*在左、右孩子中取更高者
*在AVL平衡调整前,借此确定重构方案
*************************************************************************************/
#define tallerChild(x)( \
	stature( (x)->lc ) > stature( (x)->rc ) ?(x)->lc : (/*左高*/	\
	stature( (x)->lc ) < stature( (x)->rc ) ?(x)->rc : (/*右高*/	\
	IsLChild(* (x)) ? (x)->lc : (x)->rc	/*等高︰与父亲x同侧者(zIg-zIg或zAg-zAg )优先*/	\
) \
) \

单旋

数据结构和算法(8):搜索树(二叉搜索树和AVL树)_第6张图片
做逆时针旋转zag(g(x)),得到如图(b)所示的另一棵等价二叉搜索树。

双旋

数据结构和算法(8):搜索树(二叉搜索树和AVL树)_第7张图片
节点插入后通过连续的两次旋转操作使AVL树重新平衡
此时,可先做顺时针旋转zig(p),得到如图(b)所示的一棵等价二叉搜索树。再做逆时针旋转zag(g(x)),得到如图( c )所示的另一棵等价二叉搜索树。

实现

template <typename T> BinNodePosi(T) AVL<T>::insert ( const T& e ) {	//将关键码e插入AVL树中
	BinNodePosi(T) & x = search ( e ); if ( x ) return x;//确认目标节点不存在
	BinNodePosi(T) xx = x = new BinNode<T> ( e,_hot ); _sizet+;//创建新节点x
//此时,x的父亲_hot若增高,则其祖父有可能失衡
	for ( BinNodePosi(T) g = _hot; g; g = g->parent ) { //从x之父出发向上,逐层检查各代祖先g
		if ( !AvlBalanced ( *g ) ) { //一旦发现g失衡,则(采用“3+4”算法)使之复衡,并将子树
			FromParentTo ( *g ) = rotateAt ( tallerChild ( tallerchild ( g ) ) );//重新接入原树
			break; //g复衡后,局部子树高度必然复原;其祖先亦必如此,故调整随即结束
		}else //否则( g依然平衡),只需简单地
			updateHeight ( g );//更新其高度(注意∶即便g未失衡,高度亦可能增加)
	}//至多只需一次调整﹔;若果真做过调整,则全树高度必然复原
	return xx;//返回新节点位置
}//无论e是否存在于原树中,总有AVL::insert(e)->data == e

无论单旋或双旋,经局部调整之后,不仅g(x)能够重获平衡,而且局部子树的高度也必将复原。这就意味着,g(x)以上所有祖先的平衡因子亦将统一地复原。换而言之,在AVL树中插入新节点后,仅需不超过两次旋转,即可使整树恢复平衡

算法首先按照二叉搜索树的常规算法,在O(logn)时间内插入新节点x。既然原树是平衡的,故至多检查O(logn)个节点即可确定g(x);如有必要,至多旋转两次,即可使局部乃至全树恢复平衡。
AVL树的节点插入操作可以在O(logn)时间内完成

AVL树:删除

与插入操作十分不同,在摘除节点x后,以及随后的调整过程中,失衡节点集UT(x)始终至多只含一个节点。而且若该节点g(x)存在,其高度必与失衡前相同。另外还有一点重要的差异是,g(x)有可能就是x的父亲。

与插入操作同理,从_hot节点出发沿parent指针上行,经过O(logn)时间即可确定g(x)位置。作为失衡节点的g(x),在不包含x的一侧,必有一个非空孩子p,且p的高度至少为1。于是,可按以下规则从p的两个孩子(其一可能为空)中选出节点v:若两个孩子不等高,则v取作其中的更高者;否则,优先取vp同向者(亦即,vp同为左孩子,或者同为右孩子)。

单旋

数据结构和算法(8):搜索树(二叉搜索树和AVL树)_第8张图片
(a)所示,由于在T3中删除了节点而致使g(x)不再平衡,但p的平衡因子非负时,通过以g(x)为轴顺时针旋转一次即可恢复局部的平衡。平衡后的局部子树如图(b)所示。

双旋

数据结构和算法(8):搜索树(二叉搜索树和AVL树)_第9张图片
节点删除后通过两次旋转恢复局部平衡。
g(x)失衡时若p的平衡因子为-1,则经过以p为轴的一次逆时针旋转之后(图(b)),即可转化为图(a)。再以g(x)为轴顺时针旋转,即可恢复局部平衡(图( c ))。

在删除节点之后,尽管也可通过单旋或双旋调整使局部子树恢复平衡,但复平衡之后,局部子树的高就全局而言,依然可能再次失衡。这种由于低层失衡节点的重平衡而致使其更高层祖先失衡的现象,称作“失衡传播
失衡传播的方向必然自底而上,而不致于影响到后代节点

实现

template <typename T> bool AVL<T>::remove ( const T& e ) { //从AVL树中删除关键码e
	BinNodePosi(T) & x = search ( e ); if ( !x ) return false;//确认目标存在(留意_hot的设置)
	removeAt ( x,_hot ); _size--;//先按BST规则删除之(此后,原节点之父_hot及其祖先均可能失衡)
	for ( BinNodePosi(T) g = _hot; g; g = g->parent ) { //从_hot出发向上,逐层检查各代祖先g
		if ( !AvlBalanced ( *g ) )//一旦发现g失衡,则(采用“3+4”算法)使之复衡,并将该子树联至
		g = FromParentTo ( *g ) = rotateAt ( tallerchild ( tallerChild ( g ) ) );//原父亲
		updateHeight ( g );//并更新其高度(注意:即便g未失衡,高度亦可能降低)
	}//可能需做O(logn)次调整——无论是否做过调整,全树高度均可能降低
	return true;//删除成功
}//若目标节点存在且被删除,返回true;否则返回false

较之插入操作,删除操作可能需在重平衡方面多花费一些时间。不过,既然需做重平衡的节点都是x的祖先,故重平衡过程累计只需不过O(logn)时间。
综合各方面的消耗,AVL树的节点删除操作总体的时间复杂度依然是O(logn)

AVL树:(3+4)-重构

同样需要从刚发生修改的位置x出发逆行而上,直至遇到最低的失衡节点g(x)。于是在g(x)更高一侧的子树内,其孩子节点p和孙子节点v必然存在,而且这一局部必然可以g(x)pv为界,分解为四棵子树,将它们按中序遍历次序重命名为 T0 至 T3。
若同样按照中序遍历次序,重新排列g(x)pv,并将其命名为abc,则这一局部的中序遍历序列应为:{T0 , a, T1 , b, T2 , c, T3}
将这三个节点与四棵子树重新“组装”起来,恰好即是一棵AVL树
数据结构和算法(8):搜索树(二叉搜索树和AVL树)_第10张图片
这一理解涵盖了此前两节所有的单旋和双旋情况。相应的重构过程,仅涉及局部的三个节点及其四棵子树,故称作 “3 + 4”重构

/********************************************************************
*按照“3+4”结构联接3个节点及其四棵子树,返回重组之后的局部子树根节点位置(即b)
*子树根节点与上层节点之间的双向联接,均须由上层调用者完成
*可用于AVL和RedBlack的局部平衡调整
***********************************************************************/
template <typename T> BinNodePosi(T) BST<T>::connect34 (
	BinNodePosi(T) a,BinNodePosi(T) b, BinNodePosi(T) c,
	BinNodePosi(T)T0,BinNodePosi(T)T1,BinNodePosi(T) T2,BinNodePosi(T) T3
) {
	a->lc = T0; if ( T0 ) T0->parent = a;
	a->rc = T1; if ( T1 ) T1->parent = a; updateHeight ( a );
	c->lc = T2; if ( T2 ) T2->parent = c;
	c->rc = T3; if ( T3 ) T3->parent = c; updateHeight ( c );
	b->lc = a; a->parent = b;
	b->rc = c; c->parent = b; updateHeight ( b );
	return b;//该子树新的根节点
}

利用以上connect34()算法,即可视不同情况,按如下具体方法完成重平衡:

/**********************************************************************
* BST节点旋转变换统一算法( 3节点+4子树),返回调整之后局部子树根节点的位置
*注意:尽管子树根会正确指向上层节点(如果存在),但反向的联接须由上层函数完成
*************************************************************************/
template <typename T> BinNodePosi(T) BST<T>::rotateAt ( BinNodePosi(T)v ) { //v为非空孙辈节点
	BinNodePosi(T) p = v->parent;BinNodePosi(T) g = p->parent;//视v、 p和g相对位置分四种情况
	if ( IsLChild ( *p ) )/* zig */
		if ( IsLChild (*v ) ) {/* zig-zig*/
			p->parent = g->parent;//向上联接
			return connect34 ( v, p, g, v->lc, v->rc, p->rc, g->rc );
		}else { /* zig-zag */
			v->parent = g->parent;//向上联接
			return connect34 ( p, v,g,p->lc, v->lc, v->rc, g->rc );
	else/* zag */
		if ( IsRChild (*v ) ) {/* zag-zag */
			p->parent = g->parent;//向上联接
			return connect34 ( g, p, v, g->lc,p->lc,v->lc, v->rc );
		}else { /* zag-zig */
			v->parent = g->parent;//向上联接
			return connect34 ( g, v, p,g->lc, v->lc, v->rc,p->rc );
		}
}

复杂度也依然是O(1)

你可能感兴趣的:(数据结构和算法,数据结构,算法)