数据结构与算法-B(B-)树的简单实现

news2024/11/28 6:48:42

B(B-)树定义

B树(或B-tree)是一个在计算机科学中广泛使用的数据结构,它是一种自平衡的树,能够保持数据有序。

以下是B树的特性

  1. 每个节点最多右m个孩子,二叉树是B-树的特例,其有2个孩子。
  2. 除了叶节点和根节点以外,每个内部节点至少有Math.ceil(m/2)个孩子。
  3. 如果根不是叶节点,则至少有两个孩子。
  4. 所有叶节点都出现在一层,也就是说从根到叶节点点的度(层,高度)一样。
  5. 具有K个孩子的非叶节点包含 K-1个数据,在节点内部的键值是递增的。

B树模型

B-树节点

B树的阶: B树的节点(除根节点外) 最多 有多少个孩子结点(子树),一般用字母 M 表示阶数。

如果所示节点可以连接4个树,此树为四阶B树。

每个节点中存储的数据最多不超过M-1 = 3;

image-20240619142939740

B-树的一般结构

image-20240619114028879

查询

查询在B-树中数据,如果能找到我们需要知道节点对象和节点中哪个位置(每个节点内存储多个数据)

如果没有查询到我们需要记录当前的节点对象及位置为-1(表示不存在)。

NodeBind类

定义一个NodeBind类包装节点和数据的位置。

/**
 * 绑定数据在节点中位置类
 */
public class NodeBind {
    private Node node;  //当前节点
    private int index; //当前节点中数据的位置

    public Node getNode() {
        return node;
    }

    public void setNode(Node node) {
        this.node = node;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public NodeBind(Node node, int index) {
        this.node = node;
        this.index = index;
    }

    @Override
    public String toString() {
        return "Pair{" +
                "node=" + node +
                ", index=" + index +
                '}';
    }
}

查询实现

从根节点开始遍历,先从根节点开始检查是否存有要查找的数据,如果找到则返回Pair对象,如果没有则记录最后一个扫描到的对象,将其包装为Pair对象返回。

/**
 * 查询data在B树的节点位置
 *
 * @param data
 * @return
 */
public Pair search(int data) {
    Node curr = root;
    Node parent = null;
    while (curr != null) {
        int rowIndex = 0;
        while (rowIndex < curr.size) {
            List<Integer> datas = curr.datas;
            int index = datas.indexOf(data);
            if (index != -1) {
                return new Pair(curr,index);
            } else if (data > datas.get(rowIndex)) {
                rowIndex++;
            } else {
                break;
            }
        }
        parent = curr;
        curr = curr.children.get(rowIndex);
    }
    return new Pair(parent,-1);
}

添加数据

我们采用的策略是先暂时存储数据,当存入后数据超出其长度进行分裂处理。

辅助方法指定节点添加数据

private void insertBySort(Node node, int data) {
    int index = node.size;
    for (int i = 0; i < node.datas.size(); i++) {
        if (data < node.datas.get(i)) {
            index = i;
            break;
        }
    }
    node.datas.add(index, data);
    node.size++;
}

添加新数据

我们向B树中添加数据{12,45,9,78,80,5,3,79,20},跟踪B树的添加过程。

添加第一个元素12

创建新节点即为root节点,在datas中存入数据12。

image-20240622143104011
if (root == null) {//如果根节点不存在
    root = new Node();
    root.datas.add(data);
    root.size++;
    return;
}

添加数据45

在根节点的第二个位置添加45,找到要添加的节点。 节点的数据安装升序排序存放。

image-20240622143256238
/*
从B-树中查找节点是否存在
 */
Pair findNode = search(data);
//数据已经在B树中,只更新不插入
if (findNode.getIndex() != -1) return;

/*
  如果没有找到则插入节点
   - 当前要插入的节点没有满
   - 当前要插入的节点已满,需要分裂节点
 */
Node curr = findNode.getNode();

insertBySort(curr, data);

if (curr.size <= M - 1) { //如果当前节点的数据没有满,最大存储M-1数据
    return;
}

添加数据9

查找位置添加节点,代码和4.2.3一致。

image-20240622143545722

添加数据78

重复添加过程,需要判断当前节点容量是否已满,当前节点的数据容量: size<=M-1

如果超过容量则需要分裂当前节点。

image-20240622143845811
....添加节点代码
if (curr.size <= M - 1) { //如果当前节点的数据没有满,最大存储M-1数据
    return;
} else { //当前节点已满
    System.out.println("当前节点已满,准备分裂...");
    split(curr);
}

分裂节点

以当前节点的中间节点为中心将左右分裂为两个节点

