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

news2025/4/7 6:03:51

什么是排序树

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

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

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

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

 

 

 二叉排序树的操作

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

 存储结构

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

 插入算法

 

 下面插入一个图解

上面的×就表示会在当前位置给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);
    }

}

好了,说到这。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一致性算法介绍(二)

1.4. NWR N &#xff1a;在分布式存储系统中&#xff0c;有 多少份备份数据 W &#xff1a;代表一次成功的更新操作要求至少有 w 份数据写入成功 R &#xff1a; 代表一次成功的读数据操作要求至少有 R 份数据成功读取 NWR值的不同组合会产生不同的一致性效果&#xff0c;当WR…

【LeetCode刷题笔记】堆和优先级队列

358. K 距离间隔重排字符串 解题思路: 大根堆 + 队列 , 1)首先 计数数组 统计 每个字符出现的次数 ,然后将 计数 > 0 的 字符 和 次数 一起放入 大根堆 ,大根堆中

docker创建并访问本地前端

docker创建并访问本地前端&#xff0c;直接上命令&#xff1a; 安装nginx镜像&#xff1a; docker pull nginx 查看已安装的nginx&#xff1a; docker images 创建DockerFile文件&#xff0c;直接在当前文件夹种创建 touch Dockerfile 在Dockerfile写入内容&#xff1a; F…

HHDESK端口转发监控服务

端口转发是一种网络技术&#xff0c;用于将外部网络请求转发到内部网络中的特定设备或服务。它允许通过公共网络访问内部网络中的资源&#xff0c;提供了灵活性和便利性。 传统的端口转发方式是通过配置路由器的端口映射&#xff0c;但这需要具备网络知识和一定的技术操作&…

操作系统 | 编写内核

&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《操作系统实验室》&#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 目录结构 1. 操作系统实验之编写内核 1.1 实验目的 1.2 实验内容 1.3 实验步骤 1.4 实验过程 …

Git系列之Git集成开发工具及git扩展使用

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是君易--鑨&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《Git实战开发》。&#x1f3af;&#x1f3af; &a…

MATLAB中deconvwnr函数用法

目录 语法 说明 示例 使用 Wiener 滤波对图像进行去模糊处理 deconvwnr函数的功能是使用 Wiener 滤波对图像进行去模糊处理。 语法 J deconvwnr(I,psf,nsr) J deconvwnr(I,psf,ncorr,icorr) J deconvwnr(I,psf) 说明 J deconvwnr(I,psf,nsr) 使用 Wiener 滤波算法对…

Clickhouse SQL

insert insert操作和mysql一致 标准语法&#xff1a;insert into [table_name] values(…),(….)从表到表的插入&#xff1a;insert into [table_name] select a,b,c from [table_name_2] update 和 delete ClickHouse 提供了 Delete 和 Update 的能力&#xff0c;这类操作…

深入理解 TCP;场景复现,掌握鲜为人知的细节

握手失败 第一次握手丢失了&#xff0c;会发生什么&#xff1f; 当客户端想和服务端建立 TCP 连接的时候&#xff0c;首先第一个发的就是 SYN 报文&#xff0c;然后进入到 SYN_SENT 状态。 在这之后&#xff0c;如果客户端迟迟收不到服务端的 SYN-ACK 报文&#xff08;第二次…

管易云与电商平台的无代码集成:实现API连接与用户运营

管易云简介及其与电商平台的合作 金蝶管易云是金蝶集团旗下以电商为核心业务的子公司&#xff0c;是国内最早的电商ERP服务商之一&#xff0c;总部在上海&#xff0c;与淘宝、天猫、 京东、拼多多、抖音等300多家主流电商平台建立合作关系&#xff0c;同时管易云是互联网平台首…

python注释(快捷键)

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

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

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

C#,数值计算——函数计算,Epsalg的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// Convergence acceleration of a sequence by the algorithm.Initialize by /// calling the constructor with arguments nmax, an upper bound on the /// number of term…

高阶组件和Hooks

目录 1. 高阶组件&#xff08;Higher-Order Components&#xff09; 1.1 创建高阶组件 1.2 使用高阶组件 2. Hooks 2.1 使用useState Hook管理状态 2.2 创建自定义Hook 结论 1. 高阶组件&#xff08;Higher-Order Components&#xff09; 高阶组件是一个接受一个组件作为…

Apache Airflow (四) :Airflow 调度shell命令

&#x1f3e1; 个人主页&#xff1a;IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;加入大数据技术讨论群聊&#xff0c;获取更多大数据资料。 &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你大数据的个人空间-豹…

用于强化学习的置换不变神经网络

一、介绍 如果强化学习代理提供的输入在训练中未明确定义&#xff0c;则通常表现不佳。一种新方法使 RL 代理能够正常运行&#xff0c;即使受到损坏、不完整或混乱的输入的影响也是如此。 “大脑能够使用来自皮肤的信息&#xff0c;就好像它来自眼睛一样。我们不是用眼睛看&…

Ubuntu(WSL) mysql8.0.31 源码安装

要在 Ubuntu 上使用调试功能安装 MySQL 8.0 的源码&#xff0c;可以按照以下详细步骤进行操作&#xff1a; 1. 更新系统 首先&#xff0c;确保你的 Ubuntu 系统是最新的。运行以下命令更新系统软件包&#xff1a; sudo apt update sudo apt upgrade 2. 下载 MySQL 源码 访…

数字马力笔试面试复盘

笔试——10月9日19&#xff1a;00 单选&#xff1a;30题 16.如何获取AJAX 请求的响应状态码? A通过AJAX对象的 statusCode 属性获取 B通过AJAX对象的responseText 属性获取C通过AJAX对象的status 属性获取 D通过AJAX对象的responseCode属性获取 答案&#xff1a;可以通过AJAX…

Docker从零开始学习,及常用命令大全(附带代码讲解)

Docker从零开始&#xff0c;及常用命令大全&#xff08;附带代码讲解&#xff09; docker是一种开源的应用容器引擎&#xff0c;可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的Linux机器上&#xff0c;也可以实现虚拟化。…

线程安全问题解析

线程内存模型 线程在工作的时候&#xff0c;如果涉及到需要访问对象的某个成员变量&#xff0c;比如下面的这个类里的amount 属性&#xff1a; class Goods {private int amount;// balabala.....} 线程在运行期间&#xff0c;首先把这个属性从主内存里load进自己的工作内存&…