平衡二叉树(常问问题)

平衡二叉树的定义(AVL):这是一个空的树,或具有二进制以下性质:之差的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树。

平衡因子(bf):结点的左子树的深度减去右子树的深度,那么显然-1<=bf<=1;

非常显然,平衡二叉树是在二叉排序树(BST)上引入的。就是为了解决二叉排序树的不平衡性导致时间复杂度大大下降。那么AVL就保持住了(BST)的最好时间复杂度O(logn)。所以每次的插入和删除都要确保二叉树的平衡。那么怎么保持平衡呢?

我努力看了看数据结构上的解说。可是看的仅仅晕+_+!

我对他的解说非常无语。他所谓的“旋转”操作讲的不明不白,看的我气的蛋疼!你说你旋转。你得说清是怎样旋转?以那个结点为中心,那些或者说那个结点转了。那些结点不动。你就在哪里旋转来旋转去的,谁知道你是咋转的,你在哪慢慢转吧!哥转只是你,另找高明!于是在网上找啊找。仅仅为想搞明确是究竟怎么转的!点击打开链接让我对“旋转”有所领悟,表示感谢。

 

插入时:

那到底是怎样“转”的呢?

 

首先必须明确一个核心操作,不让它叫“旋转”!而叫——>“两个结点的变换”

如图:

就拿第一个来说->点100和101的变换:

点101占领点100的位置,点100换到了101的对面的位置。其它点的相对位置不变。

我们能够更直观的理解为:把101结点“上提”一下!

分析:101>100。所以100能够作为101的左孩子;

也就是在二叉排序树中,两个结点这种变换操作是可行的,是符合二叉排序树的性质。

不仅这个简单的图。不论什么复杂的二叉排序树都能够。你能够试试。或许你会说假设点101左边有孩子怎么办?别着急~,当然有办法!

 

下边正式说这个图的四种不平衡情况(插入时)及操作:

首先还须要明确一个概念->最小不平衡子树的根结点:也就是当你进行插入操作时。找到该须要插入结点的位置并插入后,从该结点起向上寻找(回溯)。第一个不平衡的结点即平衡因子bf变为-2或2。

为什么要引入这个最小不平衡根结点的概念,由于在插入时,对该子树进行保持平衡操作后,其他的结点的平衡因子不会变。也就是整棵树又恢复平衡了。

为什么呢?

你想想不平衡点的bf一定是-2或2吧,经过平衡操作后,他会把一边子树的一个结点分给还有一边的子树,也就是一边的深度分给还有一边,这样就平衡了。

比方。插入前:左边是深度1,右边深度是0;插入后左边深度是2,右边深度是0,经过平衡后左边深度是1。右边深度是1。

那么你说插入前和插入后该根结点所领导的子树的深度变没??仍是1,显然没变!那么就仍保持了这棵树的平衡了。

 

以下即四种情况分别为:左左、右右、左右、右左,每种情况又有两个图:①、②,①是该情况的最简单的图形,②是该情况的一般的图形。

设x为最小不平衡子树的根结点。y为刚插入的点

 

左左:

即在x的左孩子a的左孩子c上插入一个结点y(该结点也能够是c,如图①),即y能够是c,也能够是c的左孩子(如图②)。也能够是c的右孩子(不在画出)

                                  

图①就不用说了,结点x和结点a变换,则树平衡了。那么图②就是树中的普通情况了a结点有右孩子d,那要进行x和a变换,那么a的右孩子放哪啊?

非常easy,如图放在x的左孩子上;分析:x>d,d>a,所以d可作为x的左孩子。且可作为a的右孩子中的孩子。下边这种类似情况不再一一分析,自己分析分析~

实现:找到根结点x,与它的左孩子a进行交换就可以使二叉树树再次平衡;

 

 

右右:

即在x的右孩子a的右孩子c上插入一个结点y(该结点也能够是c,如图①)。即y能够是c。也能够是c的右孩子(如图②),也能够是c的左孩子(不在画出)

