【数据结构】B树家族解析:B树、B+树与B*树的理论与B树插入实现(C++)

文章目录

  • 一、常见的搜索结构
  • 二、B树
    • 2.1 B树概念
    • 2.2 开销
  • 三、代码实现
    • 3.1 B树节点的设计
    • 3.2 B树设计
    • 3.3 插入操作实现
      • 1. 查找插入位置(`Find` 函数)
      • 2. 插入关键字到节点(`InsertKey` 函数)
      • 3. 处理节点分裂(`Insert` 函数)
        • 主要步骤:
      • 4. 结论
    • 3.4 验证
  • 四、B+树与B*树
    • 4.1 B+树
      • 1. B+树的分裂
      • 2. 总结
    • 4.2 B*树
      • 1. 分裂
    • 4.3 总结
  • 五、B树的实际应用
    • 5.1 B树 与 MYSQL
    • 5.2 MYSQL的存储引擎
      • 1. MYISAM
      • 2. InnoDB
    • 5.3 总结

一、常见的搜索结构

下面是一些常见的搜索结构:

以下是您要求的查找方法及其相关的种类、数据格式和时间复杂度,以表格形式列举:

查找方法数据格式时间复杂度
顺序查找无要求O(N)
二分查找有序O(log₂ N)
二叉搜索树无要求O(N)
二叉平衡树 (AVL树和红黑树)无要求O(log₂ N)
哈希查找无要求O(1)
  • 顺序查找:适用于任何数据结构,但时间复杂度较高。
  • 二分查找:只适用于已排序的数据,因此需要事先将数据排序。
  • 二叉搜索树:每个节点的左子树比右子树小,适用于动态数据,但最坏情况下可能退化为链表,时间复杂度变为O(N)。
  • 二叉平衡树(如AVL树、红黑树等):通过平衡操作保证树的高度为O(log₂ N),因此查找时间复杂度较低,适用于动态数据。
  • 哈希查找:通过哈希表实现查找,通常可以达到O(1)的时间复杂度,但存在哈希冲突的情况。

总的来说,以上结构适合用于数据量相对较小,能够一次性存放在内存中,进行数据查找的场景

若数据量很大,比如上百G无法一次放进内存中的数据,只能放在磁盘上;

对于磁盘上需要搜索的数据:可以考虑将存放【关键字及其映射】的数据地址放到一个内存中的搜索树的节点中,当要访问数据时,取这个地址去磁盘访问相应数据


在这里插入图片描述

由于:
在这里插入图片描述

在这种情况下,使用平衡二叉树或是哈希表都有一定的缺陷:

使用平衡二叉树搜索树的缺陷:

  • 平衡二叉树搜索树的高度是logN,在内存中很快。但当数据在磁盘中时,访问磁盘速度很慢,当数据量很大时,logN次的磁盘访问,是一个大的开销。

使用哈希表的缺陷:

  • 哈希表的效率是O(1),但在极端场景下某个位置冲突很多,导致访问次数剧增,也是大的开销

那么如何提高对磁盘中数据的访问速度?

  1. IO速率优化:使用SSD等特殊磁盘(虽说速度提示,但本质没有变化)
  2. 搜索结果优化:降低树的高度,采用多叉搜索平衡树(一个节点存放多个关键字与映射)

这个结构就是下文探讨的B树;


二、B树

2.1 B树概念

B树是一种适合外查找的树,m阶(m>2)的B树,是平衡的M路平衡搜索树,可以是空树

其满足以下性质:

  1. 根节点至少有两个孩子
  2. 每个分支节点都包含 k-1个关键字 k个孩子 ,其中 ceil(m/2) ≤ k ≤ m (ceil是向上取整)
  3. 每个叶子节点都包含 k-1个关键字 ,其中 ceil(m/2) ≤ k ≤ m
  4. 所有的叶子节点都在同一层
  5. 每个节点中的关键字从小到大排列,节点当中【k-1个元素正好是k个孩子包含的元素的值域划
    分】
  6. 每个结点的结构为:(n,A0,K1,A1,K2,A2,… ,Kn,An)其中,Ki(1≤i≤n)为关键
    字,且Ki<Ki+1(1≤i≤n-1)Ai(0≤i≤n)为指向子树根结点的指针。且Ai所指子树所有结点中的
    关键字均小于Ki+1。(n为结点中关键字的个数,满足ceil(m/2)-1≤n≤m-1)

