B-Tree详解及编码实现

一、概念和特性

      1、定义

        B-Tree是一种平衡的多叉树,适用于外查找多路搜索树,这种数据结构能够保证数据节点查找、顺序访问、插入、删除的动作,其平均时间复杂读控制在O(logN)内;B树为系统大块数据的读写操作做了优化,少定位记录时所经历的中间过程,加快存储速度。

       理解B-Tree的工作原理,必须先介绍阶的概念。假如一个m阶的B-Tree,则这个数需要满足以下条件:

         (1)定义任意非叶子结点最多只有m(m>2)个子节点;

         (2)根节点的儿子节点数为[2, m],除根节点以外的非叶子节点的儿子数为[m/2, m];

         (3)每个节点存放[m/2-1,m-1]个关键字(m/2向上取整);

         (4)非叶子节点有k个子节点,则关键字个数为k-1;

         (5)非叶子节点关键字k[i+1]大于k[i]所指向的儿子节点,k[i]小于自生所指向的儿子节点; 

        (6)所有的叶子节点位于同一层;

         2、特性

        (1)关键字集合分布在整颗树中,任何一个关键字出现且只出现在一个结点中;

        (2)搜索有可能在非叶子节点结束;        

        (3)其搜索性能等价于在关键字全集内做一次二分查找;

        (4)自动层次控制。

         由于m/2的限制,在插入结点时,如果结点已满,需要将结点分裂为两个各占m/2的结点;删除结点时,需将两个不足m/2的兄弟节点合并。

二、B树的应用

          B-Tree这种数据结构常被应用在数据库文件系统的实现上。

         B-Tree是为磁盘等外存储设备设计的一种平衡查找树。系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取到内存中,而不是需要什么取什么,这将会减少磁盘I/O次数,提高查询效率,这遵循计算机的局部性原理

三、B-Tree的变体B+Tree

       B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构, B+Tree具有以下几个特点:

      1、有m个子树的节点包含有m个元素(B-Tree中是m-1);

      2、根节点和分支节点中不保存数据,只用于索引,所有数据都保存在叶子节点中;

     3、所有分支节点和根节点都同时存在于子节点中,在子节点元素中是最大或者最小的元素;

     4、叶子节点会包含所有的关键字,以及指向数据记录的指针,并且叶子节点本身是根据关键字的大小从小到大顺序链接。

        以数据库mysql为例,mysql的InnoDB存储引擎中有页的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB,因此InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小16KB,InnoDB在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。

       假如非叶子节点索引关键字平均大小为100byte,那么三层树高可以支持(16*1024/100)*(16*1024/100)*(16*1024*100),大约支持430万条记录高效查询。

四、B-Tree编码实现

1、函数声明
#ifndef _B_TREE_H__
#define _B_TREE_H__

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <string.h>

#define DEGREE 5
#define KEY_NUM (DEGREE-1)
#define MID (DEGREE/2)
// 插入数据成功
#define INSERT_SUC 0;
// 插入数据失败
#define INSERT_FAIL 500;

typedef  int  BtreeData;
typedef  struct btreenode
{
    /**关键字数组, 数组长度为DEGREE-1*/
    BtreeData* keys;
    /**孩子结点*/
    struct btreenode**  childNodes;
    /**父节点:为了便于操作减少遍历*/
    struct btreenode* parent;
    /**当前关键字个数*/
    int  curKeysNum;
}BtreeNode;

/**
 * 创建结点
 * @brief create_btreenode
 * @return
 */
BtreeNode*  create_btreenode();

/**
 * 插入数据
 * @brief insert_btreenode
 * @param root
 * @param data
 * @return
 */
int  insert_btreenode(BtreeNode** root,BtreeData  data);

/**
 * 查找关键字下标位置
 * @brief find_keys_index
 * @return
 */
int  find_keys_index(BtreeNode* node,BtreeData data);

/**
 * 通过下标查找child节点
 * @brief find_node_by_index
 * @param node
 * @param index
 * @return
 */
BtreeNode* find_childnode_by_keyindex(BtreeNode* node,BtreeData  data);