实现:找到根结点x,与它的右孩子a进行交换就可以使二叉树树再次平衡;

 

 

左右:

即在x的左孩子a的右孩子c上插入一个结点y(该结点也能够是c,如图①),即y能够是c,也能够是c的右孩子(如图②),也能够是c的左孩子(不在画出)

 

 

这个左右和下边的右左,略微复杂了点。须要进行两次交换。才干达到平衡,注意这时y是c的右孩子。终于y作为x的左孩子。若y是c的左孩子。终于y作为a

的右孩子。绘图分析一下~~下边类似,不再敖述。

 

实现:找到根结点x,让x的左孩子a与x的左孩子a的右孩子c进行交换。然后再让x与x此时的左孩子c进行交换,终于达到平衡。

 

右左:

即在x的右孩子a的左孩子c上插入一个结点y(该结点也能够是c,如图①)。即y能够是c,也能够是c的右孩子(如图②),也能够是c的左孩子(不在画出)

 

实现:找到根结点x,让x的右孩子a与x的右孩子a的左孩子c进行交换,然后再让x与x此时的右孩子c进行交换。终于达到平衡;

 

上边的四种情况包括了全部插入时导致不平衡的情况,上面给出的不过一棵大树中的最小不平衡子树。一定要想清楚,别迷了!

另外一定要注意这个交换操作。比方a与b交换(a在上,b在下),b一定要占领a的位置!

什么意思?就是b要放在(覆盖)储存a的那块内存上。

再通俗点说。若a是x的左孩子,则交换后b要做x的左孩子;这就是所谓的b占领a的位置!

 

那么怎样找到最小不平衡子树的根结点x。并推断出它是属于那种情况的?

 

插入一个结点时,我们首先找到须要插入的位置,并插入;数据结构上用的是递归。不要说递归太浪费时空。你想想一个含2^31个结点的平衡二叉树的深度大约是31吧,它递归再多也不就是31层!并且递归代码短小、精悍、富含艺术之美!

所以我觉得对于这个平衡二叉树,用递归非常合适!

显然插入之后就要检查是否出现不平衡的结点。那么怎样检查?

我们知道,你插入的时候用的是递归,一条线找到要插的位置,并插入;那么谁的平衡因子的有可能变呢?

不难想象。仅仅有该条线上的结点的平衡因子有可能改变。那么我们在回溯的时候不就能够找到第一个不平衡的子树的结点?!

但是我们怎样推断该结点的平衡因子是否应该改变,显然要看它被插入结点的一边的深度是否添加;

怎样看它被插入结点的一边的深度是否添加?

如上图,怎样看x的右孩子a(即被插入结点的一边)的深度添加?我们知道在a的右孩子上插入了结点y那么a的bf是一定要减1

那么x结点的bf?可依据a的bf决定是否改变!

若a:bf=-1或1。那么a之前一定为0。表示a的深度添加了,那么x的bf可依据a是x哪一边的孩子决定+1或-1。

若a:bf=0。那么a之前一定为-1或1。表示a的深度每添加,那么不仅x的bf就不用变,该条线上的全部结点的bf都不用变。直接返回就可以;

 

当然了。那么找到最小不平衡子树的根结点x了,怎样推断它属于哪种不平衡呢?

①依据上边的几种情况,我们须要知道两个方向,在回溯时能够记录一下该结点到下一个结点的方向0:左、1:右为第二个方向。传递给上一层中。那么上层中的方向就是一个方向,有了这两个方向就能确定是哪种不平衡了。

还就上边的图说吧~能够定义一个全局变量secdirection(第二个方向)。也可在递归中定义一个局部变量,返回给上一层。

在回溯到a中时。secdirection=1。到x的时候

x->a的方向也为1,定义firdirection=1;而这时x:bf=-2。那么就找到了最小不平衡子树的根结点x,又知道了两个方向。那么进行对应的平衡操作不即可了。

②事实上我代码中的就是依照①写的,但是刚又想了。事实上不用用个变量记录第二个方向,能够依据a的bf确定它的第二个方向。a:bf=-1说明右孩子的深度添加,y加到右孩子上;