  • 原节点保留左侧数据
  • 新建节点保留右侧数据
  • 如果当前节点为根节点,需要新创建节点为根节点,存储中间节点数据。

image-20240622144624956

public void split(Node curr) {
    int mid = curr.size / 2; //找到中间节点
    int midVal = curr.datas.get(mid); //记录中间节点的值
    Node parent = curr.parent; //记录当前节点的父节点

    /*
     *  创建新节点,用来存储分裂的右侧节点
     */
    Node newNode = new Node();

    for (int i = mid + 1; i < curr.size; i++) {
        newNode.datas.add(curr.datas.get(i)); //新节点循环添加mid+1开始的节点
        newNode.size++; //新节点数据大小更新
        curr.datas.remove(i); //删除右侧数据(右侧节点放中间朝右的数据)
        i--; //防止引起集合索引的移动(由于删除了数据,集合大小会改变,当前位置也会调整,回退到删除为止)
        curr.size--; //当前节点的数据-1处理
    }:
}

如果当前节点是根节点,分裂两个子树,实际上当前节点和分裂节点的每一个parent要连接新的parent,parent的每一个孩子节点的孩子节点要引用这两个节点(可以自行完成,繁琐但不难)。

/*
 *  判断当前节点是否为根节点
 */
if (parent == null) {//父节点为空,当前节点为根节点
    root = new Node();//产生一个新的父节点
    root.datas.add(midVal); //拷贝当前节点的中间值到父节点中
    curr.datas.remove(Integer.valueOf(midVal)); //从当前对象中移除已经添加到root上的数据
    curr.size--;
    root.size++; //父节点长度更新
    root.children.add(curr); //父节点的孩子节点连接子节点
    curr.parent = root; //当前节点的父节点变成新的父节点
    root.children.add(newNode); //添加右侧的子节点到父节点的孩子节点中
    newNode.parent = root; //右接点的父节点连接新父节点
    return;
}

添加80

image-20240622145021014

添加5

image-20240622145120905

添加3

image-20240622145150175

再次分裂

  • 左侧分裂时检查父节点是否有空位,如果有把中间节点(9)放到父节点去
  • 分裂出一下新节点存储中间节点的右侧数据 12
  • 递归向上操作(如果父节点已满,继续向上操作直到父节点为根节点结束)

image-20240622151412419

else {//如果parent不是根节点,且当前节点已满
    //将中间值拷贝到parent节点中
    insertBySort(parent, midVal);
    curr.datas.remove(Integer.valueOf(midVal));
    curr.size--;
    parent.children.add(newNode); //将新
    parent.children.sort((o1, o2) -> o1.datas.get(o1.size - 1) - o2.datas.get(0)); //对孩子节点安装正序排序
    if (parent.size >= M) {
        split(parent);   //低估操作父节点
    }
}

添加数据20

image-20240622151756555

完整代码实现

B(B-)树节点

public class Node {
    public List<Integer> datas; //记录数据
    public List<Node> children; //子节点
    public Node parent; //父节点
    public int size; //节点内存储的值数量

    public Node() {
        datas = new ArrayList<>();
        children = new ArrayList<>();
    }


    @Override
    public String toString() {
        return "Node{" +
                "datas=" + datas +
                ", size=" + size +
                '}';
    }
}

B(B-)树的实现

辅助类NodeBind

辅助类NodeBind用来绑定在查询节点时,将节点和查询到B-树位置绑定。

如果节点没有查到,绑定当前节点,-1;如果查找到节点绑定当前节点,数据在节点中的位置。



/**
 * 绑定数据在节点中位置类
 */
public class NodeBind {
    private Node node;  //当前节点
    private int index; //当前节点中数据的位置

    public Node getNode() {
        return node;
    }