/**
 * 将当前数值插入到关键字数组
 * @brief insert_data_to_keys
 * @return index 返回关键字坐标
 */
int add_data_to_keys(BtreeNode* node,BtreeData data);

/**
 * 分裂操作
 * @brief division_btreenode
 * @param node
 */
void  division_btreenode(BtreeNode** root,BtreeNode* node);

/**
 * 查找关键字所在的节点
 * @brief find_btrenode_by_key
 * @param root
 * @param node
 * @return
 */
BtreeNode*   find_btrenode_by_key(BtreeNode* root,BtreeData  data);

/**
 * 删除节点
 * @brief delete_btreenode
 * @param root
 * @param data
 */
void  delete_btreenode(BtreeNode** root,BtreeData  data);

/**
 * 删除关键字
 * @brief delete_key_by_index
 * @param root
 * @param data
 */
void  delete_key_by_index(BtreeNode* node,BtreeData  data);

/**
 * 合并操作
 * @brief merge_btreenode
 * @param root
 * @param node
 */
void   merge_btreenode(BtreeNode** root,BtreeNode* node,BtreeData data);

/**
 * 遍历某个节点的关键字
 * @brief show_node_kyes
 * @param node
 * @param func
 */
void  show_node_kyes(BtreeNode* node);

/**
 * 遍历B-Tree
 * @brief show_btreenode
 * @param root
 */
void   show_btreenode(BtreeNode*  root);

#endif
2、函数定义
#include "btree.h"

BtreeNode*  create_btreenode()
{
    BtreeNode* btreeNode = (BtreeNode*) malloc(sizeof(BtreeNode));
    if(btreeNode==NULL)
    {
        perror("malloc btree_node failed");
        return NULL;
    }
    int keySizeLen = sizeof(BtreeData)*KEY_NUM;
    BtreeData*  keys = (BtreeData*) malloc(keySizeLen);
    memset(keys,0,keySizeLen);
    if(keys==NULL)
    {
        perror("malloc keys failed");
        free(btreeNode);
        return NULL;
    }
    int  childSizeLen = sizeof(BtreeNode*)*DEGREE;
    BtreeNode** childNode = (BtreeNode**) malloc(sizeof(BtreeNode*)*DEGREE);
    memset(childNode,0,childSizeLen);
    if(childNode==NULL)
    {
        perror("malloc childNode failed");
        free(keys);
        free(btreeNode);
        return NULL;
    }
    btreeNode->keys = keys;
    btreeNode->childNodes=childNode;
    btreeNode->parent=NULL;
    btreeNode->curKeysNum=0;
    return btreeNode;
}

int  insert_btreenode(BtreeNode** rootPtr,BtreeData  data)
{
    ///初始化根节点
    if((*rootPtr)==NULL)
    {
        BtreeNode* newNode = create_btreenode();
        if(newNode==NULL)
        {
            return INSERT_FAIL;
        }
        newNode->keys[0] = data;
        newNode->curKeysNum = 1;
        (*rootPtr)= newNode;
        return INSERT_SUC;
    }
    ///查找适合插入数据的节点
    BtreeNode* tragetNode = find_childnode_by_keyindex(*rootPtr,data);
    show_node_kyes(tragetNode);
    printf("\n");
    /// 添加关键字
    if(tragetNode!=NULL)
    {
        //添加关键字
        add_data_to_keys(tragetNode,data);
        //当关键字达到KEY_NUM时候,插入新的关键字需要对节点进行分裂操作
        division_btreenode(rootPtr,tragetNode);
        return INSERT_SUC;
    }
    return INSERT_FAIL;
}

int  find_keys_index(BtreeNode* node,BtreeData data)
{
    int index = 0;
    BtreeData* keys = node->keys;
    //平衡查找当前节点关键字的索引
    while(index<node->curKeysNum && keys[index]<data)
    {
        index++;
    }
    return index;
}

