1 平衡二叉树简介
平衡二叉树(Balanced Binary Tree 或 Height-Balanced Tree)又称AVL树。它或者是一棵空树,或者是具有下列性质的二叉树,它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。若将二叉树上结点的平衡因子BF( Balance Factor)定义为该结点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有结点的平衡因子只可能是-1,0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。
距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡子树。
2 平衡二叉树示例
我们希望由任何初始序列构成的二叉排序树都是AVL树。因为AVL树上任何结点的左右子树的深度之差都不超过1,则可以证明它的深度和 logn 是同数量级的(其中n为结点个数)。由此,它的平均查找长度也和logn同数量级。
平衡二叉树构建的基本思想就是在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡子树。在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的连接关系,进行相应的旋转,使之成为新的平衡子树。如何使构成的二叉排序树成为平衡树呢?看一个具体例子,假设有一个数组关键字序列为(3,2,1,4,5,6,7,10,9,8),其中结点左上角的数字是平衡因子BF的值。
1、第一个关键字是“3”,直接构建为根结点,如图2-1的左图所示。
此时结点“3”的BF是0(左孩子0 - 右孩子0 = 0)
2、第二个关键字是“2”,关键字“2”比关键字“3”小,所以成为“3”的左孩子,如图2-1的右图所示。
此时结点“3”的BF是1(左孩子1 - 右孩子0 = 1)
此时结点“2”的BF是0(左孩子0 - 右孩子0 = 0)
3、第三个关键字是“1”,关键字“1”比关键字“2”小,所以成为“2”的左孩子,此时关键字“3”的BF不在平衡因子的取值范围内,此时整棵树都成了最小不平衡子树,因此需要调整,因为关键字“3”的BF值为正,因此我们将整个树进行右旋(顺时针旋转),此时结点“2”成了根结点,结点“3”成了结点“2”的右孩子,此时三个结点的BF值均为0,如图2-2所示。
此时结点“3”的BF是2(左孩子2 - 右孩子0 = 2)
此时结点“2”的BF是1(左孩子1 - 右孩子0 = 1)
此时结点“1”的BF是0(左孩子0 - 右孩子0 = 0)
此时结点“2”的BF是-1(左孩子1 - 右孩子2 = -1)
此时结点“3”的BF是-1(左孩子0 - 右孩子1 = -1)
此时结点“1”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“4”的BF是0 (左孩子0 - 右孩子0 = 0)
5、第五个关键字是“5”,关键字“5”比关键字“4”大,所以成为“4”的右孩子,此时关键字“3”的BF不在平衡因子的取值范围内,此时要对最小不平衡子树(3、4、5)进行旋转,因为关键字“3”的BF值为负,因此我们将对最小不平衡子树进行左旋(逆时针旋转),旋转后结点“3”成了结点“4”的左孩子,整个树又达到了平衡,如图2-4所示。
此时结点“3”的BF是-2(左孩子0 - 右孩子2 = -2)
此时结点“2”的BF是-2(左孩子1 - 右孩子3 = -2)
此时结点“1”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“4”的BF是-1(左孩子0 - 右孩子1 = -1)
此时结点“5”的BF是0 (左孩子0 - 右孩子0 = 0)
6、第六个关键字是“6”,关键字“6”比关键字“5”大,所以成为“5”的右孩子,此时关键字“2”的BF不在平衡因子的取值范围内,此时要对最小不平衡子树进行旋转,因为关键字“2”的BF值为负,因此我们将对最小不平衡子树进行左旋(逆时针旋转),旋转后结点“4”成了根结点,结点“3”成了结点“2”的右孩子,整个树又达到了平衡,如图2-5所示。
此时结点“3”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“2”的BF是-2(左孩子1 - 右孩子3 = -2)
此时结点“1”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“4”的BF是-1(左孩子1 - 右孩子2 = -1)
此时结点“5”的BF是-1(左孩子0 - 右孩子1 = -1)
此时结点“6”的BF是0 (左孩子0 - 右孩子0 = 0)
7、第七个关键字是“7”,关键字“7”比关键字“6”大,所以成为“6”的右孩子,此时关键字“5”的BF不在平衡因子的取值范围内,此时要对最小不平衡子树(5、6、7)进行旋转,因为关键字“5”的BF值为负,因此我们将对最小不平衡子树进行左旋(逆时针旋转),旋转后结点“5”成了结点“6”的左孩子,整个树又达到了平衡,如图2-6所示。
此时结点“3”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“2”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“1”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“4”的BF是-1(左孩子2 - 右孩子3 = -1)
此时结点“5”的BF是-2(左孩子0 - 右孩子2 = -2)
此时结点“6”的BF是-1(左孩子0 - 右孩子1 = -1)
此时结点“7”的BF是0 (左孩子0 - 右孩子0 = 0)
8、第八个关键字是“10”,关键字“10”比关键字“7”大,所以成为“7”的右孩子,此时整个树是平衡状态,不需要旋转调整,如图2-7所示。
此时结点“3”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“2”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“1”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“4”的BF是-1(左孩子2 - 右孩子3 = -1)
此时结点“5”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“6”的BF是-1(左孩子1 - 右孩子2 = -1)
此时结点“7”的BF是-1(左孩子0 - 右孩子1 = -1)
此时结点“10”的BF是0(左孩子0 - 右孩子0 = 0)
9、第九个关键字是“9”,关键字“9”比关键字“10”小,所以成为“10”的左孩子,此时关键字“7”的BF不在平衡因子的取值范围内,此时要对最小不平衡子树(7、10、9)进行旋转,但是若简单因为关键字“7”的BF值为负,因此我们将对最小不平衡子树进行左旋(逆时针旋转)后使得结点“9”成了结点“10”的右孩子,这是不符合二叉排序树特性的,此时不能简单的左旋。
根本原因在于结点“7”的BF是-2,而结点“10”的BF是1,也就是说他们俩一正一负,符号并不统一,而前面几次旋转,无论是左旋还是右旋,最小不平衡子树的根结点与它的子结点符号都是相同的,这就是不能直接旋转的原因。
既然是符号不统一,那就先旋转到符号统一后再说,于是先对结点“9”和结点“10”进行右旋,使得结点“10”成为结点“9”的右子树,此时结点“9”的BF为-1,就与结点“7”的BF值符号统一了,如图2-8所示。
此时结点“3”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“2”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“1”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“4”的BF是-2(左孩子2 - 右孩子4 = -2)
此时结点“5”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“6”的BF是-2(左孩子1 - 右孩子3 = -2)
此时结点“7”的BF是-2(左孩子0 - 右孩子2 = -2)
此时结点“10”的BF是1(左孩子1 - 右孩子0 = 1)
此时结点“9”的BF是0 (左孩子0 - 右孩子0 = 0)
再以结点“7”为最小不平衡子树进行左旋,旋转后结点“7”成了结点“9”的左孩子,整个树又达到了平衡,如图2-9所示。
此时结点“3”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“2”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“1”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“4”的BF是-2(左孩子2 - 右孩子4 = -2)
此时结点“5”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“6”的BF是-2(左孩子1 - 右孩子3 = -2)
此时结点“7”的BF是-2(左孩子0 - 右孩子2 = -2)
此时结点“10”的BF是0(左孩子0 - 右孩子0 = 0)
此时结点“9”的BF是-1(左孩子0 - 右孩子1 = -1)
10、第十个关键字是“8”,关键字“8”比关键字“7”大,所以成为“7”的右孩子,此时关键字“6”的BF不在平衡因子的取值范围内,此时要对最小不平衡子树(7、8、9、10)进行旋转,此时结点“6”的BF是-2,而结点“9”的BF是1,也就是说他们俩一正一负,符号并不统一,同上还是先旋转到符号统一后再说,于是先对最小不平衡子树(7、8、9、10)进行右旋,使得结点“9”成为结点“7”的子树,结点“8”成为结点“9”的左孩子,此时结点“7”的BF为-2,就与结点“6”的BF值符号统一了,如图2-10所示。
此时结点“3”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“2”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“1”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“4”的BF是-2(左孩子2 - 右孩子4 = -2)
此时结点“5”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“6”的BF是-2(左孩子1 - 右孩子3 = -2)
此时结点“7”的BF是-1(左孩子0 - 右孩子1 = -1)
此时结点“10”的BF是0(左孩子0 - 右孩子0 = 0)
此时结点“9”的BF是1 (左孩子2 - 右孩子1 = 1)
此时结点“8”的BF是0 (左孩子0 - 右孩子0 = 0)
再以结点“7”为最小不平衡子树进行左旋,旋转后结点“7”成了根结点,结点“6”成了结点“7”的左孩子,整个树又达到了平衡,如图2-11所示。
此时结点“3”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“2”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“1”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“4”的BF是-2(左孩子2 - 右孩子4 = -2)
此时结点“5”的BF是0 (左孩子0 - 右孩子0 = 0)
此时结点“6”的BF是-2(左孩子1 - 右孩子3 = -2)
此时结点“7”的BF是-2(左孩子0 - 右孩子2 = -2)
此时结点“10”的BF是0(左孩子0 - 右孩子0 = 0)
此时结点“9”的BF是0 (左孩子1 - 右孩子1 = 0)
此时结点“8”的BF是0 (左孩子0 - 右孩子0 = 0)
3 总结
通过上述这个例子会发现,当最小不平衡子树根结点的平衡因子BF大于1时,就右旋,小于-1时,就左旋,插入结点后,最小不平衡子树的BF与它的子树的BF符号相反时,就需要对结点先进行一次旋转以使得符号相同后,再反向旋转一次才能够完成平衡操作,其中先进行一次旋转是以子树的正负决定的旋转方向,例如结点“9”的操作,先进行右旋是因为结点“10”的BF是“1”,再例如结点“8”的操作,先进行右旋是因为结点“9”的BF是“1”。
------以上过程参考《大话数据结构》中“平衡二叉树”内容。