Swift 数据结构 - AVL树(AVL Tree)

AVL树

平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

image.png

平衡因子

某结点的左子树与右子树的 高度(深度)差 即为该结点的 平衡因子(BF,Balance Factor)。平衡二叉树上所有结点的平衡因子只可能是 -1,0 或 1。如果某一结点的平衡因子绝对值大于1则说明此树不是平衡二叉树。为了方便计算每一结点的平衡因子我们可以为每个节点赋予 height 这一属性,表示此节点的高度。

节点的 height 是获取该节点最低叶子所需的步数。 例如,在下面的树中,从A到E需要三个步,因此A的高度为3。B的高度为2,C的高度为1,其他的高度为0,因为它们是叶节点。

image.png

高度和深度是相反的表示,深度是 从上到下 数的,而高度是 从下往上 数。
我们先来看看高度和深度的定义,某节点的深度是指从根节点到该节点的最长简单路径边的条数,而高度是指从该节点到叶子节点的最长简单路径边的条数。

基础设计

首先我们可以设计出 AVL 树节点,并且实现一些简单通用的方法供后续操作。

/**
 * AVLTree是BST,所以节点值必须是可比较的
 */
public class AvlTree<E: Comparable>{
    private class Node {
        public var e: E
        public var left: Node? = nil
        public var right: Node? = nil
        public var height: Int = 1

        public init(e: E){
            self.e = e
        }
    }
    private var root: Node?
    private var size: Int

    public init() {
        root = nil
        size = 0
    }
    
    //获取某一结点的高度
    private func getHeight(node: Node?) -> Int {
        guard let node = node else {
            return 0
        }
        return node.height
    }
    
    public func getSize() -> Int {
        return size
    }
    
    public func isEmpty() -> Bool {
        return size == 0
    }
    
    /**
     * 获取节点的平衡因子
     * @param node
     * @return
     */
    private func getBalanceFactor(node: Node?) -> Int {
        guard let node = node else {
            return 0
        }
        return abs(getHeight(node: node.left) - getHeight(node: node.right))
    }
    
    //判断树是否为平衡二叉树
    public func isBalanced() -> Bool {
        return isBalanced(node: root)
    }
    
    private func isBalanced(node: Node?) -> Bool {
        guard let node = node else {
            return true
        }
        
        let balanceFactory = getBalanceFactor(node: node)
        if balanceFactory > 1 {
            return false
        }
        return isBalanced(node: node.left) && isBalanced(node: node.right)
    }
}

添加节点

往平衡二叉树中添加节点很可能会导致二叉树失去平衡,所以我们需要在每次插入节点后进行平衡的维护操作。插入节点破坏平衡性有如下四种情况:

LL(需要右旋平衡)

LL的意思是向左子树(L)的左孩子(L)中插入新节点后导致不平衡,这种情况下需要 右旋操作,而不是说LL的意思是右旋,后面的也是一样。

image.png

我们将这种情况抽象出来,得到下图:


image.png

我们需要对节点y进行平衡的维护。步骤如下图所示:

image.png

    /**
     * 右旋转,返回根节点
     */
    private func rightRotate(y: Node?) -> Node? {
        guard let y = y else {
            return nil
        }
        let x = y.left
        let t3 = x?.right
        x?.right = y
        y.left = t3
        //更新height
        y.height = max(getHeight(node: y.left),getHeight(node: y.right))+1
        x?.height = max(getHeight(node: x?.left),getHeight(node: x?.right))+1
        return x
    }

RR(需要左旋平衡)

image.png

我们将这种情况抽象出来,得到下图:


image.png

我们需要对节点y进行平衡的维护。步骤如下图所示:

image.png

    /**
     * 左旋转
     */
    private func leftRotate(y: Node?) -> Node? {
        guard let y = y else {
            return nil
        }
        let x = y.right
        let t3 = x?.left
        x?.left = y
        y.right = t3
        //更新height
        y.height = max(getHeight(node: y.left),getHeight(node: y.right)) + 1
        x?.height = max(getHeight(node: x?.left),getHeight(node: x?.right)) + 1
        return x
    }

LR(先左旋后右旋)

image.png

我们将这种情况抽象出来,得到下图:


image.png

我们需要对节点y进行平衡的维护。步骤如下图所示:

image.png

RL(先右旋后左旋)

image.png

我们将这种情况抽象出来,得到下图:
image.png

我们需要对节点y进行平衡的维护。步骤如下图所示:
image.png

添加节点代码

extension AvlTree {
    // 向二分搜索树中添加新的元素(key, value)
    public func add(e: E) {
        root = add(node: root, e: e)
    }
    