BtreeNode* find_childnode_by_keyindex(BtreeNode* node,BtreeData  data)
{
    ///printf("find_childnode_by_keyindex-----data=%d\n",data);
    if(node == NULL)
    {
        perror("find_childnode_by_index fail ,node is null ");
        return NULL;
    }

    BtreeNode** chids=node->childNodes;
    if((*chids)==NULL)
    {
        // 到达叶子节点终止递归
        return node;
    }
    /// 根据索引查找当前数值适合存放的节点
    int index = find_keys_index(node,data);
        ///递归查找子节点
    return find_childnode_by_keyindex(chids[index],data);
}

int add_data_to_keys(BtreeNode* node,BtreeData data)
{
    ///printf("add_data_to_keys  key -> data=%d\n",data);
    int c = 0 ;
    int index = find_keys_index(node,data);
    BtreeData* keys = node->keys;
    node->curKeysNum = node->curKeysNum +1;
    BtreeData  tmp[KEY_NUM];
    for(;c<KEY_NUM;c++)
    {
        tmp[c] = keys[c];
    }
    c = 0 ;
    for(;c<node->curKeysNum;c++)
    {
        if(c >index)
        {
            keys[c] = tmp[c-1];
        }
    }
    keys[index] = data;
    return index;
}

/**
 * 分裂操作
 * @brief division_btreenode
 * @param root
 * @param node
 */
void  division_btreenode(BtreeNode** root,BtreeNode* node)
{
    if(node->curKeysNum<=KEY_NUM)
    {
        return;
    }
    printf("division_btreenode start \n");
    BtreeNode* leftNode = create_btreenode();
    if(leftNode==NULL)
    {
        perror("create left  node  fail");
        return;
    }
    BtreeNode* rightNode = create_btreenode();
    if(rightNode==NULL)
    {
        perror("create right  node  fail");
        free(leftNode);
        return;
    }
    BtreeData*  keys =  node->keys;
    int index = 0;
    while(index < DEGREE/2)
    {
        leftNode->keys[index]=node->keys[index];
        index++;
    }
    index = 0;
    while(index<DEGREE/2)
    {
        rightNode->keys[index] = node->keys[DEGREE/2+index+1];
        index++;
    }
    leftNode->curKeysNum =  DEGREE/2;
    rightNode->curKeysNum = DEGREE/2;
    BtreeData   divisionKey = keys[MID];
    BtreeNode*  parent = node->parent;
    if(parent == NULL)
    {
        /// 根节点分裂
        BtreeNode**  childs = node->childNodes;
        if((*childs)!=NULL)
        {
            index = 0 ;
            int m=0,n=0;
            while (index<=DEGREE) {
                if(index<=MID)
                {
                    leftNode->childNodes[m]=childs[index];
                    m++;
                }
                if(index>MID)
                {
                    rightNode->childNodes[n]=childs[index];
                    n++;
                }
                index++;
            }
        }
        index = 0;
        //重置当前node节点的childs的parent节点
        while (index<=DEGREE && (*(node->childNodes))!=NULL) {
            node->childNodes[index]->parent =index<=MID?leftNode:rightNode;
            index++;
        }
        leftNode->parent =  node;
        rightNode->parent=  node;
        node->childNodes[0] = leftNode;
        node->childNodes[1] = rightNode;
        node->curKeysNum=1;
        /// 重置根结点
        node->keys[0]=divisionKey;
        index= 1;
        while (index<KEY_NUM) {
            node->keys[index]= 0;
            index++;
        }
        (*root)=node;
    }
    else
    {
        int  keyIndex = add_data_to_keys(parent,divisionKey);
        index = 0;
        BtreeNode**  childs = parent->childNodes;
        BtreeNode*   temp[DEGREE];
        for(;index<DEGREE;index++)
        {
            temp[index]= childs[index];
        }
        index = keyIndex;
        while(index<=KEY_NUM-2)
        {
            childs[index+2]=temp[index+1];
            index++;
        }
        index = 0;
        重置当前node节点的childs的parent节点
        while (index<=DEGREE && (*(node->childNodes))!=NULL) {
            node->childNodes[index]->parent =index<=MID?leftNode:rightNode;
            index++;
        }
        leftNode->parent = parent;
        rightNode->parent= parent;
        childs[keyIndex] = leftNode;
        childs[keyIndex+1]= rightNode;
        ///递归处理分裂操作
        division_btreenode(root,parent);
    }
}

