树之二叉排序树(二叉搜索树)

什么是排序树

说一下普通二叉树可不是左小右大的 

插入的新节点是以叶子形式进行插入的

二叉排序树的中序遍历结果是一个升序的序列

下面是两个典型的二叉排序树 

 

 

 二叉排序树的操作

 构造树的过程即是对无序序列进行排序的过程

 存储结构

 通常采用二叉链表作为存储结构 不能

 插入算法

 

 下面插入一个图解

上面的×就表示会在当前位置给delete掉一个结点 

 查找算法

 

 删除算法

  

 

第三种情况:你删除的结点下面就是说还有左右子树,那么这个时候,我们就要去找到这棵树中序遍历结果之后的直接前驱或者直接后继,然后把这个前驱或者后继给按到删除结点这个位置上,将它下面的树移到被替换结点的位置

删除操作的具体讲解

重点讲解一下删除节点的核心分析

这里在补一张中序遍历的递归调用图

 

  直接上代码

在上代码之前,先来说一下,二叉搜索树很多方法都利用了递归的思想,许多说明我都放到代码注释里面了,可以结合下面的这张图进行思维分析

 

先来看c语言代码(algorithm/bst/bst1.c)

#include <stdio.h>
#include <stdlib.h>

typedef int key_type;
typedef struct _node
{
    key_type key;
    struct _node *left;
    struct _node *right;
}node, *pnode;

void insert_bst(pnode *root, key_type key)
{
    //初始化插入结点
    pnode p = (pnode)malloc(sizeof(node));
    if (p != NULL)
    {
        p->key = key;//把值给放进去
        p->left = p->right = NULL;
    }

    //空树的时候,直接作为根结点
    if (*root == NULL)
    {
        *root = p;
        return;
    }
    //插入到当前结点的左孩子
    if ((*root)->left == NULL && (*root)->key > key)
    {
        (*root)->left = p;//直接在堆上面指就可以了
        return;
    }

    //插入到当前结点的右孩子
    if ((*root)->right == NULL && (*root)->key < key)
    {
        (*root)->right = p;
        return;
    }

    //上面都没有进入,说明结点就要往下继续存放
    //需要先把我们分配的结点内存给释放掉
    free(p);
    //左子树递归
    if ((*root)->key > key) 
    {
        insert_bst(&(*root)->left, key);
    }
    //右子树递归
    else if((*root)->key < key) 
    {
        insert_bst(&(*root)->right, key);
    }
}

//根据关键字删除某个结点,成功返回1,失败返回0
int delete_bst(pnode *root, key_type key)
{
    if (*root == NULL)
    {
        return 0;//这是一棵空树
    }
    if ((*root)->key == key)
    {
        pnode pbak1, pmove;
        //判断右子树是否为空,为空,只需要重接左子树
        if ((*root)->right == NULL)
        {
            //把当前结点的左子树接上去就可以了
            pbak1 = *root;//当前结点备份等会释放
            //改变在栈上面一级指针的指向
            *root = (*root)->left;
            //删除
            free(pbak1); 
        }
        //左子树为空的情况下,只需要重接右子树就行了
        else if ((*root)->left == NULL)
        {
            //删除结点的空间备份
            pbak1 = *root;
            *root = (*root)->right;//改变栈上结点的指向
            free(pbak1);
        }
        //左右子树都不为空
        else
        {
            //我们要找到直接前驱或者一个直接后继
            //前驱就是当前结点下一个结点左边结点的右边(尽头),所以先把root指向了左结点
            pbak1 = *root;//删除结点的一个备份
            pmove = (*root)->left;//左边等会要接接上
            //再来循环右边
            //注意的问题是我们需要指向一个直接前驱的父结点
            //以便于用来更改当前的子树结点,也就是被删除结点的下一个结点要连接上去
            while (pmove->right)
            {
                pbak1 = pmove;//前驱结点的父节点
                pmove = pmove->right;//这个是指向了我们需要的前驱结点
            }

            //s指向了前驱结点,将s放到root结点上面
            (*root)->key = pmove->key;//改变了值,不是地址,等会吧pmove给释放掉

            //重接一下下面结点的子树
            //如果pbak1没有移动过,那么pbak1->left = pmove ->left;
            if (pbak1 == *root)
            {
                pbak1->left = pmove->left;
            }
            else 
            {
                //如果移动过,那么pbak1->right就要改变
                pbak1->right = pmove->left;
            }
            //释放掉pmove这个结点
            free(pmove);
        }
        return 1;
    }
    //没有找到的情况下,我们需要遍历树
    else if (key < (*root)->key) 
    {
        //直接走左子树
        //这里必须return ,不然找到了也会false
        return delete_bst(&(*root)->left, key);
    }
    else if (key > (*root)->key)
    {
        //大于当前结点就直接走右子树
        return delete_bst(&(*root)->right, key);
    }
    return 0;
}

