iOS 红黑树(平衡二叉树)的学习以及演示Demo

之前有段时间再研究算法,然后学到了平衡二叉树 - 红黑树的理论,于是在纸上画了很多很多,导致一本笔记被我画完了……
想想,要不就干脆开发一个Demo来演示二叉树,省纸!


这是项目的github地址:https://github.com/WalkingToTheDistant/RBTree,如果觉得我写的代码对你有帮助的话,给我一个星呗^ _ ^
可能项目还存在问题(自己测试能力有限),如果你们发现了问题,告诉我一声,我一定尽快修改过来的哈。


首先看看Demo的实际效果


添加树节点的操作示例

效果截图


红黑树效果截图

首先介绍一下红黑树的规则

  • 所有节点是红色或黑色。
  • 根节点是黑色。
  • 所有叶子节点都是黑色。
  • 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。
  • 从每个叶子到根的所有路径上不能有两个连续的红色节点。

接下来介绍一下红黑树的平衡旋转方法
首先针对当前节点是父节点的左孩纸的情况讨论,当前节点是右孩纸的话,旋转就相反

  • 情况1:当前节点的兄弟节点为红色,则根据从任意节点出发,各个分子的黑高度是一样的规则,那么兄弟节点的孩纸树中必有黑节点,则把兄弟节点变为黑,父节点变为红,然后以父节点左旋,旋转之后,兄弟节点的左孩纸则变成当前节点的兄弟节点(变成情况2)。
  • 情况2:兄弟节点为黑,其子孩纸都为黑,那么则把兄弟节点变为红,然后当前节点移到其父节点。
  • 情况3:兄弟节点为黑,其左孩纸为红,右孩纸为黑,那么则把左孩纸变黑,兄弟节点变红,然后在兄弟节点右旋,这样当前节点的兄弟节点则变成了右转后的左节点(变成情况4)
  • 情况4:兄弟节点为黑,其右孩纸为红,那么则把兄弟节点的颜色变成其父节点的颜色,而父节点的颜色变成黑,兄弟节点的右孩纸变为红,在父节点左旋
  • 情况5:兄弟节点为空,则直接把当前节点指向其父节点再处理

我开发Demo的时候,大部分时间耗在这个平衡旋转的算法上,痛苦...
额,算法的规则细节,就不说了呗,具体红黑树的逻辑你们可以百度看看,只是如果要理解的话,需要深入模拟哈。


接下来看一下重要的功能代码:
先看下节点的代码结构

typedef enum : int{
    NodeColor_Red = 0,  /* 红色 */
    NodeColor_Black,    /* 黑色 */
    
} NodeColor;

/** 红黑树节点 */
@interface RBTreeNode : NSObject

/** 初始化对象,并复制节点值和节点颜色 */
+ (instancetype) treeNodeWithValue:(NSNumber*)value withColor:(NodeColor)color;

/** 节点颜色 */
@property(nonatomic, assign, setter=setNodeColor:, getter=getNodeColor) NodeColor mNodeColor;

/** 节点值 */
@property(nonatomic, retain, setter=setNodeValue:, getter=getNodeValue) NSNumber *mNodeValue;

/** 左节点 */
@property(nonatomic, retain, setter=setLeftNode:, getter=getLeftNode) RBTreeNode *mLeftNode;

/** 右节点 */
@property(nonatomic, retain, setter=setRightNode:, getter=getRightNode) RBTreeNode *mRightNode;

/** 父节点,设置为Weak避免循环引用的问题 */
@property(nonatomic, weak, setter=setParentNode:, getter=getParentNode) RBTreeNode *mParentNode;

@end

平衡旋转的代码

