二叉搜索树查找、插入、删除

二叉搜索树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。

1. 二叉搜索树结构

二叉树搜索树.png

上图是一棵典型的二叉搜索树,二叉搜索树按照中序遍历,是一个递增的序列,很容易得到其中序遍历结果为:1、3、5、6、7、8、10。

2. 二叉搜索树查找

查找比较简单,根据二叉搜索树的特性,当前节点比其左子节点值大,比其右子节点值小,依次递归比较当前节点与待查找值的大小,直到最后找到结果。如下图所示:

二叉搜索树查找.png

3. 二叉搜索树插入

从根节点开始,依次与待插入的数据进行比较,直到无法比较为止,这个时候最后找到的这个节点必然是叶子节点或者只有一个子节点,最后将待插入的数据插入到这个节点上。

二叉搜索树插入.png

4. 二叉搜索树删除

要删除一个数据,首先得在树中找到目标节点,找到目标节点很简单,前面已经讲过,但难的是删除。待删除节点一般会有以下 4 种情况:

我们先说简单的:

  • 第4种情况:待删除节点为 8 ,我们只需要将其父节点 10 的左孩子置空就可以了;
  • 第2、3种情况:这两种都是类似的。以第2个为例,待删除节点为 1,其父节点为 5,其子节点为 3,我们只需将节点 5 的左孩子置为节点 3 即可;

最后我们来看第 1 种情况,发现怎样操作都不好:如果将节点 7 删除,再将节点 5 置为根节点,由于节点 5 已经有一个右孩子节点 6,那么节点 6 与节点 10 就会发生冲突,然后还要继续调整节点的位置;如果将节点 10 置为根节点,那么还得继续调整节点 8 ,如果是一棵复杂的树,操作起来非常难。

那么怎么办呢?既然情况 1 直接调整难度很大,那么是否有某种方式,可以将其转换为第2、3、4种情况呢,如果可以的话,那这个问题就迎刃而解了。答案是肯定的,看以下流程所示:

如上图所示,经过一番操作之后,我们将原问题转换成另外一个问题了,简化后的问题我们能很方便解决了。

最后是完整的代码(kotlin编写):

//二叉树节点
class Node<T>(var value: T?) {
    var left: Node<T>? = null
    var right: Node<T>? = null
}

//二叉搜索树
class BinarySearchTree {

    //二叉搜索树根节点
    var root: Node<Int>? = null

    /**
     * 在树中进行查找
     */
    fun find(data: Int): Node<Int>? {
        var p = root
        while (p != null) {
            if (data == p.value) {
                return p
            }
            if (data < p.value!!) {
                p = p.left
            } else {
                p = p.right
            }
        }
        return null
    }

    /**
     * 往树中插入数据
     */
    fun insert(data: Int) {
        var p = root
        var pp: Node<Int>? = null
        //循环结束时,pp 最多只有1个子节点,将待插入节点与 pp 比较大小,如果比 pp 大,则插入到 pp 的右子节点,否则就插入到 pp 的左子节点
        while (p != null) {
            if (data > p.value!!) {
                pp = p
                p = p.right
            } else {
                pp = p
                p = p.left
            }
        }
        //pp 为 null,说明整棵树就是空的,我们直接为树的根节点赋值即可
        if (pp == null) {
            root = Node(data)
        } else {
            if (data > pp.value!!) {
                pp.right = Node(data)
            } else {
                pp.left = Node(data)
            }
        }
    }
    
    /**
     * 删除节点
     * 删除策略:首先找到待删除节点 p,然后找到该目标节点的右子树中的最左节点 t,交换节点 p 与 t 的值,最后删除节点 t
     */
    fun delete(data: Int) {
        var p = root                //目标节点
        var pp: Node<Int>? = null   //目标节点的父节点

        while (p != null) {
            if (data == p.value) {
                //找到目标节点,停止循环
                break
            }
            if (data > p.value!!) {
                pp = p
                p = p.right
            } else {
                pp = p
                p = p.left
            }
        }
        //如果找不到待删除节点则直接返回
        p ?: return

        //找到节点 p 的右子树中的最左叶子节点 t
        var t = p.right     //待删除目标节点的右子节点
        var tt = p          //节点 t 的父节点
        while (t?.left != null) {
            tt = t
            t = t.left
        }

        if (t != null) {
            //交换节点 p、t 的值,后面我们直接删除节点 t 仍能满足二叉搜索树的特性
            p.value = t.value
            pp = tt
            p = t
        }

        //待删除的是根节点
        if (pp == null) {
            root = if (p.left != null) p.left else p.right
        } else {
            //删除节点 p,此时的指针 p 必然最多只有1个子节点
            var child = if (p.left != null) p.left else p.right
            if (pp.left == p) {
                pp.left = child
            } else {
                pp.right = child
            }
        }
    }
}

二叉搜索树的查找、插入、删除操作的时间复杂度与树的高度有关,如果是一棵平衡二叉搜索树,则其时间复杂度均为 O(logn),如果树退化成一个链表,则其时间复杂度为最坏情况 O(n)。

所以树的高度很重要,如果我们能一直维持一棵树比较平衡,那么其增删改查的时间复杂度均为 O(logn),这样操作的效率就会非常之高。大名鼎鼎的红黑树就是这样一棵树,其实现非常复杂,涉及到节点的左旋、右旋等操作,有兴趣的可以去研究研究,不过我们还是要弄懂二叉搜索树的结构以及各种操作。

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