二叉搜索树

第一次写的代码备份:

1. 概念

二叉搜索树,BST(Binary Search Tree),即为特殊的二叉树。
以根节点为例,左子树中所有的值均小于根节点;右子树中所有的值均大于根节点。此结论对于任意节点有效。
二叉搜索树,为有序树。按照中序遍历的方式(左 - 中 - 右)得到的序列为从小到大的有序序列。
因此,最左侧的叶子节点为最小元素,左右侧的节点为最大元素。

2. 操作

2.1 二叉搜索树的创建及节点插入

二叉搜索树的创建过程,就是依次按需插入节点的过程。
整体插入过程类似于折半查找的分治思想:第一个插入的节点作为标识,小于的节点插入到左子树中,大于的节点插入到右子树中。然后每个节点上均递归此过程。

我们先创建二叉树的节点,创建过程如下:

/** 二叉树的节点 */
@interface TreeNode : NSObject

@property (nonatomic, strong) TreeNode *leftChild;
@property (nonatomic, strong) TreeNode *rightChild;

@property (nonatomic, assign) NSInteger data;

@end


@implementation TreeNode

- (NSString *)description {
    // 中序遍历输出
    NSString *desp = @"";
    if (self.leftChild) {
        desp = [NSString stringWithFormat:@"%@ %@", desp, self.leftChild];
    }
    
    desp = [NSString stringWithFormat:@"%@ %ld", desp, self.data];
    
    if (self.rightChild) {
        desp = [NSString stringWithFormat:@"%@ %@", desp, self.rightChild];
    }
    
    return desp;
}

@end

插入节点的实现如下:

// 向二叉查找树(BinarySearchTree)中插入节点
TreeNode *insertNodeToBST(TreeNode *treeNode, NSInteger data) {
    // 空树,则直接创建为根节点
    if (!treeNode) {
        treeNode = [TreeNode new];
        treeNode.data = data;
        
        return treeNode;
    }
    
    // 树存在,则根据数据大小,查看需要插入到的子树
    if (treeNode.data > data) {
        // 插入到左子树,左子树更新
        treeNode.leftChild = insertNodeToBST(treeNode.leftChild, data);
    } else {
        // 插入到右子树,右子树更新
        treeNode.rightChild = insertNodeToBST(treeNode.rightChild, data);
    }
    
    return treeNode;
}

创建二叉搜索树的过程,即依次插入节点:

// 创建二叉查找树
TreeNode *createBST(NSArray *source) {
    TreeNode *BST;
    for (NSNumber *item in source) {
        BST = insertNodeToBST(BST, item.integerValue);
    }
    return BST;
}

简单调用:

// 创建二叉查找树
TreeNode *BST = createBST(@[@34, @11, @998, @2, @45, @22]);
NSLog(@"%@", BST);

// 插入
BST = insertNodeToBST(BST, 0);
NSLog(@"%@", BST);

执行结果:
AlgorithmLearning[25055:10245382] 2 11 22 34 45 998
AlgorithmLearning[25055:10245382] 0 2 11 22 34 45 998

由此也可以看出,创建二叉查找树的过程,也是对数组进行排序的过程(需要按照中序遍历输出)

2.2 二叉搜索树的节点删除

从二叉搜索树中删除节点的要求,是不能破坏二叉树的整体结构(中序遍历出的结果仍然有序)。
可以通过修改节点数据或移除节点的方式进行。

首先,我们可以将删除的节点进行分类:

  1. 删除的节点为叶子节点,没有左右子树;
  2. 删除的节点为根节点,只有右子树;
  3. 删除的节点为根节点,只有左子树;
  4. 删除的节点为根节点,包含左右子树。

解法:

  1. 对于没有左右子树的叶子节点,删除时,只要将其父节点的对应子节点置为NULL即可;(可以理解为单链表尾部节点的删除)
  2. 对于只有右子树的节点来说,删除时,只要将其父节点对应的右子节点设置为删除节点的右节点即可;(可以理解为单链表中间某节点的删除)
  3. 对于只有左子树的节点来说,删除时,只要将其父节点对应的左子节点设置为删除节点的左节点即可;(同上)
  4. 对于同时包含左右子树的节点,删除情况要复杂一些:
    删除时,由于所有左节点均小于删除节点,因此,需要找一个大于所有左子树节点(大于左子节点即可),同时又是右子树中最小的节点即可。因此,只要选定待删除节点的右子树中,最左边的节点进行覆盖即可(没有则直接将待删节点的右节点提上即可)。

这里,需要两个额外功能,

  1. 在二叉查找树中,查找指定节点:
TreeNode *findNode(TreeNode *BST, NSInteger data) {
    if (BST.data == data) {
        return BST;
    }
    if (BST.data > data) {
        return findNode(BST.leftChild, data);
    } else {
        return findNode(BST.rightChild, data);
    }
    
    return nil;
}
  1. 在二叉树中,查找指定节点的父节点:
TreeNode *findPrevNode(TreeNode *BST, NSInteger data) {
    if (!BST) {
        return nil;
    }
    
    if (BST.leftChild.data == data || BST.rightChild.data == data) {
        return BST;
    } else {
        TreeNode *parent1 = findPrevNode(BST.leftChild, data);
        TreeNode *parent2 = findPrevNode(BST.rightChild, data);
        
        if (parent1) {
            return parent1;
        }
        
        if (parent2) {
            return parent2;
        }
    }
    
    return nil;
}

删除子节点的实现如下:

// 从二叉查找树中删除指定数据
BOOL deleteNodeFromBST(TreeNode *BST, NSInteger data) {
    // 查询数据所在节点
    TreeNode *deleteNode = findNode(BST, data);
    
    // 没有找到待删除数据,失败
    if (!deleteNode) {
        return NO;
    }
    
//    // 查看待删除节点是否存在左子树
//    if (deleteNode.leftChild) {
//        // 存在左子树,即有比待删除节点小的,需要重新调整
//        // 根据中序访问“左-中-右”的顺序,左子树中最大的,即为最右端的子结点,故遍历查找之
//        TreeNode *theRightNode = deleteNode.leftChild;
//        TreeNode *superOfTheRightNode = theRightNode;
//        while (theRightNode.rightChild) {
//            superOfTheRightNode = theRightNode; // 记录最右的父节点
//            theRightNode = theRightNode.rightChild;
//        }
//        // 将待删除结点数据赋值为左子树的最右端节点
//        deleteNode.data = theRightNode.data;
//
//        if (superOfTheRightNode == theRightNode) {
//            // 没有最右节点,左子树都是左结点
//            // 因此最大的就是待删除节点的左节点
//            // 指向下一个左节点即可(没有就是nil)
//            deleteNode.leftChild = theRightNode.leftChild;
//        } else {
//            // 最右节点的父节点,其右节点则替换为此右节点的左子树即可(没有就是nil)
//            superOfTheRightNode.rightChild = theRightNode.leftChild;
//        }
//
//
//    } else {
//        // 不存在左子树,则右子树如果存在,都比待删除节点大,
//        // 根据中序访问“左-中-右”的顺序,可以直接移除,
//
//        // 找到待删除节点的父节点
//        TreeNode *parentNode = findPrevNode(BST, data);
//
//        // 其父节点的左子树指向待删除节点的右子树即可
//        parentNode.leftChild = deleteNode.rightChild; // 直接覆盖了待删除节点
//    }
    
    
    if (!deleteNode.leftChild && !deleteNode.rightChild) {
        // 1. 左右子树都不存在
        // 获取父节点
        TreeNode *parentNode = findPrevNode(BST, data);
        // 将对应的子节点置为nil
        if (parentNode.leftChild == deleteNode) {
            parentNode.leftChild = nil;
        } else {
            parentNode.rightChild = nil;
        }
    } else {
        if (deleteNode.rightChild && !deleteNode.leftChild) {
            // 2. 只存在右子树
            // 获取父节点
            TreeNode *parentNode = findPrevNode(BST, data);
            if (!parentNode) {
                deleteNode = deleteNode.rightChild;
            } else if (parentNode.leftChild == deleteNode) {
                // 是左子节点
                // 父的相应子节点指向下一个右节点,完成删除
                parentNode.leftChild = deleteNode.rightChild;
            } else {
                // 父的相应子节点指向下一个右节点,完成删除
                parentNode.rightChild = deleteNode.rightChild;
            }
            
            
        } else if (deleteNode.leftChild && !deleteNode.rightChild) {
            // 3. 只存在左子树
            // 获取父节点
            TreeNode *parentNode = findPrevNode(BST, data);
            if (!parentNode) {
                deleteNode = deleteNode.leftChild;
            } else if (parentNode.leftChild == deleteNode) {
                parentNode.leftChild = deleteNode.leftChild;
            } else {
                parentNode.rightChild = deleteNode.leftChild;
            }
            
        } else {
            // 4. 左右子树均存在
            // 查找待删除节点的右子树中,最小的节点(最左边的节点)
            
            TreeNode *tmpNode = deleteNode.rightChild;
            
            if (tmpNode.leftChild) {
                TreeNode *prevNode = tmpNode; // 用于遍历过程中,顺便取到的tmpNode的父节点
                // 找到最小节点
                while (tmpNode.leftChild) {
                    prevNode = tmpNode; // 记录父节点
                    tmpNode = tmpNode.leftChild;
                }
                // 进行数据覆盖
                deleteNode.data = tmpNode.data;
                
                // 清除此最小节点
                prevNode.leftChild = nil;
            } else {
                // 最小节点就是待删除节点的右节点,与2的情况相同
                // 进行数据覆盖
                deleteNode.data = tmpNode.data;
                // 指向下一个
                deleteNode.rightChild = tmpNode.rightChild;
            }
        }
    }
    
    return YES;
}

注:注释代码为更简略实现。

测试如下:

//  删除
BOOL deleteResult = deleteNodeFromBST(BST, 998);
NSLog(@"%hhd", deleteResult);
NSLog(@"%@", BST);

执行结果:
AlgorithmLearning[26968:10339083] 1
AlgorithmLearning[26968:10339083] 0 2 11 22 34 45

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

推荐阅读更多精彩内容