B树(B-tree)

B树(B-tree)

B树(B-tree)是一种自平衡的多路查找树,主要用于磁盘或其他直接存取的辅助存储设备

B树能够保持数据有序,并允许在对数时间内完成查找、插入及删除等操作
这种数据结构常被应用在数据库和文件系统的实现上

B树的特点包括:
B树为了表示节点个数 通常称为 M阶树
1.M阶树每个结点至多有M棵子树(M>=2)
2.每个非根节点至少有 M/2(向上取整)个孩子,至多有M个孩子。
3.每个叶子节点至少有 M/2-1 个关键字,至多有M-1个关键字,并以升序排列
4.所有叶子节点都在同一层
5.非叶子节点的关键字个数等于其孩子数减一
6.所有叶子节点不含有任何信息

按照子节点数 y:非根节点至少有 M/2(向上取整)个孩子,至多有M个孩子
M = 3, 2 <= y <= 3, 因此称为:2-3树
M = 4, 2 <= y <= 4, 因此称为:2-3-4树
M = 5, 3 <= y <= 5, 因此称为:3-5树
M = 7, 4 <= y <= 7, 因此称为:4-7树

B树的高度对于含有n个关键字的m阶B树,其高度为O(log n)
这种数据结构通过减少定位记录时所经历的中间过程,从而加快存取速度
与自平衡的二叉查找树不同,B树为系统大块数据的读写操作做了优化

下面先看 B树节点的定义

class BTNode<T> where T : IComparable<T>
{
    private BTNode<T> parentNode;        // 父节点
    private List<T> keyList;             // 关键字向量
    private List<BTNode<T>> childList;   // 子节点向量(其长度总比key多一)
}

B树每个节点存储的关键字是从小到大有序的:keyList 是从小到大排序的
B树 关键字个数 比 子节点 个数少 1 个,为什么?
因为 关键字 和 子节点 可以理解为这么样一个排序,假设一个 5 阶树的一个节点

childList[0],keyList[0],childList[1],keyList[1],childList[2]

可以看到 子节点 和 关键字 是交替出现的,并且 子节点 比 关键字 个数多 1 个

并且还有一个隐藏的信息
1.子树中所有关键字的值,比其右侧的关键字都小
2.子树中所有关键字的值,比其左侧的关键字都大
什么意思呢?
1.(childList[0] 子树下所有节点的关键字) 小于 keyList[0]
2. keyList[0] 小于 (childList[1] 子树下所有节点的关键字)

看下图 一个 5阶树
在这里插入图片描述
Node0 包含 三个关键字(25,39,66),四个子节点(Node1,Node2,Node3,Node4)
25 对应 keyList[0]
39 对应 keyList[1]
66 对应 keyList[2]

Node0 包含 两个关键字 (5, 13) < 25
Node1 包含 两个关键字 25 < (28, 30) < 39
Node2 包含 两个关键字 39 < (40, 55) < 66
Node3 包含 两个关键字 66 < (67, 68, 90)

B树在操作 插入、删除的过程中往往会导致
上溢:节点个数大于 B树限制
下移:节点个数小于 B树限制
需要通过一系列操作将树恢复平衡

B树操作逻辑
查询: number
1.根节点作为当前节点
2. number 顺次与当前节点的关键字作比较
如果 number < keyList[index],则 number 一定在 childList[index],令当前节点= childList[index] 循环执行 2
如果 number = keyList[index],则 在关键字中找到 了number,查询完成,返回节点
如果 number > keyList[index], index++,如果 index >= childList.Count,查找失败,返回,否则继续循环执行 2 ,比较下一个 关键字 keyList[index]

我在查询操作逻辑中隐含保留了一个 hot 节点,这个 hot 节点是最接近 number 的节点,如果查询成功,则 hot 不再起作用,如果查询失败,在 插入操作中 是有用处的