//查找元素,找到返回结点指针,没找到返回NULL
//找结点,传入一个一级指针就好了
pnode search_bst(pnode root, key_type key)
{
    if (root == NULL)
    {
        return NULL;
    }
    //查找右子树
    if (key > root->key)
    {
        return search_bst(root->right, key);
    }
    //查找左子树
    else if (key < root->key)
    {
        return search_bst(root->left, key);
    }
    else 
    {
        return root;
    }
}

//查找最小的关键字,空树时返回NULL
pnode search_min_bst(pnode root)
{
    if (root == NULL)
    {
        return NULL;
    }
    //最小的话应该就是最左边孩子
    if (root->left == NULL)
    {
        return root;//叶子结点下面都是NULL
    }
    else 
    {
        return search_min_bst(root->left);
    }
}

//查找最大关键字,空树时返回NULL
pnode search_max_bst(pnode root)
{
    if (root == NULL)
    {
        return NULL;
    }
    //找到最后的孩子
    if (root->right == NULL)
    {
        return root;
    }
    else
    {
        //一直往右边找,直到没有有孩子结点
        return search_max_bst(root->right);
    }
}

//中序遍历二叉树
void inorder_traverse_bst(pnode root)
{
    if (root != NULL)
    {
        //遍历左子树
        //先走到最左边,依次调用结束,返回打印
        inorder_traverse_bst(root->left);
        //走到最后一个结束,打印,中间根结点也会打印
        printf("%d ", root->key);
        //然后走右边开始打印
        inorder_traverse_bst(root->right);
    }
}



int main()
{
    //创建一棵二叉树
    pnode root = NULL;
    insert_bst(&root, 3);
    insert_bst(&root, 8);
    insert_bst(&root, 2);
    insert_bst(&root, 5);
    insert_bst(&root, 4);
    insert_bst(&root, 9);
    insert_bst(&root, 11);

    //中序遍历二叉树
    inorder_traverse_bst(root);

    delete_bst(&root, 2);
    printf("\n---------------------\n");

    inorder_traverse_bst(root);

    return 0;
}




再来看java的运行代码(algorithm/bst1)

package com.pxx.tree.bst1;
class Node {
    int key;
    Node left, right;

    //这里就是在new的时候可以出初始化一个头结点
    public Node(int key) {
        this.key = key;
        this.left = this.right = null;
    }
}

class BstTree {
    //插入结点
    public Node insertBst(Node root, int key) {
        if (root == null) {
            //直接返回这个新结点
            //指到最后可添加位置,也是直接指向这个新节点
            return new Node(key);
        }
        if (key < root.key) {
            //往左边走
            root.left = insertBst(root.left, key);
        } else if(key > root.key) {
            //往右边走
            root.right = insertBst(root.right, key);
        }
        return root;//这里返回root的意思也就是中间的结点必须连上
    }

    //删除结点
    public Node deleteBST(Node root, int key) {
        if (root == null) {
            return root;
        }

        if (key < root.key) {
            root.left = deleteBST(root.left, key);
        } else if (key > root.key) {
            root.right = deleteBST(root.right, key);
        } else {
            //找到了这个结点
            if (root.left == null) {
                //直接返回这个结点的右结点给上一个节点
                return root.right;
            } else if (root.right == null) {
                return root.left;
            }

            //上面都没有进入,说明有左右子树,需要结点上一移动
            //先改变查找到结点的值,我们需要用它的直接后继来替换
            //也就是找到它右边的结点,然后不停的左边,一直到尽头
            root.key = minValue(root.right);
            //改变结点之间的连接
            root.right = deleteBST(root.right, root.key);
        }
        return root;
    }

    // 寻找最小值
    //从某个结点一直找到最左边就是最小值
    public int minValue(Node root) {
        while (root != null && root.left != null) {
            root = root.left;
        }
        return root.key;
    }

    //中序遍历这个结点
    public void inorderTraverseBst(Node root) {
        if (root != null) {
            //先打印左边
            inorderTraverseBst(root.left);
            System.out.print(root.key + " ");
            inorderTraverseBst(root.right);
        }
    }

