zoukankan      html  css  js  c++  java
  • 平衡二叉树(AVL树)

    1.二叉排序树的时候,树的结构是非常依赖无序序列的顺序,这样会出现极端的情况。

    【如图1】:

      

      这样的一颗二叉排序树就是一颗比较极端的情况。我们在查找时候,效率依赖树的高度,所以不希望这样极端情况出现,而是希望元素比较均匀的分布在根节点两端

    2.什么是二叉平衡树?  

      问题提出:

        能不能有一种方法,使得我们的二叉排序树不依赖无序序列的顺序,也能使得我们得到的二叉排序树是比较均匀的分布。

      引入:

      平衡二叉树(Self-Balancing Binary Search Tree 或 Height-Balanced Binary Search Tree),是一种特殊的二叉排序树,其中每一个结点的左子树和右子树的高度差至多等于1.

      这里的平衡从名字中可以看出,Height-Balanced是高度平衡。

      它或者是一颗空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1.

      若将二叉树上的结点的平衡因子BF(Balance Factor)定义为该节点的左子树的深度减去它的右子树的深度,则平衡二叉树上所有结点的平衡因子只可能是-1、0、1。否则就不是平衡二叉树。

      上图图1中,就不是平衡二叉树。

      以图1来看看各个结点的平衡因子。

    【如下图2】:

      

      如何构成平衡二叉树?

      (1)当最小不平衡树的根结点的平衡因子BF是大于1时就右旋

      (2)当最小不平衡树的根结点的平衡因子BF是小于1时就左旋

      (3)插入结点后,最小不平衡子树的BF与它的子树的BF符号相反时,就需要对结点先进行一次旋转以使得符号相同后,再反向旋转一次才能够完成平衡操作。

      

      

      要能找到最小不平衡树,4是插入结点,与2结点平衡因子绝对值大于1

      

      注意此处的结点2的变化

      

      

      注意9结点的位置出现了BF=1,与它的子树BF相反

      

    可以贴代码了:

    public class BinarySearchTree : IBinaryTree //实现画树接口
    { //成员变量
    private Node _head; //头指针
    private Node[] path = new Node[32]; //记录访问路径上的结点
    private int p; //表示当前访问到的结点在_path上的索引
    INode IBinaryTree.Head //显式接口实现
    {
    get { return (INode)_head; }
    }
    public bool Add(int value) //添加一个元素
    { //如果是空树,则新结点成为二叉排序树的根
    if (_head == null)
    {
    _head = new Node(value);
    _head.BF = 0;
    return true;
    }
    p = 0;
    //prev为上一次访问的结点,current为当前访问结点
    Node prev = null, current = _head;
    while (current != null)
    {
    path[p++] = current; //将路径上的结点插入数组
    //如果插入值已存在,则插入失败
    if (current.Data == value)
    {
    return false;
    }
    prev = current;
    //当插入值小于当前结点,则继续访问左子树,否则访问右子树
    current = (value <</SPAN> prev.Data) ? prev.Left : prev.Right;
    }
    current = new Node(value); //创建新结点
    current.BF = 0;
    if (value <</SPAN> prev.Data) //如果插入值小于双亲结点的值
    {
    prev.Left = current; //成为左孩子
    }
    else //如果插入值大于双亲结点的值
    {
    prev.Right = current; //成为右孩子
    }
    path[p] = current; //将新元素插入数组path的最后
    //修改插入点至根结点路径上各结点的平衡因子
    int bf = 0;
    while (p > 0)
    { //bf表示平衡因子的改变量,当新结点插入左子树,则平衡因子+1
    //当新结点插入右子树,则平衡因子-1
    bf = (value <</SPAN> path[p - 1].Data) ? 1 : -1;
    path[--p].BF += bf; //改变当父结点的平衡因子
    bf = path[p].BF; //获取当前结点的平衡因子
    //判断当前结点平衡因子,如果为0表示该子树已平衡,不需再回溯
    //而改变祖先结点平衡因子,此时添加成功,直接返回
    if (bf == 0)
    {
    return true;
    }
    else if (bf == 2 || bf == -2) //需要旋转的情况
    {
    RotateSubTree(bf);
    return true;
    }
    }
    return true;
    }
    //删除指定值
    public bool Remove(int value)
    {
    p = -1;
    //parent表示双亲结点,node表示当前结点
    Node node = _head;
    //寻找指定值所在的结点
    while (node != null)
    {
    path[++p] = node;
    //如果找到,则调用RemoveNode方法删除结点
    if (value == node.Data)
    {
    RemoveNode(node);//现在p指向被删除结点
    return true; //返回true表示删除成功
    }
    if (value <</SPAN> node.Data)
    { //如果删除值小于当前结点,则向左子树继续寻找
    node = node.Left;
    }
    else
    { //如果删除值大于当前结点,则向右子树继续寻找
    node = node.Right;
    }
    }
    return false; //返回false表示删除失败
    }
    //删除指定结点
    private void RemoveNode(Node node)
    {
    Node tmp = null;
    //当被删除结点存在左右子树时
    if (node.Left != null && node.Right != null)
    {
    tmp = node.Left; //获取左子树
    path[++p] = tmp;
    while (tmp.Right != null) //获取node的中序遍历前驱结点,并存放于tmp中
    { //找到左子树中的最右下结点
    tmp = tmp.Right;
    path[++p] = tmp;
    }
    //用中序遍历前驱结点的值代替被删除结点的值
    node.Data = tmp.Data;
    if (path[p - 1] == node)
    {
    path[p - 1].Left = tmp.Left;
    }
    else
    {
    path[p - 1].Right = tmp.Left;
    }
    }
    else //当只有左子树或右子树或为叶子结点时
    { //首先找到惟一的孩子结点
    tmp = node.Left;
    if (tmp == null) //如果只有右孩子或没孩子
    {
    tmp = node.Right;
    }
    if (p > 0)
    {
    if (path[p - 1].Left == node)
    { //如果被删结点是左孩子
    path[p - 1].Left = tmp;
    }
    else
    { //如果被删结点是右孩子
    path[p - 1].Right = tmp;
    }
    }
    else //当删除的是根结点时
    {
    _head = tmp;
    }
    }
    //删除完后进行旋转,现在p指向实际被删除的结点
    int data = node.Data;
    while (p > 0)
    { //bf表示平衡因子的改变量,当删除的是左子树中的结点时,平衡因子-1
    //当删除的是右子树的孩子时,平衡因子+1
    int bf = (data <= path[p - 1].Data) ? -1 : 1;
    path[--p].BF += bf; //改变当父结点的平衡因子
    bf = path[p].BF; //获取当前结点的平衡因子
    if (bf != 0) //如果bf==0,表明高度降低,继续后上回溯
    {
    //如果bf为1或-1则说明高度未变,停止回溯,如果为2或-2,则进行旋转
    //当旋转后高度不变,则停止回溯
    if (bf == 1 || bf == -1 || !RotateSubTree(bf))
    {
    break;
    }
    }
    }
    }
    //旋转以root为根的子树,当高度改变,则返回true;高度未变则返回false
    private bool RotateSubTree(int bf)
    {
    bool tallChange = true;
    Node root = path[p], newRoot = null;
    if (bf == 2) //当平衡因子为2时需要进行旋转操作
    {
    int leftBF = root.Left.BF;
    if (leftBF == -1) //LR型旋转
    {
    newRoot = LR(root);
    }
    else if (leftBF == 1)
    {
    newRoot = LL(root); //LL型旋转
    }
    else //当旋转根左孩子的bf为0时,只有删除时才会出现
    {
    newRoot = LL(root);
    tallChange = false;
    }
    }
    if (bf == -2) //当平衡因子为-2时需要进行旋转操作
    {
    int rightBF = root.Right.BF; //获取旋转根右孩子的平衡因子
    if (rightBF == 1)
    {
    newRoot = RL(root); //RL型旋转
    }
    else if (rightBF == -1)
    {
    newRoot = RR(root); //RR型旋转
    }
    else //当旋转根左孩子的bf为0时,只有删除时才会出现
    {
    newRoot = RR(root);
    tallChange = false;
    }
    }
    //更改新的子树根
    if (p > 0)
    {
    if (root.Data <</SPAN> path[p - 1].Data)
    {
    path[p - 1].Left = newRoot;
    }
    else
    {
    path[p - 1].Right = newRoot;
    }
    }
    else
    {
    _head = newRoot; //如果旋转根为AVL树的根,则指定新AVL树根结点
    }
    return tallChange;
    }
    //root为旋转根,rootPrev为旋转根双亲结点
    private Node LL(Node root) //LL型旋转,返回旋转后的新子树根
    {
    Node rootNext = root.Left;
    root.Left = rootNext.Right;
    rootNext.Right = root;
    if (rootNext.BF == 1)
    {
    root.BF = 0;
    rootNext.BF = 0;
    }
    else //rootNext.BF==0的情况,删除时用
    {
    root.BF = 1;
    rootNext.BF = -1;
    }
    return rootNext; //rootNext为新子树的根
    }
    private Node LR(Node root) //LR型旋转,返回旋转后的新子树根
    {
    Node rootNext = root.Left;
    Node newRoot = rootNext.Right;
    root.Left = newRoot.Right;
    rootNext.Right = newRoot.Left;
    newRoot.Left = rootNext;
    newRoot.Right = root;
    switch (newRoot.BF) //改变平衡因子
    {
    case 0:
    root.BF = 0;
    rootNext.BF = 0;
    break;
    case 1:
    root.BF = -1;
    rootNext.BF = 0;
    break;
    case -1:
    root.BF = 0;
    rootNext.BF = 1;
    break;
    }
    newRoot.BF = 0;
    return newRoot; //newRoot为新子树的根
    }
    private Node RR(Node root) //RR型旋转,返回旋转后的新子树根
    {
    Node rootNext = root.Right;
    root.Right = rootNext.Left;
    rootNext.Left = root;
    if (rootNext.BF == -1)
    {
    root.BF = 0;
    rootNext.BF = 0;
    }
    else //rootNext.BF==0的情况,删除时用
    {
    root.BF = -1;
    rootNext.BF = 1;
    }
    return rootNext; //rootNext为新子树的根
    }
    private Node RL(Node root) //RL型旋转,返回旋转后的新子树根
    {
    Node rootNext = root.Right;
    Node newRoot = rootNext.Left;
    root.Right = newRoot.Left;
    rootNext.Left = newRoot.Right;
    newRoot.Right = rootNext;
    newRoot.Left = root;
    switch (newRoot.BF) //改变平衡因子
    {
    case 0:
    root.BF = 0;
    rootNext.BF = 0;
    break;
    case 1:
    root.BF = 0;
    rootNext.BF = -1;
    break;
    case -1:
    root.BF = 1;
    rootNext.BF = 0;
    break;
    }
    newRoot.BF = 0;
    return newRoot; //newRoot为新子树的根
    }
    }
    

      

    参考文档:

    http://blog.sina.com.cn/s/blog_66770c8501015xmw.html

    http://www.eefocus.com/xiaols/blog/13-12/300934_f5e45.html

  • 相关阅读:
    如何配置mysql的超时时间
    什么是P2P流标
    为何农历10月1号要祭祖上坟?原来有这么多讲究,你知道吗?
    “请家堂”的旧习俗不是封建迷信
    sourcetree合并分支
    mybatis sql参考
    source tree使用经验
    关于 early Z 与 z-prepass
    发现一个好工具RenderDoc
    HASHSET不能预留容量问题
  • 原文地址:https://www.cnblogs.com/GumpYan/p/5755584.html
Copyright © 2011-2022 走看看