下图是一个简易B树的示意图:

在这里插入图片描述

对于B树来说,其整体的结构的实现都是遵循其插入的规律来的(与AVL树与红黑树不同,这两个是根据已有的稳定结构,去实现相应的插入逻辑)

下面举一个实例,我们通过其插入的过程图理解这一过程,我们插入一组数据:{53,139,75,49,145,35,52 48, 100, 110}

  • 插入前三个数时,情况如下:
    在这里插入图片描述

  • 当插入第四个值时,关键字数量超过了最大值,会进行节点分裂:

在这里插入图片描述

  • 再插入两个值到同一子节点,会再次进行分裂:

在这里插入图片描述

  • 再次插入一些值,此时根节点的关键字达到最大值:

在这里插入图片描述

  • 再次插入两个值,使其中一个子节点需要分裂,当子节点将关键字向上传递后,此时根节点关键字超出最大值,也需要分裂:

在这里插入图片描述

根据上图,我们理解了B树的插入过程,且容易看出,B树是天然平衡的,因为比起其他搜索结构,B树是向右扩充,向上生长的;


2.2 开销

不妨分析一下其面对相应场景时的开销:

对于一个M=1000,高为4的B树:

在这里插入图片描述


三、代码实现

根据上面插入的思路,我们可以用代码进行实现:

3.1 B树节点的设计

我们通过模板+结构体定义B树的一个节点,包括以下成员变量:

  1. K _keys[M]:存储关键字
  2. BTreeNode<K, M>* _sub[M + 1]: 存储指向子节点的指针数组
  3. BTreeNode<K, M>* _parent:指向父节点的指针
  4. size_t _size:当前节点包含的关键字数量
// 模板类定义:B树节点(BTreeNode)
// K: 关键字类型
// M: 每个节点最多包含的关键字数(默认 M=3)

template <class K, int M = 3>
struct BTreeNode {
    // 存储关键字的数组
    K _keys[M];  

    // 存储指向子节点的指针数组,M+1 是因为子节点数比关键字数多 1
    BTreeNode<K, M>* _sub[M + 1];  

    // 指向父节点的指针,根节点的父节点为 nullptr
    BTreeNode<K, M>* _parent;       

    // 当前节点包含的关键字数量(有效关键字个数)
    size_t _size;

    // 构造函数,初始化节点的成员变量
    BTreeNode() : _parent(nullptr), _size(0) {
        // 初始化所有子节点指针为 nullptr
        for (int i = 0; i <= M; ++i) {
            _sub[i] = nullptr;
        }
    }
};

3.2 B树设计

我们这里主要实现B树的插入操作,下面是B树类的构成,包含:

  1. 节点结构 BTreeNode:B树的每个节点有若干关键字、子节点指针和一个指向父节点的指针。
  2. B树类 BTree:管理整个树的结构,包括根节点、插入、查找等操作。
template<class K, int M = 3>
class BTree
{
public:
    using Node = BTreeNode<K, M>;
	
	// 构造函数:初始化空树
    BTree() : _root(nullptr) {}

    // 析构函数:释放树的内存
    ~BTree() {
        clear(_root);
    }
    
	// 成员函数
	// 查找指定节点
	std::pair<Node*, int> Find(const K& key); 
	// 插入
	void InsertKey(Node* cur, const K& key, Node* sub)
	bool Insert(const K& key);
	
private:
    Node* _root = nullptr;
};

3.3 插入操作实现

我们实现的插入操作主要包括三个步骤:

  1. 查找插入位置:通过 Find 函数找到插入位置。
  2. 插入关键字到节点:通过 InsertKey 函数将关键字插入到适当位置。
  3. 节点分裂:如果节点满了(即 cur->_size >= M),则需要分裂节点,分裂过程是递归的,直到根节点为止。

1. 查找插入位置(Find 函数)