a:bf=1;说明左孩子的深度添加,y加到左孩子上;

 

好了。找到了最小不平衡子树的根结点x了,也知道了那种不平衡,调用keepbalance(...)就使树平衡了,但是某些结点的平衡因子在变换是变了~~咋办?

我就是一种一种情况推出来的,不知道别人怎么变的,每一种情况都有固定的几个点变了,变成了一个固定的值!

谁有更好的办法,请多多不吝赐教!

 

下边一一列出(插入操作中)变换后bf改变的结点及值:

左左:前a->bf=1 后 x->bf=0,a->bf=0;右右:前a->bf=-1 后x->bf=0,a->bf=0。显然左左与右右的x与a变换后的bf都为0;

左右、右左中结点bf的改变要依据之前c的bf!

左右:若c->bf=1,则x->bf=-1,a->bf=0,c->bf=0;若c->bf=-1,则x->bf=0,a->bf=1,c->bf=0。若c->bf=0,则x->bf=0,a->bf=0,c->bf=0;

右左:若c->bf=1,则x->bf=0,a->bf=-1,c->bf=0;若c->bf=-1,则x->bf=1,a->bf=0,c->bf=0。若c->bf=0,则x->bf=0,a->bf=0,c->bf=0;

能够发现。当左右、右左的c->bf同样时x与a的bf刚好取相反的值。

 

好了,到如今插入一个结点的二叉树最终平衡了,对应的平衡因子也改动了!插入算是完毕了。!

 

删除时:

删除类似插入的操作,蛋又不同,删除会有一些特殊情况须要特殊处理。当然核心操作“保持平衡”是不变的!

 

删除时少一个结点。也就是该结点所在的子树深度可能会减小,而插入时多一个结点,该结点所在的子树深度可能会添加,

所以递归删除一个结点时,回溯时找到最小不平衡子树的根结点时,要向相反的方向去找属于哪种情况;

如图:

y为要删除的结点;

图①:y结点删除后。回溯到x结点x:bf=-1变为x:bf=-2;则需从相反方向即从x的右孩子的方向向下检查属于哪种情况,显然第一个方向为1:右。

第二个方向看a:bf的值——若为1时,那就相当于插入时‘右左’的情况;若为-1时,那就相当于插入时‘左左’的情况;可如今a:bf既不是1也不是-1

而是0,这就是删除的特殊情况了!

我们最好还是试试对他进行类似于插入时的‘右右’操作,看怎么样~    如上图,经过变换后该子树平衡了!

可是因子的

改动就跟插入时的‘右右’不一样了!此时变为:x:bf=-1,a:bf=1;所以我们最好还是就把a:bf=0也归纳为删除的‘右右’或‘左左’(如图②,不再敖述)操作。

那么删除时因子的改变需在插入时因子的改变中加入上:

左左:前a:bf=0 后x:bf=1,a:bf=-1。 右右:前a:bf=0 后x:bf=-1,a:bf=1;其它不变!

 

插入时结束结点平衡因子的改动,直接返回(也就是该树已经平衡了):

回溯时发现儿子结点的平衡因子为0(当发现不平衡结点,并进行平衡操作后。平衡后根结点的bf一定为0。也结束了)

可是删除时结束结点平衡因子的改动。直接返回,就与插入时不一样了:回溯时发现儿子结点的平衡因子为-1或1!

再删除操作中。平衡一个子树后。该子树的深度不一定不变。而仅仅有上边那种特殊情况该子树的深度不变,其它都会变!

能够想象,事实上是非常easy的道理:除了特殊情况其它都与插入的情况一模一样,说白了就是把深度大的子树(根结点的当中一个)向深度小子树贡献的深度,

于是就这样,子树(对于树根的领导)深度不小于原始1该?!

所以继续搜索一个一个,直到根达!

posted @ 2015-10-27 09:41  lcchuguo  阅读(859)  评论(0编辑  收藏  举报