- (void) handleRBTreeForDeleteNode:(RBTreeNode*)parentNodeForCurrentNode
                   withCurrentNode:(RBTreeNode*)currentNode
                   withBrotherNode:(RBTreeNode*)brotherForCurrentNode
                 withIsLeftOrRight:(BOOL)isLeftOfParentNodeForCurrentNode
{
    /**
     下面针对当前节点是父节点的左孩纸的情况讨论,当前节点是右孩纸的话,旋转就相反
     情况1:当前节点的兄弟节点为红色,则根据从任意节点出发,各个分子的黑高度是一样的规则,那么兄弟节点的孩纸树中必有黑节点,则把兄弟节点变为黑,父节点变为红,然后以父节点左旋,旋转之后,兄弟节点的左孩纸则变成当前节点的兄弟节点(变成情况2)。
     情况2:兄弟节点为黑,其子孩纸都为黑,那么则把兄弟节点变为红,然后当前节点移到其父节点。
     情况3:兄弟节点为黑,其左孩纸为红,右孩纸为黑,那么则把左孩纸变黑,兄弟节点变红,然后在兄弟节点右旋,这样当前节点的兄弟节点则变成了右转后的左节点(变成情况4)
     情况4:兄弟节点为黑,其右孩纸为红,那么则把兄弟节点的颜色变成其父节点的颜色,而父节点的颜色变成黑,兄弟节点的右孩纸变为红,在父节点左旋
     情况5:兄弟节点为空,则直接把当前节点指向其父节点再处理
     */
    if(currentNode != mRootTreeNode
       && currentNode.getNodeColor == NodeColor_Black)
    {
        RBTreeNode *grandFatherNode = parentNodeForCurrentNode.getParentNode; // 不是根节点,则必存在父节点
        RBTreeNode *uncleNode = nil;
        BOOL isLeftOrRightForGrandFather = YES;
        if(grandFatherNode == nil){
            isLeftOrRightForGrandFather = YES;
            uncleNode = nil;
            
        } else if(grandFatherNode.getLeftNode == parentNodeForCurrentNode){
            isLeftOrRightForGrandFather = YES;
            uncleNode = grandFatherNode.getRightNode;
            
        } else {
            isLeftOrRightForGrandFather = NO;
            uncleNode = grandFatherNode.getLeftNode;
        }
        
        // ======= 先处理没有旋转的情况 =======
        if(brotherForCurrentNode == nil){ // 情况5
            [self handleRBTreeForDeleteNode:grandFatherNode withCurrentNode:parentNodeForCurrentNode withBrotherNode:uncleNode withIsLeftOrRight:isLeftOrRightForGrandFather];
            
        } else if(brotherForCurrentNode.getNodeColor == NodeColor_Black
                  && ( brotherForCurrentNode.getLeftNode == nil || brotherForCurrentNode.getLeftNode.getNodeColor == NodeColor_Black )
                  && ( brotherForCurrentNode.getRightNode == nil || brotherForCurrentNode.getRightNode.getNodeColor == NodeColor_Black) )
        {   // 情况2
            [brotherForCurrentNode setNodeColor:NodeColor_Red];
            [self handleRBTreeForDeleteNode:grandFatherNode withCurrentNode:parentNodeForCurrentNode withBrotherNode:uncleNode withIsLeftOrRight:isLeftOrRightForGrandFather];
            
        }
        // ======= 接下来处理需要进行旋转的情况 =======
        else if(isLeftOfParentNodeForCurrentNode == YES){ // 当前节点是左孩纸:接下来的情况需要进行旋转, 则需要判断当前节点是左孩纸还是右孩纸
            if(brotherForCurrentNode.getNodeColor == NodeColor_Red){ // 情况1
                [brotherForCurrentNode setNodeColor:NodeColor_Black];
                [parentNodeForCurrentNode setNodeColor:NodeColor_Red];
                [self nodeRotate_toLeft:parentNodeForCurrentNode];
                [self handleRBTreeForDeleteNode:parentNodeForCurrentNode withCurrentNode:currentNode withBrotherNode:parentNodeForCurrentNode.getRightNode withIsLeftOrRight:isLeftOfParentNodeForCurrentNode];
         
            } else if(brotherForCurrentNode.getNodeColor == NodeColor_Black
                      && (brotherForCurrentNode.getLeftNode != nil && brotherForCurrentNode.getLeftNode.getNodeColor == NodeColor_Red)
                      && (brotherForCurrentNode.getRightNode == nil || brotherForCurrentNode.getRightNode.getNodeColor == NodeColor_Black))
            {   // 情况3
                [brotherForCurrentNode.getLeftNode setNodeColor:NodeColor_Black];
                [brotherForCurrentNode setNodeColor:NodeColor_Red];
                [self nodeRotate_toRight:brotherForCurrentNode];
                [self handleRBTreeForDeleteNode:parentNodeForCurrentNode
                                withCurrentNode:currentNode
                                withBrotherNode:parentNodeForCurrentNode.getRightNode
                              withIsLeftOrRight:isLeftOfParentNodeForCurrentNode];
                
            } else if(brotherForCurrentNode.getNodeColor == NodeColor_Black
                      && (brotherForCurrentNode.getRightNode != nil && brotherForCurrentNode.getRightNode.getNodeColor == NodeColor_Red))
            {   // 情况4
                [brotherForCurrentNode setNodeColor:[parentNodeForCurrentNode getNodeColor]];
                [parentNodeForCurrentNode setNodeColor:NodeColor_Black];
                [brotherForCurrentNode.getRightNode setNodeColor:NodeColor_Black];
                [self nodeRotate_toLeft:parentNodeForCurrentNode];
                [self handleRBTreeForDeleteNode:parentNodeForCurrentNode
                                withCurrentNode:currentNode
                                withBrotherNode:parentNodeForCurrentNode.getRightNode
                              withIsLeftOrRight:YES];
            }
        
        } else if(isLeftOfParentNodeForCurrentNode != YES){ // 当前节点是右孩纸
            if(brotherForCurrentNode.getNodeColor == NodeColor_Red){ // 情况1
                [brotherForCurrentNode setNodeColor:NodeColor_Black];
                [parentNodeForCurrentNode setNodeColor:NodeColor_Red];
                [self nodeRotate_toRight:parentNodeForCurrentNode];
                [self handleRBTreeForDeleteNode:parentNodeForCurrentNode withCurrentNode:currentNode withBrotherNode:parentNodeForCurrentNode.getLeftNode withIsLeftOrRight:isLeftOfParentNodeForCurrentNode];
                
            } else if(brotherForCurrentNode.getNodeColor == NodeColor_Black
                      && (brotherForCurrentNode.getRightNode != nil && brotherForCurrentNode.getRightNode.getNodeColor == NodeColor_Red)
                      && (brotherForCurrentNode.getLeftNode == nil || brotherForCurrentNode.getLeftNode.getNodeColor == NodeColor_Black))
            {   // 情况3
                [brotherForCurrentNode.getRightNode setNodeColor:NodeColor_Black];
                [brotherForCurrentNode setNodeColor:NodeColor_Red];
                [self nodeRotate_toLeft:brotherForCurrentNode];
                [self handleRBTreeForDeleteNode:parentNodeForCurrentNode
                                withCurrentNode:currentNode
                                withBrotherNode:parentNodeForCurrentNode.getLeftNode
                              withIsLeftOrRight:isLeftOfParentNodeForCurrentNode];
                
            } else if(brotherForCurrentNode.getNodeColor == NodeColor_Black
                      && (brotherForCurrentNode.getLeftNode != nil && brotherForCurrentNode.getLeftNode.getNodeColor == NodeColor_Red))
            {   // 情况4
                [brotherForCurrentNode setNodeColor:[parentNodeForCurrentNode getNodeColor]];
                [parentNodeForCurrentNode setNodeColor:NodeColor_Black];
                [brotherForCurrentNode.getLeftNode setNodeColor:NodeColor_Black];
                [self nodeRotate_toRight:parentNodeForCurrentNode];
                [self handleRBTreeForDeleteNode:parentNodeForCurrentNode
                                withCurrentNode:currentNode
                                withBrotherNode:parentNodeForCurrentNode.getLeftNode
                              withIsLeftOrRight:isLeftOfParentNodeForCurrentNode];
            }
            
        }
    }
    [currentNode setNodeColor:NodeColor_Black];
}