Find 函数用于查找关键字在 B 树中的位置。如果找到匹配的关键字,返回该节点和关键字的位置。如果没有找到,返回父节点和插入位置的负值。

std::pair<Node*, int> Find(const K& key)
{
    Node* cur = _root;         // 当前节点,从根节点开始查找
    Node* parent = nullptr;    // 父节点

    while (cur)
    {
        size_t i = 0;
        while (i < cur->_size) {
            if (key < cur->_keys[i]) {     // 找到合适的插入位置
                break;
            }
            else if (key > cur->_keys[i]) { // 如果关键字大于当前节点的关键字,继续查找下一个
                i++;
            }
            else { // 找到相同的关键字,返回当前节点及其位置
                return std::make_pair(cur, i);
            }
        }

        parent = cur;
        cur = cur->_sub[i]; // 查找对应的子节点
    }

    return std::make_pair(parent, -1); // 返回父节点和 -1,表示未找到,需插入
}

在查找过程中,我们遍历节点的关键字,寻找该关键字的位置。如果找到相同的关键字,则直接返回。否则,递归查找相应的子节点。

2. 插入关键字到节点(InsertKey 函数)

InsertKey 函数用于将一个新关键字插入到当前节点 cur 中的正确位置。如果当前节点有空余空间,直接插入。

void InsertKey(Node* cur, const K& key, Node* sub)
{
    int end = cur->_size - 1;
    // 将大于新插入关键字的元素向后移动一位
    while (end >= 0)
    {
        if (key < cur->_keys[end]) {
            cur->_keys[end + 1] = cur->_keys[end];
            cur->_sub[end + 2] = cur->_sub[end + 1];
        }
        else {
            break;
        }
        end--;
    }

    cur->_keys[end + 1] = key;   // 插入新关键字
    cur->_sub[end + 2] = sub;    // 插入子节点指针
    if (sub) sub->_parent = cur; // 如果插入的不是叶子节点,更新子节点的父指针
    cur->_size++;                // 更新当前节点的大小
}

这个函数实现了将关键字插入到节点中的一个排序数组。插入时,从后往前找到合适的位置,并将比插入关键字大的元素向后移动。然后插入关键字,并且更新子节点指针。

3. 处理节点分裂(Insert 函数)

如果插入导致节点的大小超过了树的最大节点大小 M,则需要分裂节点并更新父节点。分裂过程是递归的,当根节点也需要分裂时,树的高度会增加。

bool Insert(const K& key)
{
    // 如果根节点为空,创建根节点并插入关键字
    if (_root == nullptr) {
        _root = new Node;
        _root->_size = 1;
        _root->_keys[0] = key;
        return true;
    }

    // 查找插入位置
    auto ret = Find(key);
    if (ret.second >= 0) { // 如果元素已存在
        std::cout << "元素已存在: " << key << std::endl;
        return false;
    }

    Node* cur = ret.first;
    Node* brother = nullptr;
    K k = key;

    // 递归插入
    while (true)
    {
        InsertKey(cur, k, brother); // 向节点插入关键字

        // 如果当前节点没有满,则直接返回
        if (cur->_size < M) return true;

        // 如果节点满了,进行分裂
        int mid = M / 2;
        brother = new Node;  // 创建兄弟节点

        // 将当前节点右半部分的元素和子树指针移到兄弟节点
        for (int i = mid + 1; i < M; ++i) {
            brother->_keys[brother->_size] = cur->_keys[i];
            brother->_sub[brother->_size] = cur->_sub[i];
            if (cur->_sub[i]) cur->_sub[i]->_parent = brother;
            brother->_size++;
        }

        // 设置兄弟节点的子树指针
        brother->_sub[brother->_size] = cur->_sub[cur->_size];
        if (cur->_sub[cur->_size]) cur->_sub[cur->_size]->_parent = brother;

        // 更新当前节点的大小
        cur->_size = mid;

        // 如果当前节点没有父节点,说明树的高度增加
        if (cur->_parent == nullptr) {
            _root = new Node; // 创建新的根节点
            _root->_keys[0] = cur->_keys[mid]; // 将中间关键字升到新根节点
            _root->_sub[0] = cur;
            _root->_sub[1] = brother;
            _root->_size = 1;
            cur->_parent = brother->_parent = _root; // 更新父节点指针
            break;
        }
        else { // 否则,将中间关键字提升到父节点,并继续分裂
            k = cur->_keys[mid];
            cur = cur->_parent;
        }
    }

    return true;
}
主要步骤:
  1. 查找插入位置:首先通过 Find 函数查找插入位置,如果关键字已经存在,返回 false
  2. 插入关键字:通过 InsertKey 向当前节点插入新关键字。如果当前节点未满,直接返回。
  3. 处理节点分裂
    • 如果当前节点满了,需要分裂。
    • 分裂时,选择中间关键字作为新的父节点。
    • 将当前节点的右半部分(包括子节点)转移到新创建的兄弟节点中。
    • 递归更新父节点,如果父节点也满了,继续分裂。
    • 如果根节点也满了,创建新的根节点。