    public void setNode(Node node) {
        this.node = node;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public NodeBind(Node node, int index) {
        this.node = node;
        this.index = index;
    }

    @Override
    public String toString() {
        return "Pair{" +
                "node=" + node +
                ", index=" + index +
                '}';
    }
}

添加节点和分裂节点的实现

package com.ffyc.tree;

import com.ffyc.tree.node.Node;
import com.ffyc.tree.node.NodeBind;

import java.util.*;

public class BTree {

    private Node root;
    // M表示B-树的阶数
    // 根节点的数据数: [1,M]之间

    private final int M = 4; //4阶B-树

    /**
     * 添加节点数据
     * @param data
     */
    public void add(int data) {
        if (root == null) {//如果根节点不存在
            root = new Node();
            root.datas.add(data);
            root.size++;
            return;
        }
        /*
        从B-树中查找节点是否存在
         */
        NodeBind findNode = search(data);
        //数据已经在B树中,只更新不插入
        if (findNode.getIndex() != -1) return;

        /*
          如果没有找到则插入节点
           - 当前要插入的节点没有满
           - 当前要插入的节点已满,需要分裂节点
         */
        Node curr = findNode.getNode();

        insertBySort(curr, data);

        if (curr.size <= M - 1) { //如果当前节点的数据没有满,最大存储M-1数据
            return;
        } else { //当前节点已满
            System.out.println("当前节点已满,准备分裂...");
            split(curr);
        }
    }

    /**
     * B树的节点进行分裂
     * @param curr 当前要分裂的节点
     */
    public void split(Node curr) {
        int mid = curr.size / 2; //找到中间节点
        int midVal = curr.datas.get(mid);
        Node parent = curr.parent;

        /*
         *  创建新节点,用来存储分裂的右侧节点
         */
        Node newNode = new Node();
        //newNode.datas = curr.datas.subList(mid + 1, curr.size);

        for (int i = mid + 1; i < curr.size; i++) {
            newNode.datas.add(curr.datas.get(i));
            newNode.size++;
            curr.datas.remove(i);
            i--;
            curr.size--;
        }
        /*
         *  判断当前节点是否为根节点
         */
        if (parent == null) {//父节点为空,当前节点为根节点
            root = new Node();//产生一个新的父节点
            root.datas.add(midVal); //拷贝当前节点的中间值到父节点中
            curr.datas.remove(Integer.valueOf(midVal)); //从当前对象中移除已经添加到root上的数据
            curr.size--;
            root.size++; //父节点长度更新
            root.children.add(curr); //父节点的孩子节点连接子节点
            curr.parent = root; //当前节点的父节点变成新的父节点
            root.children.add(newNode); //添加右侧的子节点到父节点的孩子节点中
            newNode.parent = root; //右接点的父节点连接新父节点
            return;
        } else {//如果parent不是根节点,且当前节点已满
            //将中间值拷贝到parent节点中
            insertBySort(parent, midVal);
            curr.datas.remove(Integer.valueOf(midVal));
            curr.size--;
            parent.children.add(newNode); //将新
            parent.children.sort((o1, o2) -> o1.datas.get(o1.size - 1) - o2.datas.get(0));
            if (parent.size >= M) {
                split(parent);
            }
        }
    }


    /**
     * 在节点中按顺序插入数据
     * @param node
     * @param data
     */
    private void insertBySort(Node node, int data) {
        int index = node.size;
        for (int i = 0; i < node.datas.size(); i++) {
            if (data < node.datas.get(i)) {
                index = i;
                break;
            }
        }
        node.datas.add(index, data);
        node.size++;
    }


    /**
     * 查询data在B树的节点位置
     *
     * @param data
     * @return
     */
    public NodeBind search(int data) {
        Node curr = root;  //记录根节点,从根节点开始遍历
        Node parent = curr.parent;  //记录父节点

        while (curr != null) {   //从root遍历所有节点
            int rowIndex = 0;  // 每个节点中数据的存储位置索引
            while (rowIndex < curr.size) { //扫描节点中所有数据
                List<Integer> datas = curr.datas;
                int index = datas.indexOf(data); //判断节点中的数据汇总是否包含要查找的数据
                if (index != -1) { //数据存在,返回终止查找
                    return new NodeBind(curr, index);
                } else if (data > datas.get(rowIndex)) { //数据大于节点中的数据,继续查找下一个数据
                    rowIndex++;
                } else {//找到比节点中数据小的位置跳出循环,表示查找完毕没有找到
                    break;
                }
            }
            parent = curr;
            try {
                curr = curr.children.get(rowIndex);
            } catch (IndexOutOfBoundsException e) {
                curr = null;
            }
        }
        return new NodeBind(parent, -1);
    }