添加新节点时,判断新节点的位置,以及旋转红黑树的操作

/** 根据当前指向的节点调整红黑树 */
- (void) handleRBTreeForAddNode:(RBTreeNode*)treeNode
{
    /** 情况1:红黑树只有一个根节点 */
    if(treeNode == mRootTreeNode){ // 如果是根节点,那么直接将节点变黑即可
        [treeNode setNodeColor:NodeColor_Black];
        return;
    }
    /** 以下情况则必定存在父节点 */
    /** 情况2:当前节点的父节点为黑色 */
    if(treeNode.getParentNode.getNodeColor == NodeColor_Black){ // 符合红黑树规则,不需要处理
        return;
    }
    
    /** 以下情况父节点必定是红色,所以按照红黑树规则,必定存在祖父节点(规则:红黑树的根节点必是黑色) */
    RBTreeNode *parentNode = treeNode.getParentNode; // 父节点
    RBTreeNode *grandfatherNode = parentNode.getParentNode; // 祖父节点
    BOOL isLeftForParentNodeToGrandfather = (grandfatherNode.getLeftNode == parentNode)? YES : NO; // 父节点是左节点还是右节点,这个决定处理红黑树旋转的方向
    RBTreeNode *uncleNode = (isLeftForParentNodeToGrandfather == YES)? grandfatherNode.getRightNode : grandfatherNode.getLeftNode; // 叔叔节点`
    BOOL isLeftForCurrentNodeToParent = (parentNode.getLeftNode == treeNode)? YES : NO; // 当前节点是左节点还是右节点
    
    /** 情况3:当前节点的父节点为红色,而叔叔节点也为红色(祖父节点和叔叔节点不为空) */
    if(parentNode.getNodeColor == NodeColor_Red
        && uncleNode != nil
        && uncleNode.getNodeColor == NodeColor_Red){
        // 把父节点、叔叔节点都变为黑,祖父节点变为红,然后当前节点指针指向祖父节点再继续监测红黑树
        [parentNode setNodeColor:NodeColor_Black];
        [uncleNode setNodeColor:NodeColor_Black];
        [grandfatherNode setNodeColor:NodeColor_Red];
        return [self handleRBTreeForAddNode:grandfatherNode];
    }
    
    /** 情况4:当前节点的父节点为红色,而叔叔节点为黑色,或者为空 */
    if(parentNode.getNodeColor == NodeColor_Red
        && (uncleNode == nil || uncleNode.getNodeColor == NodeColor_Black)){
        
        if(isLeftForParentNodeToGrandfather == YES){ // 父节点是祖父节点的左孩纸
            if(isLeftForCurrentNodeToParent == YES){ // 当前节点是父节点的左孩纸,这时候只需要把父节点变为黑,祖父节点变成红,当前节点指向祖父节点,并在祖父节点向右旋转
                [parentNode setNodeColor:NodeColor_Black];
                [grandfatherNode setNodeColor:NodeColor_Red];
                [self nodeRotate_toRight:grandfatherNode];
                return [self handleRBTreeForAddNode:grandfatherNode];
            } else { // 当前节点是父节点的右孩纸,那么先把当前节点指向父节点,然后在父节点左旋
                [self nodeRotate_toLeft:parentNode];
                return [self handleRBTreeForAddNode:parentNode];
            }
            
        } else { // 父节点是祖父节点的右孩纸
            if(isLeftForCurrentNodeToParent == YES){// 当前节点是父节点的左孩纸,那么先把当前节点指向父节点,然后在父节点右旋
                [self nodeRotate_toRight:parentNode];
                return [self handleRBTreeForAddNode:parentNode];
            
            } else {// 当前节点是父节点的右孩纸,这时候只需要把父节点变为黑,祖父节点变成红,当前节点指向祖父节点,并在祖父节点向左旋转
                [parentNode setNodeColor:NodeColor_Black];
                [grandfatherNode setNodeColor:NodeColor_Red];
                [self nodeRotate_toLeft:grandfatherNode];
                return [self handleRBTreeForAddNode:grandfatherNode];
            }
        }
    }
    [mRootTreeNode setParentNode:nil];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • R-B Tree简介 R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找...
    张晨辉Allen阅读 9,065评论 5 30
  • 1、红黑树介绍 红黑树又称R-B Tree,全称是Red-Black Tree,它是一种特殊的二叉查找树,红黑树的...
    文哥的学习日记阅读 9,761评论 1 35
  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,373评论 1 31
  • 0.目录 1.算法导论的红黑树本质上是2-3-4树 2.红黑树的结构和性质 3.红黑树的插入 4.红黑树的删除 5...
    王侦阅读 2,394评论 1 2
  • 有很多事我都不明白 但我相信一件事:上天让我们来到这个世上,就是为了让我们创造奇迹。看完大鱼海棠,我有种强烈的...
    柠夏__阅读 414评论 2 10