4. 结论

整个插入操作通过递归方式处理节点分裂,保证了 B 树的平衡。插入过程中会不断调整节点,分裂节点直到根节点,确保树的结构符合 B 树的定义,即每个节点的关键字数始终满足 ⌈M/2⌉ - 1 <= _size <= M - 1

3.4 验证

对于一个平衡树,我们可以通过中序遍历的方式,将所有节点值打印出来,如果结果有序,则树结构无误

中序遍历包含以下步骤,下面是中序遍历代码:

  • 遍历当前节点的每个关键字对应的左子树(_sub[i])。
  • 输出当前节点的关键字。
  • 最后,遍历当前节点的右子树(_sub[_size])。
  void printInOrder() {
      inOrder(_root);
  }

  void inOrder(Node* root)
  {
      if (root == nullptr) return;

      for (int i = 0; i < root->_size; ++i) {
          inOrder(root->_sub[i]);
          std::cout << root->_keys[i] << " ";
      }

      inOrder(root->_sub[root->_size]);
  }

随后插入测试数据进行测试:

int main()
{
    BTree<int, 3> tree;
    tree.Insert(10);
    tree.Insert(20);
    tree.Insert(5);
    tree.Insert(6);
    tree.Insert(15);
    tree.Insert(30);
    tree.Insert(25);
    tree.Insert(35);

    std::cout << "B-tree in-order traversal:" << std::endl;
    tree.printInOrder();
    return 0;
}

结果验证:

在这里插入图片描述


四、B+树与B*树

4.1 B+树

B+树是B树的变形,其在B树基础上优化的多路平衡搜索树,B+树的规则跟B树大体类似,在B树的基础上做了以下几点改进优化:

  1. 分支节点的子树指针与关键字个数相同
  2. 分支节点的子树指针p[i]指向关键字值大小在(k[i],k[i+1])区间之间
  3. 所有叶子节点增加一个链接指针链接在一起
  4. 所有关键字及其映射数据都在叶子节点中;

在这里插入图片描述

B树的特性:

  1. 所有数据都存储在叶子节点:B树中的所有关键字(或数据)都保存在最底层的叶子节点里,且这些叶子节点按顺序排列,形成一个有序的链表。

  2. 分支节点不是数据存储的地方:在B树的中间层(分支节点)并不会存储实际的数据,只是用来指引到正确的叶子节点。也就是说,你在分支节点上找不到数据。

  3. 分支节点是叶子节点的“索引”:分支节点的作用类似于目录或索引,它们帮助你快速找到数据所在的叶子节点,真正存储数据的地方是叶子节点。

即:B+树的叶子节点才是存储数据的地方,分支节点只起到“导航”的作用,帮助快速定位数据。


1. B+树的分裂

用文字描述B+树的分裂过程即:

当一个节点满了时,会创建一个新的节点,把原节点的一半数据移动到新节点。然后,在父节点中加入指向新节点的链接。B+树的分裂只会影响到当前节点和父节点,不会影响到其他兄弟节点,所以不需要为兄弟节点添加指针。

下面我们演示一下B+树的插入与分裂过程:

在这里插入图片描述

首先对于上图,我们得到几点信息:

  1. B+树插入第一个元素时,就会创建两个节点
  2. 一次分裂的过程可以简述为:
    • 创建新节点(兄弟节点),将中位数向后的元素给兄弟节点,将中位数向上传递
    • 将父节点的关键字指向新的兄弟节点的头部

