二叉树

本着构建一颗二叉树的目的是为了更快的搜索数据,因此我们在构建二叉树之初就应该构建一颗有序的二叉树。

首先,我们来构建一颗二叉查找树。

二叉查找树(Binary Search Tree)是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。

1. 构建二叉查找树,打印构建结果:
package main

import "fmt"

type binarysearchtree struct {
   value int
   left, right *binarysearchtree
}
func Newbinarysearchtree(rootvaue int) *binarysearchtree {
   return &binarysearchtree{value: rootvalue}
}
func (t *binarysearchtree) Insert(value int) *binarysearchtree {
   if t == nil {
      t = Newbinarysearchtree(value)
      return t
   }
   if value < t.value {
      t.left = t.left.Insert(value)
   }else {
      t.right = t.right.Insert(value)
   }
   return t
}
func (t *binarysearchtree) Getall() []int {
   value := []int{}
   return Appendvalues(value, t)
}
func Appendvalues(values []int, t *binarysearchtree) []int {
   if t != nil {
      values = Appendvalues(values, t.left)
      values = append(values, t.value)
      values = Appendvalues(values, t.right)
   }
   return values
}
func main() {
   binaryTree := Newbinarysearchtree(50)
   binaryTree.Insert(20)
   binaryTree.Insert(10)
   binaryTree.Insert(100)
   binaryTree.Insert(60)
   binaryTree.Insert(70)
   binaryTree.Insert(5)
   binaryTree.Insert(35)
   binaryTree.Insert(40)
   fmt.Println(binaryTree.Getall())
}
输出结果为:
 
2. 查看是否包含某一个元素
func (t *binarysearchtree) Contains(value int) bool {
   if t == nil {
      return false
   }
   v := t.compareTo(value)
   if v < 0 {
      return t.left.Contains(value)
   }else if v > 0 {
      return t.right.Contains(value)
   }else {
      return true
   }
}
func (t *binarysearchtree)compareTo(value int) int {
   return value-t.value
}

输入35,打印查找结果为:

3. 从二叉搜索树中移除某个元素
func (t *binarysearchtree) Remove(value int) *binarysearchtree {
   if t == nil {
      return t
   }
   compareresult := t.compareTo(value)
   if compareresult < 0 {
      t.left = t.left.Remove(value)
   }else if compareresult > 0 {
      t.right = t.right.Remove(value)
   }else if t.left != nil && t.right != nil {
      t.value = t.right.Findmin()
      t.right = t.right.Remove(t.value)
   }else if t.left != nil {
      t = t.left
   }else {
      t = t.right
   }
   return t
}
输入35,打印删除结果为:

4. 查找二叉搜索树中的最大值
func (t *binarysearchtree) Findmax() int {
   if t == nil {
      fmt.Println("tree is empty")
      return -1
   }
   if t.right == nil {
      return t.value
   }else {
      return t.right.Findmax()
   }
}

打印结果:

但是当数据接近有序数据时,二叉查找树的查找效率和顺序查找相当,这是无法忍受的。造成这种情况的主要原因是二叉查找树左右子树高度差太大。基于此,AVL树就诞生了,AVL树是带有平衡条件的二叉查找树,每个节点的左子树和右子树的高度最多差1。

当一个二叉查找树中插入一个数据时,使其成为不平衡二叉树一共有4中可能:

1. 对root节点的左儿子的左子树进行一次插入

2.对root节点的左儿子的右子树进行一次插入

3.对root节点的右儿子的左子树进行一次插入

4.对root节点的右儿子的右子树进行一次插入

对于以上出现的四种情况,使用单旋转和双旋转两种方式来解决。

1. 单旋转来解决1和4情况的。

如图此时k1为不平衡点,左树-右树=2,因此我们需要将右树加深,将左树变浅。这时,我们使用k2代替k1的位置,因为k1>k2,所以将k1变为k2右节点,Y大于k2小于k1所以Y变为k1的左节点 。变平衡后为:

2. 使用双旋转来解决2和3两种情况。

遇到2和3情况使用单旋转已经做不到了。这个时候我们需要将k3往上移,我们把k3可以看成新的根,当我们把k3移到k1的位置,此刻我们应该如何变化呢?k1<k3所以k3的左孩子为k1,k2>k3所以k3的左孩子为k2,B大于k1小于k3,所以B为k1的右子树,C大于3小于k2,所以C为k2的左子树。双旋转后:

构建平衡二叉树的AVL代码为:
package main
import (
   "fmt"
)
type avlTreeNode struct {
   key   int
   high  int
   left  *avlTreeNode
   right *avlTreeNode
}
func NewAVLTreeNode(value int) *avlTreeNode{
   return &avlTreeNode{key:value}
}
func highTree(p *avlTreeNode) int {
   if p == nil {
      return -1
   } else {
      return p.high
   }
}
func max(a, b int) int {
   if a > b {
      return a
   } else {
      return b
   }
}
// look LL
func left_left_rotation(k *avlTreeNode) *avlTreeNode {
   var kl *avlTreeNode
   kl = k.left
   k.left = kl.right
   kl.right = k
   k.high = max(highTree(k.left), highTree(k.right)) + 1
   kl.high = max(highTree(kl.left), k.high) + 1
   return kl
}
//look RR
func right_right_rotation(k *avlTreeNode) *avlTreeNode {
   var kr *avlTreeNode
   kr = k.right
   k.right = kr.left
   kr.left = k
   k.high = max(highTree(k.left), highTree(k.right)) + 1
   kr.high = max(k.high, highTree(kr.right)) + 1
   return kr
}
func left_righ_rotation(k *avlTreeNode) *avlTreeNode {
   k.left = right_right_rotation(k.left)
   return left_left_rotation(k)
}
func right_left_rotation(k *avlTreeNode) *avlTreeNode {
   k.right = left_left_rotation(k.right)
   return right_right_rotation(k)
}
//insert to avl
func avl_insert(avl *avlTreeNode, key int) *avlTreeNode {
   if avl == nil {
      avl = NewAVLTreeNode(key)
   } else if key < avl.key {
      avl.left = avl_insert(avl.left, key)
      if highTree(avl.left)-highTree(avl.right) == 2 {
         if key < avl.left.key { //LL
            avl = left_left_rotation(avl)
         } else { // LR
            avl = left_righ_rotation(avl)
         }
      }
   } else if key > avl.key {
      avl.right = avl_insert(avl.right, key)
      if (highTree(avl.right) - highTree(avl.left)) == 2 {
         if key < avl.right.key { // RL
            avl = right_left_rotation(avl)
         } else {
            fmt.Println("right right", key)
            avl = right_right_rotation(avl)
         }
      }
   } else if key == avl.key {
      fmt.Println("the key", key, "has existed!")
   }
   //notice: update high(may be this insert no rotation, so you should update high)
   avl.high = max(highTree(avl.left), highTree(avl.right)) + 1
   return avl
}
//display avl tree  key 
func display(avl *avlTreeNode) []int{
   return appendValues([]int{},avl)
}
func appendValues(values []int, avl *avlTreeNode)  []int{
   if avl != nil {
      values = appendValues(values,avl.left)
      values = append(values,avl.key)
      values = appendValues(values,avl.right)
   }
   return values
}
func main() {
   data := []int{10, 20, 30, 40, 50, 60}
   root := NewAVLTreeNode(5)
   for _, value := range data {
      root = avl_insert(root, value)
   }

   fmt.Println(display(root))
}

当将数值为10, 20, 30, 40, 50, 60的节点插入到平衡二叉树中后得到的运行结果为:

二叉平衡树的严格平衡策略以牺牲建立查找结构(插入,删除操作)的代价,换来了稳定的O(logN) 的查找时间复杂度。能否找到一种折中的办法即不牺牲太大的建立查找结构的代价,也能保证稳定高效的查找效率呢?-----------------红黑树 

红黑树并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。红黑树能够以O(log2 n) 的时间复杂度进行搜索、插入、删除操作。首先红黑树是不符合AVL树的平衡条件的,即每个节点的左子树和右子树的高度最多差1的二叉查找树。但是提出了为节点增加颜色,红黑是用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,而AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多。所以红黑树的插入效率更高!

红黑树是满足如下条件的二叉查找树(binary search tree):

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点必须是黑色
  3. 红色节点不能连续(也即是,红色节点的孩子和父亲都不能是红色)。
  4. 对于每个节点,从该点至null(树尾端)的任何路径,都含有相同个数的黑色节点。

在树的结构发生改变时(插入或者删除操作),往往会破坏上述条件3或条件4,需要通过调整使得查找树重新满足红黑树的条件。

当查找树的结构发生改变时,红黑树的条件可能被破坏,需要通过调整使得查找树重新满足红黑树的条件。调整可以分为两类:一类是颜色调整,即改变某个节点的颜色;另一类是结构调整,集改变检索树的结构关系。结构调整过程包含两个基本操作:左旋(Rotate Left),右旋(RotateRight)

左旋的过程是将x的右子树绕x逆时针旋转,使得x的右子树成为x的父亲,同时修改相关节点的引用。旋转之后,二叉查找树的属性仍然满足。右旋同理。

红黑树结构的建立

红黑树结构体: 

节点有红黑两色,我们先定义2个常量。布尔型足够:

const (
    // RED 红树设为true
    RED bool = true
    // BLACK 黑树设为false
    BLACK bool = false
)

       然后是节点结构,包括树共有的特性:节点的值,指向父节点、左右儿子节点的3个指针;还有红黑树特有的:颜色。下面是树的结构:

// RBNode 红黑树
type RBNode struct {
    value               int64
    color               bool
    left, right, parent *RBNode
}

树的结构只包含一个根节点Root,代码结构如下:

type RBTree struct {
    root *RBNode
}

二叉树的旋转代码如下:

// rotate() true左旋/false右旋
// 若有根节点变动则返回根节点
func (rbnode *RBNode) rotate(isRotateLeft bool) (*RBNode, error) {
    var root *RBNode
    if rbnode == nil {
        return root, nil
    }
    if !isRotateLeft && rbnode.left == nil {
        return root, errors.New("右旋左节点不能为空")
    } else if isRotateLeft && rbnode.right == nil {
        return root, errors.New("左旋右节点不能为空")
    }

    parent := rbnode.parent
    var isleft bool
    if parent != nil {
        isleft = parent.left == rbnode
    }
    if isRotateLeft {
        grandson := rbnode.right.left
        rbnode.right.left = rbnode
        rbnode.parent = rbnode.right
        rbnode.right = grandson
    } else {
        grandson := rbnode.left.right
        rbnode.left.right = rbnode
        rbnode.parent = rbnode.left
        rbnode.left = grandson
    }
    // 判断是否换了根节点
    if parent == nil {
        rbnode.parent.parent = nil
        root = rbnode.parent
    } else {
        if isleft {
            parent.left = rbnode.parent
        } else {
            parent.right = rbnode.parent
        }
        rbnode.parent.parent = parent
    }
    return root, nil
}

红黑树的插入操作主要分为两个部分,首先是一个查找树的插入,然后是分治法进行树的变化以符合红黑树特征。

节点的插入主要检查以下几种情况: 
       1. 要检查的节点没有父节点,意为此节点为root,则设置此节点的颜色为黑色(一开始提到过,插入时的节点初始时都是红色),直接返回,若不是根节点,则进行情况2的检查。 
       2. 如果添加节点的父节点是黑色,那就省事儿多了。插入的是红色,不影响黑色数量,且由于父节点是黑色,不会出现父子节点都是红色的情况。 
       3. 若添加点的父节点也是红色,那就得考虑考虑了,这里应用了分治法。

func (rbtree *RBTree) insertNode(pnode *RBNode, data int64) {
    if pnode.value >= data {
        // 插入数据不大于父节点,插入左节点
        if pnode.left != nil {
            rbtree.insertNode(pnode.left, data)
        } else {
            tmpnode := NewRBNode(data)
            tmpnode.parent = pnode
            pnode.left = tmpnode
            rbtree.insertCheck(tmpnode)
        }
    } else {
        // 插入数据大于父节点,插入右节点
        if pnode.right != nil {
            rbtree.insertNode(pnode.right, data)
        } else {
            tmpnode := NewRBNode(data)
            tmpnode.parent = pnode
            pnode.right = tmpnode
            rbtree.insertCheck(tmpnode)
        }
    }
}
func (rbtree *RBTree) insertCheck(node *RBNode) {
    if node.parent == nil {
        // 检查1:若插入节点没有父节点,则该节点为root
        rbtree.root = node
        // 设置根节点为black
        rbtree.root.color = BLACK
        return
    }

    // 父节点是黑色的话直接添加,红色则进行处理
    if node.parent.color == RED {
        if node.getUncle() != nil && node.getUncle().color == RED {
            // 父节点及叔父节点都是红色,则转为黑色
            node.parent.color = BLACK
            node.getUncle().color = BLACK
            // 祖父节点改成红色
            node.getGrandParent().color = RED
            // 递归处理
            rbtree.insertCheck(node.getGrandParent())
        } else {
            // 父节点红色,父节点的兄弟节点不存在或为黑色
            isleft := node == node.parent.left
            isparentleft := node.parent == node.getGrandParent().left
            if !isleft && isparentleft {
                rbtree.rotateLeft(node.parent)
                rbtree.rotateRight(node.parent)

                node.color = BLACK
                node.left.color = RED
                node.right.color = RED
            } else if isleft && !isparentleft {
                rbtree.rotateRight(node.parent)
                rbtree.rotateLeft(node.parent)

                node.color = BLACK
                node.left.color = RED
                node.right.color = RED
            } else if isleft && isparentleft {
                node.parent.color = BLACK
                node.getGrandParent().color = RED
                rbtree.rotateRight(node.getGrandParent())
            } else if !isleft && !isparentleft {
                node.parent.color = BLACK
                node.getGrandParent().color = RED
                rbtree.rotateLeft(node.getGrandParent())
            }
        }
    }
}

 

红黑树多用在内部排序,在数据较小,可以完全放到内存中时,红黑树的时间复杂度相比于其他算法比较优越,但当数据量较大,外存中占主要部分时,红黑树这种结构并不适用,在大规模数据存储的时候,红黑树往往出现由于树的深度过大而造成磁盘IO读写过于频繁,进而导致效率低下的情况。为什么会出现这样的情况,我们知道要获取磁盘上数据,必须先通过磁盘移动臂移动到数据所在的柱面,然后找到指定盘面,接着旋转盘面找到数据所在的磁道,最后对数据进行读写。磁盘IO代价主要花费在查找所需的柱面上,树的深度过大会造成磁盘IO频繁读写。根据磁盘查找存取的次数往往由树的高度所决定,所以,只要我们通过某种较好的树结构减少树的结构尽量减少树的高度,B树可以有多个子女,从几十到上千,可以降低树的高度。B树因其读磁盘次数少,而具有更快的速度。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值