插入: number
1.先执行查询操作,如果查询到节点,则说明已经存在,不再添加,返回
2.查询逻辑中 保存的 hot 节点是最接近 numbe 的节点,我们将 number 插入到hot节点
3.令 number 与 keyList 中的数据比较
如果 keyList[i] < number, 将 number 插入到 keyList第 i + 1 位置,然后 childList 第 i + 2 位置插入一个空节点
如果 keyList 中的关键字 > number,将 number 插入到 keyList 第 0 个位置,然后 childList 第 1 个位置插入一个空节点
4. 如果 hot 节点发生上溢,做分裂处理

C#代码实现如下
B树节点定义

    /// <summary>
    /// B-树节点定义
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class BTNode<T> where T : IComparable<T>
    {
        private BTNode<T> parentNode;       // 父节点
        private List<T> keyList;            // 关键字向量
        private List<BTNode<T>> childList;  // 子节点向量(其长度总比key多一)

        public BTNode()
        {
            keyList = new List<T>();
            childList = new List<BTNode<T>>();
            childList.Insert(0, null);
        }

        public BTNode(T t, BTNode<T> lc, BTNode<T> rc)
        {
            parentNode = null;
            keyList.Insert(0, t);
            // 左右孩子
            childList.Insert(0, lc);
            childList.Insert(1, rc);

            if (null != lc)
            {
                lc.parentNode = this;
            }
            if (null != rc)
            {
                rc.parentNode = this;
            }
        }

        public BTNode<T> ParentNode
        {
            get { return parentNode; }
            set { parentNode = value; }
        }
            
        private List<T> KeyList
        {
            get { return keyList; }
            set { keyList = value; }
        }

        public int KeyCount
        {
            get { return KeyList.Count; }
        }

        public void InsertKey(int index, T key)
        {
            KeyList.Insert(index, key);
        }

        public T GetKey(int index)
        {
            return KeyList[index];
        }

        public void RemoveKeyAt(int index)
        {
            KeyList.RemoveAt(index);
        }

        public void SetKey(int index, T key)
        {
            KeyList[index] = key;
        }

        private List<BTNode<T>> ChildList
        {
            get { return childList; }
            set { childList = value; }
        }

        public void InsertChild(int index, BTNode<T> node)
        {
            ChildList.Insert(index, node);
            if (null != node)
            {
                node.parentNode = this;
            }
        }

        public BTNode<T> GetChild(int index)
        {
            return ChildList[index];
        }

        public void AddChild(BTNode<T> node)
        {
            InsertChild(ChildList.Count, node);
        }

        public BTNode<T> RemoveChildAt(int index)
        {
            BTNode<T> node = ChildList[index];
            ChildList.RemoveAt(index);
            return node;
        }

        public void SetChild(int index, BTNode<T> node)
        {
            ChildList[index] = node;
        }

        public int ChildCount
        {
            get { return ChildList.Count; }
        }
    }