随后再次进行插入关键字,直到再次存在节点满
在这里插入图片描述

此时会引发连续分裂,子节点将中位数向上传递时,父节点也满了,父节点再次进行分裂;
在这里插入图片描述


2. 总结

  1. B+树的孩子与关键字的数量相等
  2. 所有数据都存储在叶子节点上,方便遍历查找所有值

4.2 B*树

B*树是B+树的变形,给B+树的【非根 & 非叶子节点】增加【指向兄弟节点的指针】。

在这里插入图片描述


1. 分裂

用文字描述B*树的分裂即:

  1. 当一个结点满时,如果它旁边的兄弟结点未满,则将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(兄弟结点的关键字范围改变);
  2. 如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针

下面用示例图简单展示B*树的分裂过程:

首先将一个节点插入满,此时该节点会将自己一半的关键字传给兄弟节点
在这里插入图片描述

此时再次插入两个关键字,此时两个节点均为满,再次进行分裂,此时会创建新的节点,左右两节点分别将自己的1/3(不一定),传给新节点

在这里插入图片描述


通过上面的图,可以很明显的得出一个结论:

  • B*树分配新结点的概率比B+树要低,空间使用率更高;

4.3 总结

通过对三种树的了解,做一个总结:

B树:有序数组+平衡多叉树;
B+树:有序数组链表+平衡多叉树;
B*树:一棵更丰满的,空间利用率更高的B+树。


五、B树的实际应用

5.1 B树 与 MYSQL

根据B树自身性质,其最常见的应用就是作索引(快速定位要查找的内容的位置),MYSQL底层用的就是B树,MYSQL官方对索引有解释:索引(index)是帮助MySQL高效获取数据的数据结构,简单来说:索引就是数据结构

在这里插入图片描述
需要注意的是:

  1. MySQL中索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的
  2. 索引是基于表的,而不是基于数据库的

5.2 MYSQL的存储引擎

1. MYISAM

  1. MyISAM是MySQL5.5.8版本前的默认引擎,不支持事物,支持全文检索。
  2. 使用B+树作索引结构,叶节点的data域存放的是【数据记录的地址】

在这里插入图片描述

上图可以很明显的看出:

MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复

我们利用创建索引语句中创建的索引就是辅助索引 CREATE INDEX idx_email ON users(email);

如果以第二列作辅助索引,则其存储结构如下:

在这里插入图片描述

因此,MyISAM中索引检索的算法为先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,随后以data域的值为地址,读取相应数据记录。MyISAM的索引方式也叫做 “非聚集索引”

2. InnoDB

InnoDB存储引擎支持事务,其设计目的主要面向在线事务的处理应用,从MySQLv5.5.8开始,InnoDB存储引擎是默认的存储引擎。

InnoDB支持B+树索引、全文索引、哈希索引。尽管InnoDB使用B+Tree作为索引结构,其具体实现方式却与MyISAM大不相同。

  1. InnoDB数据文件本身就是索引文件(类似我们上文讲解的B+树)。MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址