    //查找某一个元素
    public Node searchBST(Node root, int key) {
        if (root == null || root.key == key) {
            return root;
        }

        if (key < root.key) {
            return searchBST(root.left, key);
        }

        return searchBST(root.right, key);
    }



}

public class Solution {
    public static void main(String[] args) {
        BstTree bstTree = new BstTree();
        Node root = null;
        //root在堆上就已经建立空间
        root = bstTree.insertBst(root, 3);
        bstTree.insertBst(root, 8);
        bstTree.insertBst(root,2);
        bstTree.insertBst(root,5);
        bstTree.insertBst(root,4);
        bstTree.insertBst(root,9);
        bstTree.insertBst(root,1);

        //中序遍历这片空间
        bstTree.inorderTraverseBst(root);

        System.out.println("-----------------");
       bstTree.deleteBST(root,2);
       bstTree.deleteBST(root,8);

        bstTree.inorderTraverseBst(root);
    }

}

好了,说到这。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/130986.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

口水战,余承东从没输过,小鹏最终只能低头和解

小鹏汽车创始人何小鹏近日发言称与余承东握手言和&#xff0c;感谢余总的大度&#xff0c;还表示与余承东探讨了技术路线&#xff0c;双方成为好朋友&#xff0c;可以看出这场口水战最终的赢家还是余承东。 这场口水战先以何小鹏吐槽友商的AEB误触太多&#xff0c;还声言99%是假…

基于springboot实现家具商城管理系统项目【项目源码】计算机毕业设计

基于springboot实现家具商城管理系统演示 Java语言简介 Java是由SUN公司推出&#xff0c;该公司于2010年被oracle公司收购。Java本是印度尼西亚的一个叫做爪洼岛的英文名称&#xff0c;也因此得来java是一杯正冒着热气咖啡的标识。Java语言在移动互联网的大背景下具备了显著的…

VMware部署CentOS7

一、创建虚拟机 1、点击新建虚拟机 2、选择自定义 下一步 3、点击下一步 4、选择稍后安装操作系统 5、选择linux 下一步 6、选择要安装的centos 版本 这里选择centos7 7、自定义虚拟机名称 设置虚拟机运行空间 8、配置处理器&#xff0c;使用默认 1个处理器 1核 9、修改虚拟机…

企业级操作之STM32项目版本管理方法

在MCU开发过程中&#xff0c;有时候需要软件的迭代&#xff0c;比如从V1.9升级到V1.10&#xff0c;或者从V23.09.23升级到V23.09.24&#xff0c;我们常常通过手动改动字符串或者数组来实现这个功能&#xff0c;从现在开始&#xff0c;我们会使用Keil的内置宏__DATE__和__TIME__…

wav格式如何转mp3?

wav格式如何转mp3&#xff1f;WAV格式是一种高品质的音频文件格式&#xff0c;其采用无损压缩技术存储音频数据。通常&#xff0c;WAV文件使用PCM编码方式将声音信号转换为数字信号&#xff0c;并按照一定规则存储到文件中。这种编码方式可以确保音频数据的完整性和准确性&…

python注释(快捷键)

首先介绍以下三种注释方式&#xff1a; # 123&#xff08;单行注释&#xff09; """123"""&#xff08;多行注释&#xff09; 123&#xff08;多行注释&#xff09; 下面介绍一下快捷键&#xff1a; Ctrl/ 注释单行&#xff1a;指针只要在这行代…

关于近期360自动屏保导致的问题

本身是一个好产品 但是对于某些应用就有点画蛇添足了 1、导致K3无法使用 K3中间层需要用户持续登入系统 2、导致系统停止工作 3、停止网络 4、占用系统资源 5、占用网络资源 6、占用硬件资源 。。。。。。 对于24小时开机的用户影响巨大 对于局域网信息点多的网络影响巨…

c语言:用指针解决有关字符串等问题

题目1&#xff1a;将一个字符串str的内容颠倒过来&#xff0c;并输出。 数据范围&#xff1a;1≤len(str)≤10000 代码和思路&#xff1a; #include <stdio.h> #include<string.h> int main() {char str1[10000];gets(str1);//读取字符串内容char* p&str1[…

无需标注海量数据,目标检测新范式OVD

当前大火的多模态GPT-4在视觉能力上只具备目标识别的能力&#xff0c;还无法完成更高难度的目标检测任务。而识别出图像或视频中物体的类别、位置和大小信息&#xff0c;是现实生产中众多人工智能应用的关键&#xff0c;例如自动驾驶中的行人车辆识别、安防监控应用中的人脸锁定…