B树实现

    /// <summary>
    /// B-树
    /// </summary>
    /// <typeparam name="T"></typeparam>
    class BTree<T> where T : IComparable<T>
    {
        private int _order;         // 介次
        protected BTNode<T> _root;  // 跟节点
        protected BTNode<T> _hot;   // search() 最后访问的非空节点位置

        public BTree(int order)
        {
            _order = order;
        }

        public BTNode<T> Root
        {
            get { return _root; }
            set { _root = value; }
        }

        /// <summary>
        /// 查找
        /// </summary>
        public BTNode<T> Search(T t)
        {
            BTNode<T> v = Root; // 从根节点触发
            _hot = null;

            while (null != v)
            {
                int index = -1;
                for (int i = 0; i < v.KeyCount; ++i)
                {
                    int compare = v.GetKey(i).CompareTo(t);
                    if (compare <= 0)
                    {
                        index = i;
                        if (compare == 0)
                        {
                            break;
                        }
                    }
                }

                // 若成功,则返回
                if (index >= 0 && v.GetKey(index).CompareTo(t) == 0)
                {
                    return v;
                }

                _hot = v;
                // 沿引用转至对应的下层子树,并载入其根
                v = v.ChildCount > (index + 1) ? v.GetChild(index + 1) : null;
            }
            // 若因 null == v 而退出,则意味着抵达外部节点
            return null; // 失败
        }

        /// <summary>
        /// 插入
        /// </summary>
        public bool Insert(T t)
        {
            BTNode<T> node = Search(t);
            if (null != node)
            {
                return false;
            }

            int index = -1;
            for (int i = 0; i < _hot.KeyCount; ++i)
            {
                int compare = _hot.GetKey(i).CompareTo(t);
                if (compare <= 0)
                {
                    index = i;
                    if (compare == 0)
                    {
                        break;
                    }
                }
            }

            _hot.InsertKey(index + 1, t);      // 将新关键码插至对应的位置
            _hot.InsertChild(index + 2, null);      // 创建一个空子树指针

            SolveOverflow(_hot); // 如发生上溢,需做分裂

            return true;
        }

        /// <summary>
        /// 删除
        /// </summary>
        public bool Remove(T t)
        {
            BTNode<T> node = Search(t);
            if (null == node)
            {
                return false;
            }

            int index = -1;
            for (int i = 0; i < node.KeyCount; ++i)
            {
                if(node.GetKey(i).CompareTo(t) == 0)
                {
                    index = i;
                    break;
                }
            }

            // node 不是叶子节点
            if (null != node.GetChild(0))
            {
                BTNode<T> u = node.GetChild(index + 1); // 在右子树中一直向左,即可
                while (null != u.GetChild(0))
                {
                    u = u.GetChild(0); // 找到 t 的后继(必需于某叶节点)
                }
                // 至此,node 必然位于最底层,且其中第 r 个关键码就是待删除者

                node.SetKey(index, u.GetKey(0));
                node = u;  // 并与之交换位置
                index = 0;
            }

            node.RemoveKeyAt(index);
            node.RemoveChildAt(index + 1);

            SolveUnderflow(node); // 如有必要,需做旋转或合并

            return false;
        }

        /// <summary>
        /// 上溢:因插入而上溢后的分裂处理
        /// </summary>
        private void SolveOverflow(BTNode<T> v)
        {
            if (_order >= v.ChildCount)
            {
                return; //递归基:当前节点并未上溢
            }

            int s = _order / 2; //轴点(此时应有_order = key.Count = child.Count - 1)
            BTNode<T> u = new BTNode<T>(); //注意:新节点已有一个空孩子
            for (int j = 0; j < _order - s - 1; j++)
            { //v右侧_order-s-1个孩子及关键码分裂为右侧节点u
                BTNode<T> node = v.GetChild(s + 1);
                v.RemoveChildAt(s + 1);
                u.InsertChild(j, node); //逐个移动效率低

                T key = v.GetKey(s + 1);
                v.RemoveKeyAt(s + 1);
                u.InsertKey(j, key); //此策略可改进
            }

            BTNode<T> node2 = v.GetChild(s + 1);
            v.RemoveChildAt(s + 1);
            u.SetChild(_order - s - 1, node2); //移动v最靠右的孩子
            if (null != u.GetChild(0)) //若u的孩子们非空,则
            {
                for (int j = 0; j < _order - s; j++) //令它们的父节点统一
                {
                    u.GetChild(j).ParentNode = u; //指向u
                }
            }
                
            BTNode<T> p = v.ParentNode; //v当前的父节点p
            if (null == p)
            {
                _root = p = new BTNode<T>();
                p.SetChild(0, v);
                v.ParentNode = p;
            } //若p空则创建之

            int index = -1;
            for (int i = 0; i < p.KeyCount; ++i)
            {
                int compare = p.GetKey(i).CompareTo(v.GetKey(0));
                if (compare <= 0)
                {
                    index = i;
                    if (compare == 0)
                    {
                        break;
                    }
                }
            }

            int r = 1 + index; //p中指向u的指针的秩

            T key2 = v.GetKey(s);
            v.RemoveKeyAt(s);
            p.InsertKey(r, key2); //轴点关键码上升
            p.InsertChild(r + 1, u); 
            u.ParentNode = p; //新节点u与父节点p互联
            SolveOverflow(p); //上升一层,如有必要则继续分裂——至多递归O(logn)层
        }

        /// <summary>
        /// 下溢:因删除而下溢后的合并处理
        /// </summary>
        /// <param name="node"></param>
        private void SolveUnderflow(BTNode<T> v)
        {
            if ((_order + 1) / 2 <= v.ChildCount) return; //递归基:当前节点并未下溢
            BTNode<T> p = v.ParentNode;
            if (null == p)
            { //递归基:已到根节点,没有孩子的下限
                if (v.KeyCount <= 0 && null != v.GetChild(0))
                {
                    //但倘若作为树根的v已不含关键码,却有(唯一的)非空孩子,则
                    /*DSA*/
                    _root = v.GetChild(0);
                    _root.ParentNode = null; //这个节点可被跳过
                    v.SetChild(0, null); //release(v); //并因不再有用而被销毁
                } //整树高度降低一层
                return;
            }
            int r = 0;
            while (p.GetChild(r) != v)
            {
                r++;
            }

            //确定v是p的第r个孩子——此时v可能不含关键码,故不能通过关键码查找
            //另外,在实现了孩子指针的判等器之后,也可直接调用Vector::find()定位
            /*DSA*/
            // 情况1:向左兄弟借关键码
            if (0 < r)
            { //若v不是p的第一个孩子,则
                BTNode<T> ls = p.GetChild(r - 1); //左兄弟必存在
                if ((_order + 1) / 2 < ls.ChildCount)
                { //若该兄弟足够“胖”,则
                  /*DSA*/
                    v.InsertKey(0, p.GetKey(r - 1)); //p借出一个关键码给v(作为最小关键码)
                    T key = ls.GetKey(ls.KeyCount - 1);
                    ls.RemoveKeyAt(ls.KeyCount - 1);
                    p.SetKey(r - 1, key); //ls的最大关键码转入p

                    BTNode<T> node = ls.GetChild(ls.ChildCount - 1);
                    ls.RemoveChildAt(ls.ChildCount - 1);
                    //同时ls的最右侧孩子过继给v
                    //作为v的最左侧孩子
                    v.InsertChild(0, node);
                    return; //至此,通过右旋已完成当前层(以及所有层)的下溢处理
                }
            } //至此,左兄弟要么为空,要么太“瘦”
              // 情况2:向右兄弟借关键码
            if (p.ChildCount - 1 > r)
            { //若v不是p的最后一个孩子,则
                BTNode<T> rs = p.GetChild(r + 1); //右兄弟必存在
                if ((_order + 1) / 2 < rs.ChildCount)
                { //若该兄弟足够“胖”,则
                  /*DSA*/
                    v.InsertKey(v.KeyCount, p.GetKey(r)); //p借出一个关键码给v(作为最大关键码)
                    T key = rs.GetKey(0);
                    rs.RemoveKeyAt(0);
                    p.SetKey(r, key); //rs的最小关键码转入p

                    BTNode<T> node = rs.GetChild(0);
                    rs.RemoveChildAt(0);
                    v.InsertChild(v.ChildCount, node);
                    //同时rs的最左侧孩子过继给v
                    if (null != v.GetChild(v.ChildCount - 1)) //作为v的最右侧孩子
                    {
                        v.GetChild(v.ChildCount - 1).ParentNode = v;
                    }
                    return; //至此,通过左旋已完成当前层(以及所有层)的下溢处理
                }
            } //至此,右兄弟要么为空,要么太“瘦”
              // 情况3:左、右兄弟要么为空(但不可能同时),要么都太“瘦”——合并
            if (0 < r)
            { //与左兄弟合并
              /*DSA*/
                BTNode<T> ls = p.GetChild(r - 1); //左兄弟必存在
                T key = p.GetKey(r - 1);
                p.RemoveKeyAt(r - 1);
                ls.InsertKey(ls.KeyCount, key);
                p.RemoveChildAt(r);

                //p的第r - 1个关键码转入ls,v不再是p的第r个孩子
                BTNode<T> node = v.GetChild(0);
                v.RemoveChildAt(0);
                ls.InsertChild(ls.ChildCount, node);//v的最左侧孩子过继给ls做最右侧孩子

                while (v.KeyCount > 0)
                { //v剩余的关键码和孩子,依次转入ls
                    T key2 = v.GetKey(0);
                    v.RemoveKeyAt(0);
                    ls.InsertKey(ls.KeyCount, key2);

                    BTNode<T> node2 = v.GetChild(0);
                    v.RemoveChildAt(0);
                    ls.InsertChild(ls.ChildCount, node2);
                }
                //release(v); //释放v
            }
            else
            { //与右兄弟合并
              /*DSA*/
               // printf(" ... case 3R\n");
                BTNode<T> rs = p.GetChild(r + 1); //右兄弟必存在
                T key = p.GetKey(r);
                    p.RemoveKeyAt(r);
                rs.InsertKey(0, key); p.RemoveChildAt(r);
                //p的第r个关键码转入rs,v不再是p的第r个孩子

                BTNode<T> node = v.GetChild(v.ChildCount - 1);
                v.RemoveChildAt(v.ChildCount - 1);
                rs.InsertChild(0, node);
                if (null != rs.GetChild(0))
                {
                    rs.GetChild(0).ParentNode = rs; //v的最左侧孩子过继给ls做最右侧孩子
                }
                while (v.KeyCount > 0)
                { //v剩余的关键码和孩子,依次转入rs
                    T key2 = v.GetKey(v.KeyCount - 1);
                    v.RemoveKeyAt(v.KeyCount - 1);
                    rs.InsertKey(0, key2);

                    BTNode<T> node2 = v.GetChild(v.ChildCount - 1);
                    v.RemoveChildAt(v.ChildCount - 1);
                    rs.InsertChild(0, node2);
                    if (null != rs.GetChild(0))
                    {
                        rs.GetChild(0).ParentNode = rs;
                    }
                }
                //release(v); //释放v
            }
            SolveUnderflow(p); //上升一层,如有必要则继续分裂——至多递归O(logn)层
        }

        /// <summary>
        /// 层序遍历,获取所有节点,切是按照每一层节点返回
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public List<List<BTNode<T>>> TraverseLevelList(BTNode<T> node)
        {
            List<List<BTNode<T>>> listResult = new List<List<BTNode<T>>>();
            if (null == node)
            {
                return listResult;
            }

            Queue<BTNode<T>> queue = new Queue<BTNode<T>>();
            queue.Enqueue(node);
            while (queue.Count > 0)
            {
                int count = queue.Count;
                List<BTNode<T>> list = new List<BTNode<T>>();

                while(count > 0)
                {
                    --count;
                    node = queue.Dequeue();
                    if (null == node)
                    {
                        continue;
                    }

                    list.Add(node);
                    for (int i = 0; i < node.ChildCount; ++i)
                    {
                        queue.Enqueue(node.GetChild(i));
                    }
                }
                listResult.Add(list);
            }
            return listResult;
        }

        /// <summary>
        /// 层序遍历,获取所有节点
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public List<BTNode<T>> TraverseLevel(BTNode<T> node)
        {
            List<BTNode<T>> list = new List<BTNode<T>>();
            if (null == node)
            {
                return list;
            }

            Queue<BTNode<T>> queue = new Queue<BTNode<T>>();
            queue.Enqueue(node);
            while (queue.Count > 0)
            {
                node = queue.Dequeue();
                if (null == node)
                {
                    continue;
                }

                list.Add(node);
                for (int i = 0; i < node.ChildCount; ++i)
                {
                    queue.Enqueue(node.GetChild(i));
                }
            }
            return list;
        }
    }

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

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