InnoDB索引,表的数据文件本身就是按B+树 组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。(即聚集索引

在这里插入图片描述

可以推测出,对于InnoDB为存储引擎的数据库表,为什么每一个表都必须要有一个唯一的主键,建表时的主键实际上就是B+树的一个索引(关键字),所以每个表的逐渐都要求,不能重复值,必须唯一
(如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数
据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型)

同理对于辅助索引的dat·a域,其存储相应记录主键的值不是地址;所有辅助索引都引用主键作为data域。

在这里插入图片描述


5.3 总结

  • InnoDB 存储引擎要求每个表必须有一个 唯一的主键,并且主键索引是 B+树结构,保证数据行的唯一性、顺序存储和高效查询。
  • MyISAM 存储引擎并不强制要求每个表必须有主键。MyISAM 表可以没有主键,而且其索引结构与 InnoDB 略有不同,通常为 非聚集索引。
  • 聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录

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

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

相关文章

protobuf c++开发快速上手指南

1、环境准备 在c环境使用 protobuf&#xff0c;需要安装protobuf runtime以及protobuf的编译器&#xff1a;protoc&#xff0c;其作用如下表格&#xff1a; 需要安装的环境作用protoc将proto文件编译成c源码protobuf runtime编译c源码需要链接到protobuf库 注意&#xff1a;…

【实践·专业课】内存管理-存储管理-文件系统

1. 基于Linux的简单区块链实现 1.1. 环境准备 确保使用的 Linux 系统&#xff08;如 Ubuntu、CentOS 等&#xff09;已安装 Python 3。 在终端输入python3命令&#xff0c;若出现 Python 解释器的版本信息等提示&#xff0c;则表示已安装&#xff1b; 若提示未找到命令&…

科技潮头浪接天,一桥飞架两界连。EthernetIP转Profinet互译连

本案例介绍的是西门子1200PLC通过稳联技术PROFINET转EtherNetIP网关&#xff08;WL-ABC2006&#xff09;连接HCS-6100系统配置案例。 打开稳联技术Ethernetip转profient网关(WL-ABC2006)配置软件&#xff0c;因为网关作为EtherNetIP从站&#xff0c;所以选择PN2EIP。设置网关Pr…

【网络篇】TCP知识

TCP首部格式&#xff1f; 为什么需要 TCP 协议&#xff1f; TCP 工作在哪一层&#xff1f; IP 层是不可靠的&#xff0c;它不保证网络包的交付、不保证网络包的按序交付也不保证网络包中的数据的完整性。如果需要保障网络数据包的可靠性&#xff0c;那么就需要由上层&#xff0…

PDFMathTranslate,PDF多语言翻译,批量处理,学术论文,双语对照(WIN/MAC)

分享一个非常实用的PDF文档翻译项目——PDFMathTranslate。作为一个经常逛GitHub的开发者&#xff0c;我总喜欢翻看各种项目附带的论文&#xff0c;虽然大多时候是瞎研究&#xff0c;但却乐在其中。该项目能够完美保留公式、图表、目录和注释&#xff0c;对于需要阅读外文文献的…

Ape-DTS:开源 DTS 工具,助力自建 MySQL、PostgreSQL 迁移上云

Ape-DTS 是一款高效、轻量级且功能强大的开源工具&#xff0c;专注于解决数据迁移、同步、校验、订阅与加工的需求。无论是将自建的 MySQL/PostgreSQL 数据库迁移到云端&#xff0c;还是在不同数据库间进行数据迁移&#xff0c;Ape-DTS 都能为您提供便捷且可靠的解决方案。它特…

【经典论文阅读】Latent Diffusion Models(LDM)

Latent Diffusion Models High-Resolution Image Synthesis with Latent Diffusion Models 摘要 动机&#xff1a;在有限的计算资源下进行扩散模型训练&#xff0c;同时保持质量和灵活性 引入跨注意力层&#xff0c;以卷积方式实现对一般条件输入&#xff08;如文本或边界框…

使用torch模拟 BMM int8量化计算。

使用torch模型BMM int8计算。 模拟&#xff1a;BMM->softmax->BMM 计算流程 import torch import numpy as np torch.manual_seed(777) def int8_quantize_per_token(x: torch.Tensor, axis: int -1, attnsFalse):if x.dtype ! torch.float32:x x.type(torch.float32)…

Leetcode 每日一题 219.存在重复元素 II

目录 问题描述 输入输出格式 示例 算法分析 过题图片 代码实现 复杂度分析 题目链接 总结 问题描述 给定一个整数数组nums和一个整数k&#xff0c;我们需要判断数组中是否存在两个不同的索引i和j&#xff0c;使得nums[i] nums[j]且|i - j| < k。如果存在这样的i和…

ragflow连不上ollama的解决方案

由于前期wsl默认装在C盘&#xff0c;后期部署好RagFlow后C盘爆红&#xff0c;在连接ollama的时候一直在转圈圈&#xff0c;问其他人没有遇到这种情况&#xff0c;猜测是因为内存不足无法加载模型导致&#xff0c;今天重新在E盘安装wsl 使用wsl装Ubuntu Win11 wsl-安装教程 如…

PR的选择与移动

选择工具 可以选择序列上的剪辑&#xff0c;如果需要多选可以按住shift键选中多个剪辑 CtrlA&#xff1a;可以进行全选 编组 选中多个剪辑后“右键-编组“可以将所选的剪辑连接在一起。这时单击任意剪辑都可以选中全部 向前选择轨道工具与向后选择轨道工具 向前选择轨道工具…

使用C#基于ADO.NET编写MySQL的程序

MySQL 是一个领先的开源数据库管理系统。它是一个多用户、多线程的数据库管理系统。MySQL 在网络上特别流行。MySQL 数据库可在大多数重要的操作系统平台上使用。它可在 BSD Unix、Linux、Windows 或 Mac OS 上运行。MySQL 有两个版本&#xff1a;MySQL 服务器系统和 MySQL 嵌入…

Python3中赋值运算符说明二

一. 简介 前面文章简单学习了 Python3中一些赋值运算符&#xff0c;文章如下&#xff1a; Python3中赋值运算符上篇-CSDN博客 本文继续学习 Python3中另外一些赋值运算符。 二. Python3 中赋值运算符 1. Python3 中赋值运算符 前一篇文章简单学习了 Python3 中的一些赋值…

如何在 Ubuntu 22.04 上安装和使用 Apache Kafka

简介 Apache Kafka是一个高性能、低延迟的分布式流处理平台&#xff0c;广泛用于构建实时数据管道和流式应用。本文将指导你如何在Ubuntu 22.04系统上快速部署Apache Kafka&#xff0c;让你体验到Kafka在处理大规模实时数据流方面的强大能力。通过本教程&#xff0c;你将学会如…

群控系统服务端开发模式-应用开发-自动退出发送邮件

一、修改Redis配置文件 将redis.conf里面的notify-keyspace-events参数对应的值改为Ex&#xff0c;具体代码如下&#xff1a; notify-keyspace-events Ex 二、创建控制台命令 在根目录下config文件夹下找到console.php文件修改&#xff0c;具体代码如下&#xff1a; <?p…

前端篇 -- jQuery详细教程

jQuery教程 jQuery官网1.1 jQuery的基本介绍1.2 jQuery 基本开发步骤1.3 jQuery对象和DOM对象 1.3.1 jQuery对象的基本介绍1.3.2 DOM对象转 jQuery对象1.3.3 jQuery对象转DOM对象 1.4 jQuery选择器 1.4.1 jQuery 基本选择器介绍1.4.2 基本选择器1.4.3 层次选择器1.4.4 基础过滤…

【数模学习笔记】模糊综合评价

声明&#xff1a;以下笔记中的图片均来自“数学建模学习交流”清风老师的课程ppt&#xff0c;仅用作学习交流使用 模糊综合评价 文章目录 模糊综合评价模糊数学经典集合和模糊集合的基本概念经典集合和特征函数模糊集合和隶属函数模糊集合的分类 隶属函数的确定方法方法一 模糊…

STM32F103单片机使用STM32CubeMX新建IAR工程步骤

打开STM32CubeMX软件&#xff0c;选择File 选择新建工程 在打开的窗口输入单片机型号 在右下角选择单片机型号&#xff0c;然后点右上角 start project&#xff0c;开始新建工程。 接下来设置调试接口&#xff0c;在左边System Core中选择 SYS&#xff0c;然后在右右边debu…

相机(Camera)硬件组成详解

简介&#xff1a;个人学习分享&#xff0c;如有错误&#xff0c;欢迎批评指正。 写在前面&#xff1a;可以去B站观看一些相机原理的视频来配合学习&#xff0c;这里推荐&#xff1a;推荐1&#xff0c;推荐2&#xff0c;推荐3 相机&#xff08;Camera&#xff09;是一种复杂的光…

String【Redis对象篇】

&#x1f3c6; 作者简介&#xff1a;席万里 ⚡ 个人网站&#xff1a; 文章目录 String1.String是什么&#xff1f;2.String怎么用&#xff1f;3.常用操作4.底层实现&#xff1f;5.总结&#xff08;重点&#xff09; String 1.String是什么&#xff1f; String就是字符串&…