0%

平衡二叉树-树的旋转

树结构

  1. 树结构是由一个父节点以及若干个子节点,然后子节点又是其他子节点的父节点,由此而形成的一种结构即是树。其中节点的子节点的子节点叫做该节点的孙节点。如下所示:

  2. 二叉树(Binary Tree, BT)

    二叉树是树结构的应用形式之一,二叉树每个节点至多有两个子节点,如上面第三个树结构所示,位于左边的子节点叫做左孩子或者左子节点,位于右边的叫做右孩子或者右子节点。

  3. 二叉搜索(查找)树(Binary Search Tree, BST)

    二叉搜索树是二叉树的应用之一,在一棵二叉搜索树中,父节点的值总是小于(或者大于)左孩子,而右孩子的值总是大于(或者小于)父节点,由此便构成了一棵有序的树结构。如下图所示:

  4. 平衡二叉树(AVL)

    二叉搜索树是一棵有序的树,但是大多数情况下,往二叉搜索树中插入节点时,可能存在的情况是插入的节点始终位于一个分支上,如下图所示:

    这样就出现了一种不尽如人意的情况,就是在一棵二叉搜索树中,某一个分支节点很少,而另一个分支上节点却很多,导致在查找、插入、删除等操作上效率很低。

    在一棵二叉搜索树中,对于某一个节点,如果该节点左子树和右子树的高度差的绝对值超过了abs(h) > 1,则称该树为非平衡二叉搜索树。为了改善这种情况,便出现了平衡二叉树,顾名思义,平衡二叉树任意一个节点的左子树和右子树高度差的绝对值都abs(h)<=1。平衡二叉树是在二叉搜索树的基础上,增加了平衡二叉树的操作,使得二叉搜索树是一棵平衡树。如下图为一棵平衡二叉树:

平衡查找树的平衡

前面已经提到什么是平衡二叉树,那么怎么样形成一棵平衡的二叉树呢?

权威们给出的答案是旋转,即通过对二叉树进行旋转来改变树的结构并且不改变节点值的顺序,从而得到一棵平衡的二叉树。下面介绍树的旋转,树的旋转分为左旋转、右旋转以及左右旋转,右左旋转。

因为树的节点个数变化为1、2、3…n,所以当节点总数小于3时一棵二叉搜索树一定是平衡的,如下图:

此时左子树的高度与右子树的高度差的绝对值为1,所以是平衡的(在下文中提到的高度差都是左子树高度减去右子树高度)。但是随着节点的插入,有可能变成不平衡的了。以下为需要旋转操作进行平衡二叉树的情况,均是针对最少节点数的情况,即需要旋转操作的最小子树。

注: 下文提到的高度差均为左子树减去右子树的高度差的绝对值,同时本文中的平衡二叉树为左节点小于父节点,父节点小于右节点

  1. 左旋转

    • 在右子树添加节点造成不平衡。root只有右孩子的情况,以root的右孩子为中心,向左(逆时针)旋转root节点,旋转结果为root节点变为root右孩子的左孩子,如下图:

      在右子树添加节点(图中的16),造成不平衡。

    • 在右子树添加节点造成不平衡,其中root同时有左右子树,左子树只有一个节点,右孩子只有一个右子节点,添加一个节点(下图中的17)后造成不平衡树,此时可以看到,root的右子树不平衡,此时按照第一种旋转方式可以将右子树旋转平衡,进而使整棵树平衡,如下图:

    • 在右子树添加节点造成不平衡,其中root只有一个左孩子,root的右孩子同时存在左右孩子,如下图:

    从上面可以看出,如果root右子树比左子树高2,并且右子树的右子树比右子树的左子树高,则执行左旋转,以下是左旋转的代码(Golang):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    type Element interface {
    Value() interface{}
    Compare(Element) int //相等返回0,小于返回负数,大于返回正数
    }

    type AVLNode struct {
    Data Element //存放的元素
    Height int //存放节点的高度
    Left *AVLNode //左子树
    Right *AVLNode //右子树
    }

    func (avl *AVLNode) Max() *AVLNode {
    node := avl
    for node.Right != nil {
    node = node.Right
    }
    return node
    }

    func getHeight(avl *AVLNode) int {
    if avl != nil {
    return avl.Height
    }
    return 0
    }

    func maxHeight(h1, h2 int) int {
    if h1 > h2 {
    return h1
    }
    return h2
    }

    func leftRotate(avl *AVLNode) *AVLNode {
    if avl == nil {
    return nil
    }
    node := avl.Right
    avl.Right = node.Left
    node.Left = avl

    avl.Height = maxHeight(getHeight(avl.Left), getHeight(avl.Right)) + 1
    node.Height = maxHeight(getHeight(node.Left), getHeight(node.Right)) + 1
    return node
    }

  2. 右旋转

    • 在左子树添加节点造成不平衡, root没有右孩子,同时左孩子只有左孩子一个节点, 此时以root的左孩子为中心,进行右旋转(顺时针旋转), 将root左孩子提升为root,root降为左孩子的右孩子,如下图:

    • 在左子树添加节点造成不平衡, root同时包含左右孩子,右孩子没有子节点,左孩子只有一个左孩子节点,此时root的左子树为不平衡树,按照上面的方式对左子树进行右旋转得到平衡树,如下图:

    • 在左子树添加节点造成不平衡, root只有一个右孩子, 左孩子同时有左右孩子, 在左孩子的左孩子下添加一个节点, 如下图:

    从上面可以看出, 如果root左子树比右子树高2,并且左子树的左子树比左子树的右子树高,则执行右旋转,以下是右旋转的代码(Golang):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    func rightRotate(avl *AVLNode) *AVLNode {
    if avl == nil {
    return nil
    }
    node := avl.Left //左子树
    avl.Left = node.Right //左子树的右子树变为左子树
    node.Right = avl //avl降为左子树的右子树

    //更新节点高度
    avl.Height = maxHeight(getHeight(avl.Left), getHeight(avl.Right)) + 1
    node.Height = maxHeight(getHeight(node.Left), getHeight(node.Right)) + 1
    return node
    }

  3. 左右旋转

    左右旋转是指先执行左旋转再执行右旋转。

    • 在左子树添加节点造成不平衡,root只有左子树,且左子树只有一个节点,如下图:

    • 在左子树添加节点造成不平衡,root同时有左右孩子,root的左孩子同时有左右孩子,在root的左孩子的右孩子上添加左节点造成不平衡,如下图:

    • 在左子树添加节点造成不平衡,root同时有左右孩子,root的左孩子同时有左右孩子,在root的左孩子的左孩子上添加右节点造成不平衡,如下图:

    从上面可以看出, 如果root左子树比右子树高2,并且左子树的右子树比左子树的左子树高2,则先执行左旋转,再执行右旋转,以下是左右旋转的代码(Golang):

    1
    2
    3
    4
    5
    //先将左孩子左旋转,自己再右旋转
    func (avl *AVLNode) leftRightRotate() *AVLNode {
    avl.Left = avl.Left.leftRotate()
    return avl.rightRotate()
    }
  4. 右左旋转

    右左旋转是指先执行右旋转再执行左旋转。

    • 在右子树添加节点造成不平衡, root只有右子树,且右子树只有一个节点,如下图:

    • 在右子树添加节点造成不平衡, root同时有左右孩子,root的右孩子同时有左右孩子,在root的右孩子的左孩子上添加右节点,如下图:

    • 在右子树添加节点造成不平衡, root同时有左右孩子,root的右孩子同时有左右孩子,在root的右孩子的左孩子上添加左节点,如下图:

    从上面可以看出, 如果root右子树比左子树高2, 并且右子树的右子树比右子树的左子树矮, 则执行右旋转后再执行左旋转,以下是左右旋转的代码(Golang):

    1
    2
    3
    4
    5
    //先将右孩子右旋转,然后自己右旋转
    func (avl *AVLNode) rightLeftRotate() *AVLNode {
    avl.Right = avl.Right.rightRotate()
    return avl.leftRotate()
    }

