golang 手擼 平衡二叉樹
樹是一種計算機數據結構中非常常用的一種結構,其中就包含了:平衡二叉樹,這種樹是一種特殊的二叉查找樹(二叉查找樹也就是,右孩子大于其父結點,左孩子小于其父結點的樹),但是簡單的二叉查找樹存在的問題就是不平衡,最差的查找效率為O(n),故就有人發明了一種平衡的額二叉查找樹。
特點
- 平衡二叉樹是一種二叉查找樹
- 每個結點的左子樹的高度減去右子樹的高度的絕對值不超過1
- 空樹和左右子樹都是平衡二叉樹
- 相比紅黑樹,平衡二叉樹比較適用于沒有刪除的情況
平衡因子
平衡二叉樹是在二叉查查找樹的基礎上進行構建了,為了維持平衡二叉樹的平衡,那么就需要一種機制來判斷平衡二叉樹是否是平衡的。這種機制就叫做平衡因子。
平衡二叉樹的每個結點都會維持一個值,這個值就是平衡因子,這個平衡因子就是這個結點的左子樹的高度減去右子樹的高度得到的值。如果這個平衡因子的值的絕對值大于1了,說明這個樹就不平衡了,那么就需要調整樹的結構了。
我們可以從如上這個這個圖中看的到:每個結點都維持了一個值,比如左邊的AVL 樹根結點的值為-1,這個-1是怎么來的呢,就是結點3
的左子樹的高度為 2, 右子樹的高度為 3, 左子樹的高度減去右子樹的高度就得到這個結點的平衡因子。
如果某個結點的平衡因子的絕對值大于了 1 ,那么就說明這個平衡二叉樹不平衡了,就需要調整平衡二叉樹的結構。
旋轉
由于AVL樹需要做到平衡,所以每次插入葉子節點,如果發現不平衡,都需要進行旋轉以保持平衡。
找到了要給別人做的一個旋轉圖例,可以參考的看下:
我們來總結下以上圖片中出現的這幾種情況:
-
三個結點的單旋轉
- 我們可以看到上途中結點3的平衡因子為2了,這樣就不平衡橫了,那么就需要進行一個對結點3的順時針旋轉,旋轉完的結果就是上圖中的右圖。
-
三個結點的雙旋轉
- 針對這種情況其實就是上面一種情況的特殊版本:我們要先把這種情況先轉化為:
三個結點的單旋轉
這種情況。 - 首先對結點11進行一個逆時針的旋轉,然后就變為了:
三個結點的單旋轉
。 - 然后再像;
三個結點的單旋轉
一樣的對結點3進行右旋轉。
什么時候怎樣旋轉呢?
-
我們插入一個結點后,某個結點p的平衡因子由原來的1變成了2。
- 那就可能是這兩種情況:
-
新插入的結點插入到了結點p的左子樹的左孩子下
-
golang 代碼實現
// 順時針旋轉,右旋 func (avlNode *AVLNode) RightRotate() *AVLNode { headNode := avlNode.Left avlNode.Left = headNode.Right headNode.Right = avlNode // 更新旋轉后結點的高度 avlNode.height = Max(avlNode.Left.GetHeight(), avlNode.Right.GetHeight())+1 headNode.height = Max(headNode.Left.GetHeight(), headNode.Right.GetHeight())+1 return headNode }
-
新插入的結點插入到了結點p的左子樹的右孩子下
從以上例子中我們就可以發現,順時針旋轉和逆時針旋轉的規律所在
-
golang 代碼實現
// 先逆時針旋轉再順時針旋轉,先左旋,在右旋 func (avlNode *AVLNode) LeftThenRightRotate() *AVLNode { // 先把左孩子結點進行左旋 avlNode.Left = avlNode.Left.LeftRotate() // 然后把自己右旋 return avlNode.RightRotate() }
- 從以上的三張圖中我們就可以發現這樣的一個容易忽略的問題
- 假設有一個結點,這個結點是P,我們在逆時針旋轉一個結點的時候需要把結點P的右孩子的左子樹移動為結點P的右子樹
- 假設有一個結點,這個結點是P,我們在順時針旋轉時,需要把P結點的左孩子的右子樹移動為P結點的左子樹
-
我們插入一個結點后,某個結點p的平衡因子由原來的-1變成了-2。
- 那就可能是這兩種情況:
-
新插入的結點插入到了結點p的右子樹的右孩子下
- 那就應該進行對結點p的逆時針旋轉
- 旋轉過程可以參考上圖
- golang 實現
// 逆時針旋轉,左旋 func (avlNode *AVLNode) LeftRotate() *AVLNode { headNode := avlNode.Right avlNode.Right = headNode.Left headNode.Left = avlNode // 更新結點的高度 // 這里應該注意的俄式應該先更新avlNode 的高度,因為headNode結點在avlNode結點的上面 // headNode計算高度的時候要根據avlNode的高度來計算 avlNode.height = Max(avlNode.Left.GetHeight(), avlNode.Right.GetHeight())+1 headNode.height = Max(headNode.Left.GetHeight(), headNode.Right.GetHeight())+1 return headNode }
-
新插入的結點插入到了結點p的右子樹的左孩子下
- 那就因該先對結點p的右孩子進行順時針旋轉
- 然后在對結點p進行逆時針旋轉
- 旋轉過程可以參考上圖
- golang 實現
// 先順時針旋轉再逆時針旋轉,先右旋,再左旋 func (avlNode *AVLNode) RightThenLeftRotate() *AVLNode { // 先把右孩子進行右旋 avlNode.Right = avlNode.Right.RightRotate() // 然后把自己右旋 return avlNode.LeftRotate() }
上面我們展示了針對具體的四種情況,是怎么怎么旋轉的,但是我們要寫一個函數用來判斷這四種情況:
// 調整AVL樹的平衡
func (avlNode *AVLNode) adjust() *AVLNode {
// 如果右子樹的高度比左子樹的高度大于2
if avlNode.Right.GetHeight() - avlNode.Left.GetHeight() == 2 {
// 如果 avlNode.Right 的右子樹的高度比avlNode.Right的左子樹高度大
// 直接對avlNode進行左旋轉
// 否則先對 avlNode.Right進行右旋轉然后再對avlNode進行左旋轉
if avlNode.Right.Right.GetHeight() > avlNode.Right.Left.GetHeight() {
avlNode = avlNode.LeftRotate()
}else{
avlNode = avlNode.RightThenLeftRotate()
}
// 如果左子樹的高度比右子樹的高度大2
}else if avlNode.Right.GetHeight() - avlNode.Left.GetHeight() == -2 {
// 如果avlNode.Left的左子樹高度大于avlNode.Left的右子樹高度
// 那么就直接對avlNode進行右旋
// 否則先對avlNode.Left進行左旋,然后對avlNode進行右旋
if avlNode.Left.Left.GetHeight() > avlNode.Left.Right.GetHeight() {
avlNode = avlNode.RightRotate()
}else {
avlNode = avlNode.LeftThenRightRotate()
}
}
return avlNode
}
golang 實現
AVLNode
type AVLNode struct {
Left, Right *AVLNode // 表示指向左孩子和右孩子
Data interface{} // 結點存儲數據
height int // 記錄這個結點此時的高度
}
AVL樹還需要一些簡單的獲取和設置結點性質的方法
注意:每次NewAVLNode的時候height一定是1,因為一個簡單的高度最低就是1
// 定義comparer 指針類型
// 用來比較兩個結點中Data的大小
type comparator func(a, b interface{}) int
// compare 指針
var compare comparator
// 新建一個結點
func NewAVLNode(data interface{}) *AVLNode {
return &AVLNode{
Left: nil,
Right: nil,
Data: data,
height: 1,
}
}
// 新建AVL 樹
func NewAVLTree(data interface{}, myfunc comparator) (*AVLNode, error) {
if data == nil && myfunc == nil {
return nil, errors.New("不能為空")
}
compare = myfunc
return NewAVLNode(data), nil
}
// 獲取結點數據
func (avlNode *AVLNode) GetData() interface{} {
return avlNode.Data
}
// 設置結點數據
func (avlNode *AVLNode) SetData(data interface{}) {
if avlNode == nil {
return
}
avlNode.Data = data
}
// 獲取結點的右孩子結點
func (avlNode *AVLNode) GetRight() *AVLNode {
if avlNode == nil {
return nil
}
return avlNode.Right
}
// 獲取結點的左孩子結點
func (avlNode *AVLNode) GetLeft() *AVLNode {
if avlNode == nil {
return nil
}
return avlNode.Left
}
// 獲取結點的高度
func (avlNode *AVLNode) GetHeight() int {
if avlNode == nil {
return 0
}
return avlNode.height
}
//比較兩個子樹高度的大小
func Max(a, b int) int {
if a >= b {
return a
} else {
return b
}
}
查找結點
查找指定結點
// 查找指定值
func (avlNode *AVLNode) Find(data interface{}) *AVLNode {
var finded *AVLNode
// 調用比較函數比較兩個結點的指的大小
switch compare(data, avlNode.Data) {
case -1:
finded = avlNode.Left.Find(data)
case 1:
finded = avlNode.Right.Find(data)
case 0:
return avlNode
}
return finded
}
查找最大結點
// 查找最大值
func (avlNode *AVLNode) FindMax() *AVLNode {
var finded *AVLNode
if avlNode.Right != nil {
finded = avlNode.Right.FindMin()
} else {
finded = avlNode
}
return finded
}
查找最小結點
// 查找最小值
func (avlNode *AVLNode) FindMin() *AVLNode { // 遞歸寫法
var finded *AVLNode
if avlNode.Left != nil {
finded = avlNode.Left.FindMin()
} else {
finded = avlNode
}
return finded
}
插入和刪除
插入
// 插入數據
// 因為沒有定義 結點的parent指針,所以你插入數據就只能遞歸的插入,因為我要調整樹的平衡和高度
func (avlNode *AVLNode) Insert(value interface{}) *AVLNode {
if avlNode == nil {
return NewAVLNode(value)
}
switch compare(value, avlNode.Data) {
case -1:
// 如果value 小于 avlNode.Data 那么就在avlNode的左子樹上去插入value
avlNode.Left = avlNode.Left.Insert(value)
avlNode = avlNode.adjust() // 自動調整平衡
case 1:
avlNode.Right = avlNode.Right.Insert(value)
avlNode = avlNode.adjust()
case 0:
fmt.Print("數據已經存在")
}
// 修改結點的高度
avlNode.height = Max(avlNode.Left.GetHeight(), avlNode.Right.GetHeight()) + 1
return avlNode
}
插入數據的時候我們應該注意的是,我們要針對插入點相關的父結點,都要判斷是否平衡,然后就行平衡的調整。
刪除
// 刪除數據
func (avlNode *AVLNode) Delete(value interface{}) *AVLNode {
// 沒有找到匹配的數據
if avlNode == nil {
//fmt.Println("不存在", value)
return nil
}
switch compare(value, avlNode.Data) {
case 1:
avlNode.Right = avlNode.Right.Delete(value)
case -1:
avlNode.Left = avlNode.Left.Delete(value)
case 0:
// 找到數據,刪除結點
if avlNode.Left != nil && avlNode.Right != nil { // 結點有左孩子和右孩子
avlNode.Data = avlNode.Right.FindMin().Data
avlNode.Right = avlNode.Right.Delete(avlNode.Data)
} else if avlNode.Left != nil { // 結點只有左孩子,無右孩子
avlNode = avlNode.Left
} else { // 結點只有右孩子或者無孩子
avlNode = avlNode.Right
}
}
// 自動調整平衡, 如果avlNode!=nil說明執行了對avlNode 的某個子樹執行了刪除結點,那么就需要重新調整樹的平衡
if avlNode != nil {
avlNode.height = Max(avlNode.Left.GetHeight(), avlNode.Right.GetHeight()) + 1
avlNode = avlNode.adjust() // 自動平衡
}
return avlNode
}