BtreeNode*  find_btrenode_by_key(BtreeNode* root,BtreeData data)
{
    if((root)==NULL)
    {
        return NULL;
    }
    int index = find_keys_index(root,data);
    BtreeData  keyData = root->keys[index];
    if(keyData==data)
    {
        return  root;
    }
    BtreeNode*  node = root->childNodes[index];
    return find_btrenode_by_key(node, data);
}

/**
 * 删除节点
 * @brief delete_btreenode
 * @param root
 * @param data
 */
void  delete_btreenode(BtreeNode** root,BtreeData  data)
{
    BtreeNode* tragetNode = find_btrenode_by_key(*root,data);
    ///BtreeNode**  childs = tragetNode->childNodes;
    delete_key_by_index(tragetNode,data);
    //处逻辑理合并
    merge_btreenode(root,tragetNode,data);
}

void  delete_key_by_index(BtreeNode* node,BtreeData  data)
{

    int index = find_keys_index(node,data);
    BtreeData* keys = node->keys;
    BtreeData  tmp[KEY_NUM];
    int c = 0;
    for(;c<KEY_NUM;c++)
    {
        tmp[c] = c<node->curKeysNum?keys[c]:0;
    }
    while(index < node->curKeysNum)
    {
        keys[index]=tmp[index+1];
        index++;
    }
    node->curKeysNum =node->curKeysNum-1;
}

/**
 * 合并操作
 * @brief merge_btreenode
 * @param root
 * @param node
 */
void   merge_btreenode(BtreeNode** root,BtreeNode* node,BtreeData data)
{
    if(node==NULL)
    {
        return;
    }
    if(node->parent==NULL)
    {
        (*root) =  node;
        return;
    }
    BtreeNode*  parent = node->parent;
    int index = find_keys_index(parent,data);
    BtreeNode** childs = parent->childNodes;
    BtreeNode*  leftNode;
    BtreeNode*  rightNode;
    if(index<parent->curKeysNum)
    {
        leftNode=childs[index];
        rightNode=childs[index+1];
    }
    else
    {
        leftNode=childs[index-1];
        rightNode=childs[index];
    }
    int totalKeysNum= 1 + leftNode->curKeysNum + rightNode->curKeysNum;
    if(totalKeysNum>KEY_NUM)
    {
        return;
    }
    ///执行合并
    int  c = 0;
    int  leftMidIndex =  leftNode->curKeysNum;
    BtreeData parentMergeData = parent->keys[index-1];
    leftNode->keys[leftMidIndex] = parentMergeData;
    //重置关键字
    while (c < rightNode->curKeysNum) {
        leftNode->keys[leftMidIndex+1+c]=rightNode->keys[c];
        c++;
    }
    c= 0;
     /// 重置子节点
    while(c<=rightNode->curKeysNum)
    {
        leftNode->childNodes[leftMidIndex+1+c]= rightNode->childNodes[c];
        c++;
    }
    leftNode->curKeysNum = totalKeysNum;
    free(rightNode);
    if(parent->parent==NULL)
    {
        (*root) = leftNode;
        free(parent);
        return;
    }
    delete_key_by_index(parent,parentMergeData);
    merge_btreenode(root,parent,data);
}

void  show_node_kyes(BtreeNode* node)
{
    if(node==NULL)
    {
        printf("show_node_kyes  node  is null \n");
        return;
    }
    int  len =  node->curKeysNum;
    BtreeData* keys = node->keys;
    int  c = 0;
    for(;c<len;c++)
    {
        printf("%d ",keys[c]);
    }
}

void show_btreenode(BtreeNode*  root)
{
    if(root==NULL)
    {
        return;
    }
    BtreeNode** childs =  root->childNodes;
    int len  = root->curKeysNum;
    int c = 0 ;
    show_node_kyes(root);
    printf("\n");
    /// 为了方便看结果,这里值展示三层效果,如果需要展多层,可采用递归查询
    while((*childs)!=NULL && c<=len){
        BtreeNode* chid =  childs[c];
        show_node_kyes(chid);
        c++;
    }
    printf("\n");
    c=0;
    while((*childs)!=NULL && c<=len){
        BtreeNode* chid =  childs[c];
        BtreeNode** subChids = chid->childNodes;
        int n = 0;
        int subLen = chid->curKeysNum;
        while ((*subChids)!=NULL && n<=subLen ) {
            BtreeNode*  tmp =  subChids[n];
            show_node_kyes(tmp);
            n++;
        }
        c++;
    }
    printf("\n");
}
3、测试
#include <stdio.h>
#include "btree.h"