    public void print() {
        Queue<Node> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {   //从root遍历所有节点
            Node curr = queue.poll();
            for (int i = 0; i < curr.size; i++) {
                System.out.print(curr.datas.get(i) + "\t");
            }

            List<Node> children = curr.children;
            for (Node node : children) {
                queue.offer(node);
            }

        }
    }

    public Node getRoot() {
        return this.root;
    }


    /**
     * 测试代码
     */
    public static void main(String[] args) {
        BTree bTree = new BTree();

        bTree.add(12);
        bTree.add(45);
        bTree.add(9);
        bTree.add(78);
        bTree.add(80);
        bTree.add(5);
        bTree.add(3);
        bTree.add(79);
        bTree.add(20);
        bTree.add(1);



        bTree.print();
    }
}

运行结果

image-20240622154318947

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

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

相关文章

探索 Kubernetes v1.30:与 MinIO 部署相关的增强功能

Kubernetes v1.30 的发布带来了一系列更新&#xff0c;其中一些更新对于高性能 Kubernetes 原生对象存储 MinIO 的用户来说可能意义重大。随着组织继续利用这两种技术来提供可扩展且安全的存储解决方案&#xff0c;了解这些新 Kubernetes 功能的影响非常重要。以下是 Kubernete…

华为开发者大会:全场景智能操作系统HarmonyOS NEXT

文章目录 一、全场景智能操作系统 - HarmonyOS NEXT1.1 系统特性1.2 关于架构、体验和生态 二、应用案例2.1 蚂蚁mpaas平台的性能表现 三、新版本应用框架发布3.1 新语言发布3.2 新数据库发布3.3 新版本编译器的发布 四、CodeArts和DataArts4.1 CodeArts4.2 DataArts 五、总结 …

网络富集显著性检验NEST(?)

https://doi.org/10.1002/hbm.26714 背景 一般情况下&#xff0c;研究者通过评估统计量较大的脑区与功能网络重叠的情况&#xff0c;或者计算网络的体素占比&#xff0c;来确定行为和功能网络的相关性。NEST能检测行为表型和大脑表型的相关性是否富集在特定的功能网络中。例如下…

[数据集][目标检测]棉花叶子害虫检测数据集VOC+YOLO格式595张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;595 标注数量(xml文件个数)&#xff1a;595 标注数量(txt文件个数)&#xff1a;595 标注类别…

手动重新平衡您的 MinIO Modern Datalake

当通过添加新的服务器池来扩展 MinIO Modern Datalake 部署时&#xff0c;默认情况下它不会重新平衡对象。相反&#xff0c;MinIO 会将新文件/对象写入具有更多可用空间的池中。MinIO 的手动重新平衡触发器会扫描整个部署&#xff0c;然后在服务器池周围移动对象&#xff08;如…

textarea标签改写为富文本框编辑器KindEditor

下载 - KindEditor - 在线HTML编辑器 KindEditor的简单使用-CSDN博客 一、 Maven需要的依赖&#xff1a; 如果依赖无法下载&#xff0c;可以多添加几个私服地址&#xff1a; 在Maven框架中加入镜像私服 <mirrors><!-- mirror| Specifies a repository mirror site to…

编程书籍的枯燥真相:你也有同样的感受吗?

讲动人的故事,写懂人的代码 我得实话实说,你可能已经发现市面上的大部分编程入门书籍有些枯燥。这个问题的根源在于许多作者把本应该充满乐趣和吸引力的入门指南,写得就像一本沉闷的参考手册。这就好比把一本充满冒险和乐趣的旅行日记,写成了一本单调乏味的字典。 我完全理…

Apache Paimon系列之:Append Table和Append Queue

Apache Paimon系列之&#xff1a;Append Table和Append Queue 一、Append Table二、Data Distribution三、自动小文件合并四、Append Queue五、压缩六、Streaming Source七、Watermark Definition八、Bounded Stream 一、Append Table 如果表没有定义主键&#xff0c;则默认为…

密码学及其应用 —— 密码学概述

1 安全属性和机制 1.1 基本概念 1.1.1 三个核心概念 在讨论信息安全时&#xff0c;我们通常会谈到三个核心概念&#xff1a;保密性、完整性和可用性。这三个概念共同构成了信息安全的基础。 保密性&#xff1a;指的是确保信息只能被授权的人员访问。这就意味着信息在存储、传…

【React】AntD组件的使用--极客园--02.登录模块

基本结构搭建 实现步骤 在 Login/index.js 中创建登录页面基本结构在 Login 目录中创建 index.scss 文件&#xff0c;指定组件样式将 logo.png 和 login.png 拷贝到 assets 目录中 代码实现 pages/Login/index.js import ./index.scss import { Card, Form, Input, Button }…

AI语言文字工具类API实现自动化的写作

热门实用的AI语言文字工具类API是当今开发者们追逐的宝藏。这些API利用先进的人工智能和自然语言处理技术&#xff0c;为开发者提供了一系列实用而强大的语言文字处理能力。这些API包括了文本翻译、情感分析、智能写作、关键词提取、语言检测等功能&#xff0c;使得开发者能够轻…

使用java +paho mqtt编写模拟发布温度及订阅的过程

启动mqtt 服务 创建项目&#xff0c;在项目中添加模块 添加文件夹 添加maven依赖 <dependencies><dependency><groupId>org.eclipse.paho</groupId><artifactId>org.eclipse.paho.client.mqttv3</artifactId><version>1.2.0<…

提升研发效率:三品PLM解决方案在汽车汽配行业的实践

随着全球汽车市场的快速发展&#xff0c;中国汽车汽配行业迎来了前所未有的发展机遇。然而&#xff0c;在这一过程中&#xff0c;企业也面临着诸多挑战&#xff0c;如研发能力的提升、技术资料管理的复杂性、以及跨部门协作的困难等。为了应对这些挑战&#xff0c;三品产品生命…

90V转12V1A恒压WT6039

90V转12V1A恒压WT6039 WT6039降压DC-DC转换器芯片专为处理宽泛的电压输入范围设计&#xff0c;支持从12V至90V。该芯片集成了关键功能&#xff0c;如使能控制开关、参考电源、误差放大器、过热保护、限流保护及短路保护等&#xff0c;以确保系统在各种操作条件下的安全与稳定性…

NSSCTF-Web题目14

目录 [CISCN 2019华东南]Web11和[NISACTF 2022]midlevel 1、题目 2、知识点 3、思路 [HDCTF 2023]SearchMaster 1、题目 2、知识点 3、思路 [CISCN 2019华东南]Web11和[NISACTF 2022]midlevel 这两道题目一样 1、题目 2、知识点 SSTI&#xff08;服务端模板注入漏洞&…

攻防世界-2-1

下载附件&#xff0c;发现是一张损坏的png文件&#xff0c;扔winhex里面修改文件头 修改之后发现还是打不开&#xff0c;提示CRC错误&#xff0c;脚本跑一下 循环冗余校验CRC&#xff08;Cyclic Redundancy Check&#xff09;是数据通信领域常用的一种数据传输检错技术。通过在…

企业级Web项目中应该如何做单元测试、集成测试和功能测试?

先自我介绍下&#xff1a; 本人有过10年测试经验&#xff0c;也参与过公安部网络安全产品测试交付、华为4G 网络设备测试交付、腾讯QQ空间APP产品测试交付。 关于“企业级Web项目中应该如何做单元测试、集成测试和功能测试”这个问题&#xff0c;我想给大家唠唠&#xff0c;我…

kafka(五)spring-kafka(2)详解与demo

一、简单的收发消息demo 父工程pom&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&qu…

外包IT运维解决方案

随着企业信息化进程的不断深入&#xff0c;IT系统的复杂性和重要性日益增加。高效的IT运维服务对于保证业务连续性、提升企业竞争力至关重要。外包IT运维解决方案通过专业的服务和技术支持&#xff0c;帮助企业降低运维成本、提高运维效率和服务质量。 本文结合《外包IT运维解…

Go语言的诞生背景

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…