    // 向以node为根的二分搜索树中插入元素(key, value),递归算法
    // 返回插入新节点后二分搜索树的根
    private func add(node: Node?, e: E) -> Node? {
        guard let node = node else {
             size += 1
            return Node(e: e)
        }

        //判断是插入根节点的左子树还是右子树
        if e < node.e {
            node.left = add(node: node.left, e: e)
        } else if e > node.e {
            node.right = add(node: node.right, e: e)
        }
        
        //更新height
        node.height = 1 + max(getHeight(node: node.left),getHeight(node: node.right))
        
        //计算平衡因子
        let balanceFactor = getBalanceFactor(node: node)
        if balanceFactor > 1 && getBalanceFactor(node: node.left) > 0 {
            //右旋LL
            return rightRotate(y: node)
        }
        if balanceFactor < -1 && getBalanceFactor(node: node.right) < 0  {
            //左旋RR
            return leftRotate(y: node)
        }
        
        //LR
        if balanceFactor > 1 && getBalanceFactor(node: node.left) < 0 {
            node.left = leftRotate(y: node.left)
            return rightRotate(y: node)
        }
        
        //RL
        if balanceFactor < -1 && getBalanceFactor(node: node.right) > 0 {
            node.right = rightRotate(y: node.right)
            return leftRotate(y: node)
        }
        return node
    }
}

删除节点

在删除AVL树节点前需要知道二分搜索树的节点删除操作【点此学习吧!】,和二分搜索树删除节点不同的是我们删除AVL树的节点后需要进行平衡的维护操作。

extension AvlTree {
    //查找二叉搜索树的节点
    private func getNode(root: Node?, e: E) -> Node? {
        guard let root = root else {
            return nil
        }
        
        if(root.e == e) {
            return root
        } else if e > root.e {
            return getNode(root: root.right, e: e)
        } else {
            return getNode(root: root.left, e: e)
        }
        return nil
    }
    //移出节点
    public func remove(e: E) -> E? {
        guard let node = getNode(root: root, e: e) else {
            return nil
        }
        root = remove(node: root, e: e)
        return node.e
    }
    
    //二叉搜索树的最小值一直找左孩子就行了
    private func minNode(node: Node?) -> Node? {
        guard let node = node else {
            return nil
        }
        
        if let node = node.left {
            return minNode(node: node.left)
        } else {
            return node
        }
    }

    private func remove(node: Node?, e: E) -> Node? {
        guard let node = node else {
            return nil
        }
        var retNode: Node?
        if e < node.e {
            node.left = remove(node: node.left , e: e)
            retNode = node
        } else if e > node.e {
            node.right = remove(node: node.right, e: e)
            retNode = node
        } else {
            // 待删除节点左子树为空的情况
            if node.left == nil {
                let rightNode = node.right
                node.right = nil
                size -= 1
                retNode = rightNode
            } else if node.right == nil {
                // 待删除节点右子树为空的情况
                let leftNode = node.left
                node.left = nil
                size -= 1
                retNode = leftNode
            } else {
                // 待删除节点左右子树均不为空的情况
                // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
                // 用这个节点顶替待删除节点的位置
                let successor = minNode(node: node.right)!
                successor.right = remove(node: node.right, e: successor.e)
                successor.left = node.left

                node.left = nil
                node.right = nil
                retNode = successor
            }
        }
        
        guard let retNodeTmp = retNode else {
            return nil
        }
        //维护平衡
        //更新height
        retNodeTmp.height = 1 + max(getHeight(node: retNodeTmp.left),getHeight(node: retNodeTmp.right))
        
        //计算平衡因子
        let balanceFactor = getBalanceFactor(node: retNodeTmp)
        if balanceFactor > 1 && getBalanceFactor(node: retNodeTmp.left) >= 0 {
            //右旋LL
            return rightRotate(y: retNodeTmp)
        }
        
        if balanceFactor < -1 && getBalanceFactor(node: retNodeTmp.right) <= 0 {
            //左旋RR
            return leftRotate(y: retNodeTmp)
        }
        
        //LR
        if balanceFactor > 1 && getBalanceFactor(node: retNodeTmp.left) < 0 {
            node.left = leftRotate(y: retNodeTmp.left)
            return rightRotate(y: retNodeTmp)
        }
        
        //RL
        if balanceFactor < -1 && getBalanceFactor(node: retNodeTmp.right) > 0 {
            node.right = rightRotate(y: retNodeTmp.right)
            return leftRotate(y: retNodeTmp)
        }
        return retNodeTmp
    }
}

参考文章

详细图文——AVL树

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270