综上可以得出平衡二叉查找树的函数为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//调整树为二叉平衡树
func balance(avl *AVLNode) *AVLNode {
if avl == nil {
return nil
}
if getHeight(avl.Right)-getHeight(avl.Left) == 2 {
if getHeight(avl.Right.Right) > getHeight(avl.Right.Left) {
avl = avl.leftRotate()
} else {
avl = avl.rightLeftRotate()
}
} else if getHeight(avl.Left)-getHeight(avl.Right) == 2 {
if getHeight(avl.Left.Left) > getHeight(avl.Left.Right) {
avl = avl.rightRotate()
} else {
avl = avl.leftRightRotate()
}
}
return avl
}

插入、删除节点

以上为树的旋转操作,用于平衡二叉查找,对于二叉查找树,每添加或者插入一个节点后均需要执行一次平衡操作。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//添加节点
func (avl *AVLNode) AddNode(data Element) (root *AVLNode, ok bool) {
defer func() {
if ok {
root = balance(avl)
root.Height = maxHeight(getHeight(root.Left), getHeight(root.Right)) + 1
}
}()

var target = &AVLNode{
Data: data,
Height: 1,
Left: nil,
Right: nil,
}

cmpResult := avl.Data.Compare(data)
switch {
case cmpResult > 0:
if avl.Left == nil {
avl.Left = target
ok = true
return
}
avl.Left, ok = avl.Left.AddNode(data)
return
case cmpResult < 0:
if avl.Right == nil {
avl.Right = target
ok = true
return
}
avl.Right, ok = avl.Right.AddNode(data)
return
default:
ok = true
return
}
}

func (avl *AVLNode) RemoveNode(data Element) (root *AVLNode, ok bool) {
defer func() {
if ok && root != nil {
root = balance(root)
root.Height = maxHeight(getHeight(root.Left), getHeight(root.Right)) + 1
}
}()

var temp *AVLNode
cmpResult := avl.Data.Compare(data)
switch {
case cmpResult > 0:
if avl.Left == nil {
return nil, false
}
temp, ok = avl.Left.RemoveNode(data)
if ok {
avl.Left = temp
}
root = avl
return
case cmpResult < 0:
if avl.Right == nil {
return nil, false
}
temp, ok = avl.Right.RemoveNode(data)
if ok {
avl.Right = temp
}
root = avl
return
default:
if avl.Left == nil && avl.Right == nil {
return nil, true
}
if avl.Left == nil {
root, ok = avl.Right, true
return
}
if avl.Right == nil {
root, ok = avl.Left, true
return
}
//将左子树最大节点提升到头节点,并将该最大节点从左子树中删除
avl.Data = avl.Left.Max().Data
avl.Left, ok = avl.Left.RemoveNode(avl.Data)
root = avl
return
}
}

最后

源码跳转

谢谢!