相关文章

前端常用的数据加密方式

前端开发中&#xff0c;数据安全是至关重要的一个方面。数据加密是保护用户隐私和信息安全的关键方法之一。 前端常用的数据加密方式涵盖了对传输数据的加密、存储数据的加密以及客户端与服务器端之间通信的加密。 1. 对称加密算法 对称加密算法使用相同的密钥进行加密和解密…

STM32H750外设ADC之双重 ADC 模式

目录 概述 1 双重 ADC 模式介绍 1.1 双重 ADC模式 1.2 双重 ADC 模式的类型 2 双重 ADC 模式寄存器的配置 3 模式功能实现 3.1 注入同步模式 3.2 支持独立注入的常规同步模式 3.2.1 中断的方式 3.2.2 DMA 读取常规数据 3.3 支持独立注入的交替模式 3.3.1 中断触发…

Java面试八股之简述Servlet体系结构

简述Servlet体系结构 Servlet是Java Web开发中的核心组件&#xff0c;用于接收和响应HTTP请求&#xff0c;生成动态内容。它具有平台无关性、协议无关性和动态内容生成能力&#xff0c;遵循明确的生命周期。尽管现代Web开发中更多使用高级框架&#xff0c;但Servlet作为基础&a…

安装WSL2

PS C:\Users\pc> wsl --set-default-version 2 有关与 WSL 2 关键区别的信息&#xff0c;请访问 https://aka.ms/wsl2操作成功完成。PS C:\Users\pc> wsl --update 正在检查更新。 已安装最新版本的适用于 Linux 的 Windows 子系统。PS C:\Users\pc> wsl --shutdownPS…