Hbase 迁移小结:从实践中总结出的最佳迁移策略

在数据存储和处理领域&#xff0c;HBase作为一种分布式、可扩展的NoSQL数据库&#xff0c;被广泛应用于大规模数据的存储和分析。然而&#xff0c;随着业务需求的变化和技术发展的进步&#xff0c;有时候我们需要将现有的HBase数据迁移到其他环境或存储系统。HBase数据迁移是一…

Im即时通讯软件开发流程

一、需求分析 在进行软件开发之前&#xff0c;首先需要对需求进行分析&#xff0c;明确软件的功能和用户群体。即时通讯软件作为一款通讯工具&#xff0c;需要具备基本的通讯功能&#xff0c;例如聊天、文件传输、群聊等。除此之外&#xff0c;还需具备更多的特色功能以满足不…

​做好研发管理的三个条件​

1.制造鼓励创新的环境 要做好研发管理&#xff0c;首先要制造一个鼓励创新、适合研发的环境&#xff0c;必须采取弹性而目标化的管理&#xff0c;不以死板的制度限制员工的创意&#xff0c;必须要求实质的成果。 2.融入行销观念 将行销的观念融入研发中&#xff1a;为使有限的…

Linux系统编程——文件的光标移动

光标移动(lseek) 主要用于不断对文件写入数据或读取数据的的用法&#xff0c;每次写入数据后光标在数据尾&#xff0c;若要进行读取则只会没法读取到光标前的数据&#xff0c;这个时候就不需要重启文件&#xff0c;只需对光标位置做出调整就可以读取数据 使用lseek函数需要包…

十个使用Spring Cloud和Java创建微服务的实践案例

在使用Java构建微服务时&#xff0c;许多人认为只要学习一些微服务设计模式就足够了&#xff0c;比如CQRS、SAGA或每个微服务一个数据库。虽然这是正确的&#xff0c;但同时学习一些通用的最佳实践也是很有意义的。本文分享一些最佳实践。 1 设计模块化的微服务 微服务应该专…

仙侠类型游戏开发2D3D仙侠古风游戏

仙侠类游戏是一种以仙侠文化为背景的角色扮演游戏&#xff0c;玩家在游戏中扮演修仙者或武侠&#xff0c;通过修炼技能、完成任务和与其他玩家互动&#xff0c;逐步提升角色的实力和境界。这类游戏通常融合了仙侠小说中的幻想元素、武侠的武技和修仙的奇遇&#xff0c;创造了一…

数字化工厂管理系统的三个关键技术是什么

随着科技的飞速发展&#xff0c;数字化工厂管理系统已经成为了现代制造业的重要发展方向。数字化工厂管理系统通过充分运用建模技术、仿真技术和单一数据源技术&#xff0c;实现了产品设计和生产的虚拟化&#xff0c;为制造业带来了前所未有的效率和创新能力。本文将深入探讨这…

Ubuntu 20.04编译Chrome浏览器

本文记录chrome浏览器编译过程&#xff0c;帮助大家避坑qaq 官网文档&#xff1a;https://chromium.googlesource.com/chromium/src//main/docs/linux/build_instructions.md 一.系统要求 一台64位的英特尔机器&#xff0c;至少需要8GB的RAM。强烈推荐超过16GB。至少需要100…

嵌入式系统中,输入网址之后,发生了什么?

让我们一步一步地来看这个过程。 步骤1&#xff1a; 用户在浏览器中输入一个URL&#xff08;比如www.bytebytego.com&#xff09;&#xff0c;然后按下回车键。首先&#xff0c;我们需要将这个URL转换成一个IP地址。通常&#xff0c;这个映射关系会被存储在缓存中&#xff0c…

Jmeter —— jmeter参数化实现

jmeter参数化 在实际的测试工作中&#xff0c;我们经常需要对多组不同的输入数据&#xff0c;进行同样的测试操作步骤&#xff0c;以验证我们的软件的功能。这种测试方式在业界称为数据驱动测试&#xff0c; 而在实际测试工作中&#xff0c;测试工具中实现不同数据输入的过程称…

Leetcode刷题详解——全排列 II

1. 题目链接&#xff1a;47. 全排列 II 2. 题目描述&#xff1a; 给定一个可包含重复数字的序列 nums &#xff0c;按任意顺序 返回所有不重复的全排列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,2] 输出&#xff1a; [[1,1,2],[1,2,1],[2,1,1]]示例 2&#xff1a; 输…