int main()
{
    printf("Hello World  Btree come !\n");
    BtreeData  arr[] = {20,10,11,12,30,31,32,33,15,17,18,5,8,26,35,6,36,37};
    //-----------------添加元素 分裂--------------------------
    //                      18
    //    8       12        ->         31       35
    //5 6 -> 10 11 -> 15 17 -> 20 26 30 -> 32 33 -> 36 37
    BtreeNode* root = NULL;
    int  i ;
    int len =  sizeof(arr)/sizeof(BtreeData);
    for(i=0;i<len;i++)
    {
        insert_btreenode(&root,arr[i]);
    }
    printf("\nstart ------show tree--------------add\n");
    show_btreenode(root);

    //-----------------删除元素 合并---------------------------
    //    8       12       18          31
    //5 6 -> 10 11 -> 15 17 -> 20 26 30 -> 32 33 35 36
    printf("\nstart ------show tree--------------del\n");
    delete_btreenode(&root,37);
    show_btreenode(root);

    return 0;
}

  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: FP-Tree算法是一种高效的频繁项集挖掘算法,适用于大数据集。它通过构建FP-Tree来进行频繁项集挖掘。FP-Tree是一种特殊的结构,由项和链接组成。 在Python中,可以使用第三方库pyfpgrowth来实现FP-Tree算法。使用方法如下: 1. 安装pyfpgrowth库:pip install pyfpgrowth 2. 导入库:from pyfpgrowth import find_frequent_patterns 3. 使用find_frequent_patterns()函数进行频繁项集挖掘,如:frequent_itemsets = find_frequent_patterns(transactions, min_support) 其中transactions是事务数据集,min_support是最小支持度。 ### 回答2: FP-tree算法是一种用于频繁模式挖掘的算法,它是一种新型的挖掘频繁项集的算法,它挖掘频繁项集时利用了一种基于结构的压缩存储的方式,能够大大减少挖掘频繁项集的时间和空间开销。它的主要思路是在构建形结构的同时,对于每个频繁项都记录其支持度计数,这样就可以通过低空间高效的方式挖掘到频繁项集。 在FP-tree算法中,首先需要进行数据预处理,将原始数据转换为FP-tree,在转换过程中需要统计每个元素的支持度计数和元素之间的关联关系,并按照支持度计数从大到小排序,然后剪枝去除支持度计数小于最小支持度的元素。接着,使用FP-tree进行频繁项集挖掘,利用FP-tree的形结构和每个元素的支持度计数,可以使用分治法来递归地挖掘频繁项集。具体地,FP-tree算法使用递归方式遍历FP-tree,并迭代地将条件模式基转换为新的子问题,在这个过程中可以利用条件的模式基来更新FP-tree,最后根据这些更新后的频繁项集可以得出所有的频繁项集。 在Python中,FP-tree可以通过Python的库plyvel来进行实现,通过代码实现节点的添加,查询,删除,更新等操作,从而实现FP-tree的构建和使用。具体地,对于每个节点,可以用Python中的数据结构字典来进行表示,字典中包括节点的名称,支持度计数,以及指向父节点和子节点的指针。在建的过程中,可以使用递归方式遍历每个元素,并根据元素的支持度计数来进行排序和剪枝。在挖掘频繁项集的过程中,通过对进行递归遍历,可以找到每个元素的条件模式基,并使用条件模式基来更新FP-tree,最终可以得到所有的频繁项集。 总而言之,FP-tree算法是一种基于形结构和压缩存储的频繁项集挖掘算法,在实现上可以通过Python中的字典来进行节点的表示和操作,在具体实现中需要注意剪枝和更新的操作,以及频繁项集的递归遍历。 ### 回答3: FP算法是一种非常常见的频繁项集挖掘算法,它是由Han等人在2000年提出的。相比传统的Apriori算法,FP算法的优点在于它可以高效地挖掘频繁项集,大幅提升了算法的效率和准确性。 FP算法的核心是FP数据结构,它是一种压缩存储的形结构,能够高效地存储频繁项集信息。FP算法的具体步骤如下: 1. 构建项头表:遍历所有的事务,统计每个项出现的频次,并按照频次从大到小排序,构建项头表。 2. 构建FP:遍历所有的事务,按照事务中项的顺序构建FP。每个事务中的项要按照项头表中的顺序排序。如果中已存在该项的节点,则将该节点的计数加1;否则,添加一个新的节点。 3. 抽取频繁项集:根据构建好的FP和项头表,采用递归的方式,从FP底部往上挖掘频繁项集。 FP算法的实现可以使用Python语言,需要用到的库包括numpy和pandas。具体实现步骤如下: 1. 构建项头表:遍历数据集,统计每个项出现的频次,将频繁项保存到一个字典中。 ```python def create_item_dict(data_set): item_dict = {} for trans in data_set: for item in trans: if item in item_dict: item_dict[item] += 1 else: item_dict[item] = 1 return item_dict ``` 2. 构建FP:遍历数据集,按照项头表中的顺序构建FP。 ```python class TreeNode: def __init__(self, name_value, num_occurrences, parent_node): self.name = name_value self.count = num_occurrences self.parent = parent_node self.children = {} self.next = None def inc(self, num_occurrences): self.count += num_occurrences def create_tree(data_set, min_sup=1): item_dict = create_item_dict(data_set) freq_items = set([item for item in item_dict.keys() if item_dict[item] >= min_sup]) if len(freq_items) == 0: return None, None for item in item_dict.keys(): if item not in freq_items: del(item_dict[item]) header_table = {item: [item_dict[item], None] for item in freq_items} root_node = TreeNode('Null Set', 1, None) for trans in data_set: local_dict = {} for item in trans: if item in freq_items: local_dict[item] = header_table[item][0] if len(local_dict) > 0: ordered_items = [item[0] for item in sorted(local_dict.items(), key=lambda x: (-x[1], x[0]))] update_tree(ordered_items, root_node, header_table) return root_node, header_table def update_tree(items, cur_node, header_table): if items[0] in cur_node.children: cur_node.children[items[0]].inc(1) else: cur_node.children[items[0]] = TreeNode(items[0], 1, cur_node) if header_table[items[0]][1] is None: header_table[items[0]][1] = cur_node.children[items[0]] else: update_header(header_table[items[0]][1], cur_node.children[items[0]]) if len(items) > 1: update_tree(items[1:], cur_node.children[items[0]], header_table) def update_header(node_to_test, target_node): while node_to_test.next is not None: node_to_test = node_to_test.next node_to_test.next = target_node ``` 3. 抽取频繁项集:采用递归的方式,从FP底部往上挖掘频繁项集。 ```python def ascend_tree(leaf_node, prefix_path): if leaf_node.parent is not None: prefix_path.append(leaf_node.name) ascend_tree(leaf_node.parent, prefix_path) def find_prefix_path(base_pat, header_table): node = header_table[base_pat][1] pats = [] while node is not None: prefix_path = [] ascend_tree(node, prefix_path) if len(prefix_path) > 1: pats.append(prefix_path[1:]) node = node.next return pats def mine_tree(in_tree, header_table, min_sup, pre_fix, freq_item_list): bigL = [v[0] for v in sorted(header_table.items(), key=lambda x: (-x[1][0], x[0]))] for base_pat in bigL: new_freq_set = pre_fix.copy() new_freq_set.add(base_pat) freq_item_list.append(new_freq_set) cond_patt_bases = find_prefix_path(base_pat, header_table) my_cond_tree, my_head = create_tree(cond_patt_bases, min_sup) if my_head is not None: mine_tree(my_cond_tree, my_head, min_sup, new_freq_set, freq_item_list) ``` 通过以上实现,我们就可以使用FP算法高效地挖掘频繁项集了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值