中霖教育:报名一级建造师查社保吗?

一级建造师考试报名是否需要查社保&#xff1f;不同地区的要求不一样&#xff0c;有些地区需要考生提供社保&#xff0c;但是有些地区则无需出示社保证明。建议考生详细阅读当地考试通知&#xff0c;以确认是否涉及社保的相关要求。 在要求提供社保证明的地区&#xff0c;报名…

Jira搭建过程

看到很多小伙伴对jira有兴趣,我们今天就来分享一下jira的搭建吧 首先要明白jira是什么? 看来搭建jira也是我们测试人员需要具备的技能之一了.下面是详细的大家步骤: 1.系统环境准备 Centos 7.5 Mysql 5.6 Java1.8 2.软件安装包 atlassian-jira-software-7.13.0-x64.bin …

c++的学习之路:26、AVL树

摘要 本章主要是说一下AVL树的实现&#xff0c;这里说的是插入的底层原理 目录 摘要 一、原理 二、四种旋转 1、左单旋 2、右单旋 3、左右双旋 4、右左双旋 三、代码实现 1、节点创建 2、插入 3、旋转 4、判断是否平衡 5、测试 四、代码 一、原理 前面说了搜索…

刷代码随想录有感(38):N叉树的层序遍历

题干&#xff1a; 代码&#xff1a; // Definition for a Node. class Node { public:int val;vector<Node*> children;Node(int _val, vector<Node*> _children) {val _val;children _children;} };class Solution { public:vector<vector<int>> l…

Github Coplit的认证及其在JetBrains中的使用

原文地址&#xff1a;Github Coplit的认证及其在JetBrains中的使用 - Pleasure的博客 下面是正文内容&#xff1a; 前言 今天分享一个可有可无的小技巧&#xff0c;水一篇文。 如标题所述&#xff0c;Github Coplit的认证及其在JetBrains中的使用 正文 介绍JetBrains JetBrain…

了解MySQL InnoDB多版本MVCC(Multi-Version Concurrency Control)

了解MySQL InnoDB多版本MVCC&#xff08;Multi-Version Concurrency Control&#xff09; 在数据库管理系统中&#xff0c;多版本并发控制&#xff08;MVCC&#xff09;是一种用于实现高并发和事务隔离的技术。MySQL的InnoDB存储引擎支持MVCC&#xff0c;这使得它可以在提供高…

Elasticsearch 开放 inference API 增加了对 OpenAI chat completions 的支持

作者&#xff1a;Tim Grein 我们很高兴地宣布在 Elasticsearch 中推出的最新创新&#xff1a;在 Elastic 的 inference API 中集成了 OpenAI Chat Completions 功能。这一新特性标志着我们在整合尖端人工智能能力至 Elasticsearch 的旅程中又迈出了一步&#xff0c;提供了生成类…

前端css中border-radius的简单使用

前端css中border-radius的使用 一、前言二、border-radius语法三、border-radius的模型例子1.源码12.源码1效果截图 四、border-radius的动画效果&#xff08;动态交互&#xff09;1.源码22.源码2显示效果 五、结语六、定位日期 一、前言 在CSS中&#xff0c;我们常用border-r…

Mac多媒体播放器 Movist Pro v2.11.4中文激活版下载

Movist Pro for Mac是一款专业的媒体播放器&#xff0c;特别为Mac用户设计。它不仅界面简洁美观&#xff0c;而且功能强大&#xff0c;能满足用户各种播放需求。 Movist Pro v2.11.4中文激活版下载 首先&#xff0c;Movist Pro for Mac支持多种媒体文件的播放&#xff0c;包括视…

Ubuntu系统安装Anaconda

1. 下载Anconda安装包 1.1 wget命令下载 当然还可以去清华大学开源软件镜像站&#xff1a;Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror&#xff0c;下载各种版本的Anaconda。 wget下载命令如下&#xff1a; 我这里下载的是2024.02…

UART串口通信

基本原理与概念 串口通信在不同的硬件上的具体表现不同&#xff0c;但基本原理与操作流程都差不多&#xff1a; UART是一种串行、异步、全双工的通信协议&#xff0c;将所需传输的数据一位接一位地传输&#xff0c;在UART 通讯协议中信号线上的状态位高电平代表’1’&#x…

c++——类和对象(1)构造,析构函数

类的六个默认函数 如果一个类当中没有成员的话&#xff0c;那叫空类&#xff0c;实际上空类有6个编译器默认生成的函数成员 默认成员函数&#xff1a;没有显示实现&#xff0c;编译器生成的成员函数称为默认成员函数 1&#xff0c;构造函数与构析函数 1.1构造函数的概念 构造…

c语言之字符串的集合存放形式

采用指针分配的二维数组与直接定义的二维数组&#xff0c;sizeof的不同 采用指针分配的二维数组&#xff1a; 它的遍历方式是&#xff1a; 上面这个是分配二级指针的地址&#xff0c;二级指针就是一片可以用来分配一级指针空间的地址&#xff0c;然后指针寻址本来就可以当成数组…

java算法day3

移除链表元素设计链表翻转链表两两交换链表中的结点 移除链表元素 ps&#xff1a;有时候感觉到底要不要写特判&#xff0c;你想到了就写&#xff01;因为一般特判有一劳永逸的作用。 解法有两种&#xff0c;一种是不用虚拟头结点&#xff0c;另一种就是用虚拟头结点。 这里我…

python安装的详细步骤

下载 1.打开Python官网.我们建议工具类的测试软件还是官网下载比较靠谱. https://www.python.org/getit/ 2.在下图界面中选择需要的方式进行点击 3.直接点击下载.可以进入保存界面,进行保存即可下载,后续安装 4.鼠标放在Downloads显示平台和版本选择界面,点击Windows,进入wi…

Spring-dataSource事务案例分析-使用事务嵌套时,一个我们容易忽略的地方

场景如下&#xff1a; A_Bean 中的方法a()中调用B_Bean的b();方法都开启了事务&#xff0c;使用的默认的事务传递机制&#xff08;即&#xff1a;属于同一事务&#xff09;&#xff1b; 如下两种场景会存在较大的差异&#xff1a; 在b()方法中出现了异常&#xff0c;在b()中进…