B树&&B+树

打印 上一主题 下一主题

主题 866|帖子 866|积分 2598

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. // 定义 B 树的最小度数(每个节点至少有 t 个孩子,最多有 2t - 1 个键值)
  4. #define T 3
  5. // B 树节点结构
  6. typedef struct BTreeNode {
  7.     int *keys;               // 存储键值的数组
  8.     int t;                   // 最小度数
  9.     struct BTreeNode **C;    // 子节点指针数组
  10.     int n;                   // 当前节点的键值个数
  11.     int leaf;                // 是否是叶子节点
  12. } BTreeNode;
  13. // 创建一个新的 B 树节点
  14. BTreeNode *createNode(int t, int leaf) {
  15.     BTreeNode *node = (BTreeNode *)malloc(sizeof(BTreeNode));
  16.     if (!node) {
  17.         fprintf(stderr, "Memory allocation failed for BTreeNode.\n");
  18.         exit(EXIT_FAILURE);
  19.     }
  20.     node->t = t;
  21.     node->leaf = leaf;
  22.     node->keys = (int *)malloc((2 * t - 1) * sizeof(int));
  23.     node->C = (BTreeNode **)malloc(2 * t * sizeof(BTreeNode *));
  24.     node->n = 0;
  25.     if (!node->keys || !node->C) {
  26.         fprintf(stderr, "Memory allocation failed for keys or children.\n");
  27.         exit(EXIT_FAILURE);
  28.     }
  29.     return node;
  30. }
  31. // 遍历 B 树(中序遍历)
  32. void traverse(BTreeNode *root) {
  33.     if (root == NULL) return;
  34.     // 遍历每个键值前的子树
  35.     for (int i = 0; i < root->n; i++) {
  36.         if (!root->leaf) {
  37.             traverse(root->C[i]);
  38.         }
  39.         printf("%d ", root->keys[i]); // 打印键值
  40.     }
  41.     // 遍历最后一个子树
  42.     if (!root->leaf) {
  43.         traverse(root->C[root->n]);
  44.     }
  45. }
  46. // 搜索键值
  47. BTreeNode *search(BTreeNode *root, int k) {
  48.     int i = 0;
  49.     // 在当前节点中查找键值的位置
  50.     while (i < root->n && k > root->keys[i]) {
  51.         i++;
  52.     }
  53.     // 如果找到键值,返回当前节点
  54.     if (i < root->n && root->keys[i] == k) {
  55.         return root;
  56.     }
  57.     // 如果是叶子节点,直接返回 NULL
  58.     if (root->leaf) {
  59.         return NULL;
  60.     }
  61.     // 在对应的子节点递归查找
  62.     return search(root->C[i], k);
  63. }
  64. // 分裂子节点 y
  65. void splitChild(BTreeNode *parent, int i, BTreeNode *y) {
  66.     int t = y->t;
  67.     // 创建新节点 z,存储 y 的后半部分键值和子节点
  68.     BTreeNode *z = createNode(t, y->leaf);
  69.     z->n = t - 1;
  70.     // 将 y 的后半部分键值复制到 z
  71.     for (int j = 0; j < t - 1; j++) {
  72.         z->keys[j] = y->keys[j + t];
  73.     }
  74.     // 如果 y 不是叶子节点,将 y 的后半部分子节点指针复制到 z
  75.     if (!y->leaf) {
  76.         for (int j = 0; j < t; j++) {
  77.             z->C[j] = y->C[j + t];
  78.         }
  79.     }
  80.     y->n = t - 1; // 更新 y 的键值个数
  81.     // 将 z 插入到 parent 的子节点列表中
  82.     for (int j = parent->n; j >= i + 1; j--) {
  83.         parent->C[j + 1] = parent->C[j];
  84.     }
  85.     parent->C[i + 1] = z;
  86.     // 将 y 的中间键提升到 parent
  87.     for (int j = parent->n - 1; j >= i; j--) {
  88.         parent->keys[j + 1] = parent->keys[j];
  89.     }
  90.     parent->keys[i] = y->keys[t - 1];
  91.     parent->n++;
  92. }
  93. // 插入到非满节点中
  94. void insertNonFull(BTreeNode *node, int k) {
  95.     int i = node->n - 1;
  96.     // 如果是叶子节点
  97.     if (node->leaf) {
  98.         // 找到插入位置并移动键值
  99.         while (i >= 0 && k < node->keys[i]) {
  100.             node->keys[i + 1] = node->keys[i];
  101.             i--;
  102.         }
  103.         node->keys[i + 1] = k; // 插入键值
  104.         node->n++;
  105.     } else {
  106.         // 找到子节点的位置
  107.         while (i >= 0 && k < node->keys[i]) {
  108.             i--;
  109.         }
  110.         i++;
  111.         // 如果子节点已满,先分裂子节点
  112.         if (node->C[i]->n == 2 * node->t - 1) {
  113.             splitChild(node, i, node->C[i]);
  114.             // 判断 k 应插入到哪个子节点
  115.             if (k > node->keys[i]) {
  116.                 i++;
  117.             }
  118.         }
  119.         insertNonFull(node->C[i], k);
  120.     }
  121. }
  122. // 插入键值到 B 树
  123. void insert(BTreeNode **rootRef, int k, int t) {
  124.     BTreeNode *root = *rootRef;
  125.     // 如果根节点已满
  126.     if (root->n == 2 * t - 1) {
  127.         // 创建新根节点
  128.         BTreeNode *newRoot = createNode(t, 0);
  129.         newRoot->C[0] = root;
  130.         // 分裂旧根节点
  131.         splitChild(newRoot, 0, root);
  132.         // 插入到合适的子节点
  133.         int i = 0;
  134.         if (k > newRoot->keys[0]) {
  135.             i++;
  136.         }
  137.         insertNonFull(newRoot->C[i], k);
  138.         *rootRef = newRoot; // 更新根节点
  139.     } else {
  140.         insertNonFull(root, k);
  141.     }
  142. }
  143. // 释放 B 树节点
  144. void freeBTree(BTreeNode *node) {
  145.     if (node == NULL) return;
  146.     if (!node->leaf) {
  147.         for (int i = 0; i <= node->n; i++) {
  148.             freeBTree(node->C[i]);
  149.         }
  150.     }
  151.     free(node->keys);
  152.     free(node->C);
  153.     free(node);
  154. }
  155. // 主函数
  156. int main() {
  157.     // 创建空 B 树
  158.     BTreeNode *root = createNode(T, 1);
  159.     // 插入一些数据
  160.     insert(&root, 10, T);
  161.     insert(&root, 20, T);
  162.     insert(&root, 5, T);
  163.     insert(&root, 6, T);
  164.     insert(&root, 12, T);
  165.     insert(&root, 30, T);
  166.     insert(&root, 7, T);
  167.     insert(&root, 17, T);
  168.     // 遍历 B 树
  169.     printf("Traversal of the B-Tree:\n");
  170.     traverse(root);
  171.     printf("\n");
  172.     // 搜索键值
  173.     int key = 6;
  174.     printf("Searching for key %d in the B-Tree...\n", key);
  175.     BTreeNode *result = search(root, key);
  176.     if (result != NULL) {
  177.         printf("Key %d found in the B-Tree.\n", key);
  178.     } else {
  179.         printf("Key %d not found in the B-Tree.\n", key);
  180.     }
  181.     // 释放 B 树
  182.     freeBTree(root);
  183.     return 0;
  184. }
复制代码
  C语言b树

  

  • 数据结构

    • 每个节点存储一个 keys 数组和一个 C 子节点指针数组。
    • n 表现当前键值数,leaf 表现是否为叶子节点。

  • 主要功能

    • 创建节点:createNode 动态分配节点。
    • 插入:插入时处理节点分裂,包管 B 树平衡。
    • 查找:递归在树中查找键值。
    • 遍历:按中序遍历输出键值。
    • 释放内存:freeBTree 确保无内存泄漏。

  • 加强结实性

    • 内存分配检测:每次动态分配都检查是否乐成。
    • 边界条件处理:空树或单节点插入、满节点分裂等情况均已处理。

  1. #include <iostream>
  2. #include <vector>
  3. #include <memory>
  4. using namespace std;
  5. // 定义 B 树的最小度数
  6. const int T = 3; // 每个节点最多 2*T-1 个键,最少 T-1 个键
  7. // B 树节点类
  8. class BTreeNode {
  9. public:
  10.     vector<int> keys;                   // 键值数组
  11.     vector<shared_ptr<BTreeNode>> children; // 子节点指针数组
  12.     int t;                              // 最小度数
  13.     bool leaf;                          // 是否为叶子节点
  14.     // 构造函数
  15.     BTreeNode(int t, bool leaf) : t(t), leaf(leaf) {}
  16.     // 遍历 B 树(中序遍历)
  17.     void traverse() {
  18.         int i;
  19.         // 遍历每个键值前的子树
  20.         for (i = 0; i < keys.size(); i++) {
  21.             if (!leaf) {
  22.                 children[i]->traverse();
  23.             }
  24.             cout << keys[i] << " ";
  25.         }
  26.         // 遍历最后一个子树
  27.         if (!leaf) {
  28.             children[i]->traverse();
  29.         }
  30.     }
  31.     // 搜索键值
  32.     shared_ptr<BTreeNode> search(int k) {
  33.         int i = 0;
  34.         // 找到第一个大于或等于 k 的键
  35.         while (i < keys.size() && k > keys[i]) {
  36.             i++;
  37.         }
  38.         // 如果键值等于 k,返回当前节点
  39.         if (i < keys.size() && keys[i] == k) {
  40.             return shared_from_this();
  41.         }
  42.         // 如果是叶子节点,则键值不存在
  43.         if (leaf) {
  44.             return nullptr;
  45.         }
  46.         // 在对应的子节点递归查找
  47.         return children[i]->search(k);
  48.     }
  49.     // 分裂子节点
  50.     void splitChild(int i, shared_ptr<BTreeNode> y) {
  51.         // 创建一个新节点 z,存储 y 的后半部分键值和子节点
  52.         auto z = make_shared<BTreeNode>(y->t, y->leaf);
  53.         z->keys.resize(t - 1);
  54.         for (int j = 0; j < t - 1; j++) {
  55.             z->keys[j] = y->keys[j + t];
  56.         }
  57.         // 如果 y 不是叶子节点,将 y 的后半部分子节点移动到 z
  58.         if (!y->leaf) {
  59.             z->children.resize(t);
  60.             for (int j = 0; j < t; j++) {
  61.                 z->children[j] = y->children[j + t];
  62.             }
  63.         }
  64.         // 调整 y 的键值数
  65.         y->keys.resize(t - 1);
  66.         // 在父节点中插入新的子节点 z
  67.         children.insert(children.begin() + i + 1, z);
  68.         // 将 y 的中间键提升到父节点
  69.         keys.insert(keys.begin() + i, y->keys[t - 1]);
  70.     }
  71.     // 插入到非满节点中
  72.     void insertNonFull(int k) {
  73.         int i = keys.size() - 1;
  74.         if (leaf) {
  75.             // 如果是叶子节点,找到插入位置并插入
  76.             while (i >= 0 && k < keys[i]) {
  77.                 i--;
  78.             }
  79.             keys.insert(keys.begin() + i + 1, k);
  80.         } else {
  81.             // 找到子节点位置
  82.             while (i >= 0 && k < keys[i]) {
  83.                 i--;
  84.             }
  85.             i++;
  86.             // 如果子节点已满,先分裂子节点
  87.             if (children[i]->keys.size() == 2 * t - 1) {
  88.                 splitChild(i, children[i]);
  89.                 // 判断插入位置
  90.                 if (k > keys[i]) {
  91.                     i++;
  92.                 }
  93.             }
  94.             children[i]->insertNonFull(k);
  95.         }
  96.     }
  97. };
  98. // B 树类
  99. class BTree {
  100. private:
  101.     shared_ptr<BTreeNode> root; // 根节点
  102.     int t;                      // 最小度数
  103. public:
  104.     // 构造函数
  105.     BTree(int t) : t(t), root(nullptr) {}
  106.     // 遍历 B 树
  107.     void traverse() {
  108.         if (root != nullptr) {
  109.             root->traverse();
  110.         }
  111.     }
  112.     // 搜索键值
  113.     shared_ptr<BTreeNode> search(int k) {
  114.         if (root == nullptr) {
  115.             return nullptr;
  116.         } else {
  117.             return root->search(k);
  118.         }
  119.     }
  120.     // 插入键值
  121.     void insert(int k) {
  122.         if (root == nullptr) {
  123.             // 如果根节点为空,创建一个新节点
  124.             root = make_shared<BTreeNode>(t, true);
  125.             root->keys.push_back(k);
  126.         } else {
  127.             // 如果根节点已满
  128.             if (root->keys.size() == 2 * t - 1) {
  129.                 // 创建一个新根节点
  130.                 auto newRoot = make_shared<BTreeNode>(t, false);
  131.                 // 将旧根节点作为新根的子节点
  132.                 newRoot->children.push_back(root);
  133.                 // 分裂旧根节点
  134.                 newRoot->splitChild(0, root);
  135.                 // 插入到合适的子节点
  136.                 if (k > newRoot->keys[0]) {
  137.                     newRoot->children[1]->insertNonFull(k);
  138.                 } else {
  139.                     newRoot->children[0]->insertNonFull(k);
  140.                 }
  141.                 root = newRoot; // 更新根节点
  142.             } else {
  143.                 root->insertNonFull(k);
  144.             }
  145.         }
  146.     }
  147. };
  148. // 主函数
  149. int main() {
  150.     // 创建 B 树
  151.     BTree tree(T);
  152.     // 插入一些数据
  153.     tree.insert(10);
  154.     tree.insert(20);
  155.     tree.insert(5);
  156.     tree.insert(6);
  157.     tree.insert(12);
  158.     tree.insert(30);
  159.     tree.insert(7);
  160.     tree.insert(17);
  161.     // 遍历 B 树
  162.     cout << "Traversal of the B-Tree:\n";
  163.     tree.traverse();
  164.     cout << endl;
  165.     // 搜索键值
  166.     int key = 6;
  167.     cout << "Searching for key " << key << " in the B-Tree..." << endl;
  168.     auto result = tree.search(key);
  169.     if (result != nullptr) {
  170.         cout << "Key " << key << " found in the B-Tree.\n";
  171.     } else {
  172.         cout << "Key " << key << " not found in the B-Tree.\n";
  173.     }
  174.     return 0;
  175. }
复制代码
  CPP版本b树

  

  • 数据结构设计

    • BTreeNode 类表现 B 树的节点,包罗键值数组 (keys) 和子节点指针数组 (children)。
    • BTree 类表现 B 树,封装了树的根节点和相干操纵。

  • 核心操纵

    • 创建节点:通过构造函数动态创建新节点。
    • 插入

      • 假如根节点已满,会创建一个新根节点并分裂旧根。
      • 插入操纵通过递归调整子节点,确保 B 树的平衡性。

    • 查找:在节点中二分查找键值并递归到子节点。
    • 遍历:中序遍历全部键值。

  • 加强结实性

    • 使用 vector 自动管理数组内存,避免手动分配和释放。
    • 使用 shared_ptr 管理节点的生命周期,防止内存泄漏。
    • 异常检测和边界条件处理(如空树或满节点)。

  • 输入输出

    • 插入一组数据构建 B 树。
    • 遍历 B 树,输出全部键值。
    • 搜索指定键值,输出是否存在。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <stdbool.h>
  4. // 定义 B+树的最小度数 T
  5. #define T 3
  6. // 定义 B+树节点结构
  7. typedef struct BPTreeNode {
  8.     int *keys;                      // 键值数组
  9.     struct BPTreeNode **children;   // 子节点指针数组
  10.     int n;                          // 当前节点的键值数量
  11.     bool isLeaf;                    // 是否为叶子节点
  12.     struct BPTreeNode *next;        // 用于叶子节点的链表指针
  13. } BPTreeNode;
  14. // 创建一个新节点
  15. BPTreeNode *createNode(bool isLeaf) {
  16.     BPTreeNode *node = (BPTreeNode *)malloc(sizeof(BPTreeNode));
  17.     if (!node) {
  18.         fprintf(stderr, "Memory allocation failed for BPTreeNode.\n");
  19.         exit(EXIT_FAILURE);
  20.     }
  21.     node->keys = (int *)malloc((2 * T - 1) * sizeof(int));
  22.     node->children = (BPTreeNode **)malloc(2 * T * sizeof(BPTreeNode *));
  23.     node->n = 0;
  24.     node->isLeaf = isLeaf;
  25.     node->next = NULL;
  26.     if (!node->keys || !node->children) {
  27.         fprintf(stderr, "Memory allocation failed for keys or children.\n");
  28.         exit(EXIT_FAILURE);
  29.     }
  30.     return node;
  31. }
  32. // 遍历 B+树的叶子节点(范围查询)
  33. void traverse(BPTreeNode *root) {
  34.     if (!root) return;
  35.     // 找到最左叶子节点
  36.     while (!root->isLeaf) {
  37.         root = root->children[0];
  38.     }
  39.     // 遍历所有叶子节点
  40.     while (root) {
  41.         for (int i = 0; i < root->n; i++) {
  42.             printf("%d ", root->keys[i]);
  43.         }
  44.         root = root->next;
  45.     }
  46.     printf("\n");
  47. }
  48. // 搜索键值
  49. BPTreeNode *search(BPTreeNode *root, int key) {
  50.     if (!root) return NULL;
  51.     int i = 0;
  52.     // 找到键值范围
  53.     while (i < root->n && key > root->keys[i]) {
  54.         i++;
  55.     }
  56.     if (root->isLeaf) {
  57.         // 如果是叶子节点,检查是否找到
  58.         if (i < root->n && root->keys[i] == key) {
  59.             return root;
  60.         }
  61.         return NULL;
  62.     }
  63.     // 如果不是叶子节点,递归到子节点
  64.     return search(root->children[i], key);
  65. }
  66. // 分裂非叶子节点的子节点
  67. void splitNonLeafChild(BPTreeNode *parent, int idx, BPTreeNode *child) {
  68.     // 创建新节点
  69.     BPTreeNode *newNode = createNode(child->isLeaf);
  70.     newNode->n = T - 1;
  71.     // 将后半部分的键值复制到新节点
  72.     for (int i = 0; i < T - 1; i++) {
  73.         newNode->keys[i] = child->keys[i + T];
  74.     }
  75.     // 如果不是叶子节点,将子节点指针复制到新节点
  76.     if (!child->isLeaf) {
  77.         for (int i = 0; i < T; i++) {
  78.             newNode->children[i] = child->children[i + T];
  79.         }
  80.     }
  81.     child->n = T - 1;
  82.     // 插入新的子节点到父节点
  83.     for (int i = parent->n; i >= idx + 1; i--) {
  84.         parent->children[i + 1] = parent->children[i];
  85.     }
  86.     parent->children[idx + 1] = newNode;
  87.     // 将中间键值插入到父节点
  88.     for (int i = parent->n - 1; i >= idx; i--) {
  89.         parent->keys[i + 1] = parent->keys[i];
  90.     }
  91.     parent->keys[idx] = child->keys[T - 1];
  92.     parent->n++;
  93. }
  94. // 分裂叶子节点
  95. void splitLeafChild(BPTreeNode *parent, int idx, BPTreeNode *leaf) {
  96.     // 创建一个新叶子节点
  97.     BPTreeNode *newLeaf = createNode(true);
  98.     newLeaf->n = T - 1;
  99.     // 将后半部分的键值复制到新叶子节点
  100.     for (int i = 0; i < T - 1; i++) {
  101.         newLeaf->keys[i] = leaf->keys[i + T - 1];
  102.     }
  103.     leaf->n = T - 1;
  104.     // 设置叶子节点的链表指针
  105.     newLeaf->next = leaf->next;
  106.     leaf->next = newLeaf;
  107.     // 插入新叶子节点到父节点
  108.     for (int i = parent->n; i >= idx + 1; i--) {
  109.         parent->children[i + 1] = parent->children[i];
  110.     }
  111.     parent->children[idx + 1] = newLeaf;
  112.     // 将新叶子节点的第一个键值插入到父节点
  113.     for (int i = parent->n - 1; i >= idx; i--) {
  114.         parent->keys[i + 1] = parent->keys[i];
  115.     }
  116.     parent->keys[idx] = newLeaf->keys[0];
  117.     parent->n++;
  118. }
  119. // 插入到非满节点中
  120. void insertNonFull(BPTreeNode *node, int key) {
  121.     int i = node->n - 1;
  122.     if (node->isLeaf) {
  123.         // 找到插入位置
  124.         while (i >= 0 && key < node->keys[i]) {
  125.             node->keys[i + 1] = node->keys[i];
  126.             i--;
  127.         }
  128.         node->keys[i + 1] = key;
  129.         node->n++;
  130.     } else {
  131.         // 找到子节点位置
  132.         while (i >= 0 && key < node->keys[i]) {
  133.             i--;
  134.         }
  135.         i++;
  136.         // 如果子节点已满
  137.         if (node->children[i]->n == 2 * T - 1) {
  138.             if (node->children[i]->isLeaf) {
  139.                 splitLeafChild(node, i, node->children[i]);
  140.             } else {
  141.                 splitNonLeafChild(node, i, node->children[i]);
  142.             }
  143.             // 确定插入位置
  144.             if (key > node->keys[i]) {
  145.                 i++;
  146.             }
  147.         }
  148.         insertNonFull(node->children[i], key);
  149.     }
  150. }
  151. // 插入键值到 B+树
  152. void insert(BPTreeNode **rootRef, int key) {
  153.     BPTreeNode *root = *rootRef;
  154.     if (!root) {
  155.         // 如果根节点为空,创建一个新节点
  156.         root = createNode(true);
  157.         root->keys[0] = key;
  158.         root->n = 1;
  159.         *rootRef = root;
  160.         return;
  161.     }
  162.     // 如果根节点已满
  163.     if (root->n == 2 * T - 1) {
  164.         // 创建一个新根节点
  165.         BPTreeNode *newRoot = createNode(false);
  166.         newRoot->children[0] = root;
  167.         // 分裂根节点
  168.         if (root->isLeaf) {
  169.             splitLeafChild(newRoot, 0, root);
  170.         } else {
  171.             splitNonLeafChild(newRoot, 0, root);
  172.         }
  173.         // 插入到新根节点的合适位置
  174.         int i = (key > newRoot->keys[0]) ? 1 : 0;
  175.         insertNonFull(newRoot->children[i], key);
  176.         *rootRef = newRoot;
  177.     } else {
  178.         insertNonFull(root, key);
  179.     }
  180. }
  181. // 释放 B+树节点
  182. void freeBPTree(BPTreeNode *node) {
  183.     if (!node) return;
  184.     if (!node->isLeaf) {
  185.         for (int i = 0; i <= node->n; i++) {
  186.             freeBPTree(node->children[i]);
  187.         }
  188.     }
  189.     free(node->keys);
  190.     free(node->children);
  191.     free(node);
  192. }
  193. // 主函数
  194. int main() {
  195.     BPTreeNode *root = NULL;
  196.     // 插入数据
  197.     insert(&root, 10);
  198.     insert(&root, 20);
  199.     insert(&root, 5);
  200.     insert(&root, 6);
  201.     insert(&root, 12);
  202.     insert(&root, 30);
  203.     insert(&root, 7);
  204.     insert(&root, 17);
  205.     // 遍历 B+树
  206.     printf("Traversal of the B+ Tree:\n");
  207.     traverse(root);
  208.     // 搜索键值
  209.     int key = 6;
  210.     printf("Searching for key %d in the B+ Tree...\n", key);
  211.     BPTreeNode *result = search(root, key);
  212.     if (result) {
  213.         printf("Key %d found in the B+ Tree.\n", key);
  214.     } else {
  215.         printf("Key %d not found in the B+ Tree.\n", key);
  216.     }
  217.     // 释放 B+树
  218.     freeBPTree(root);
  219.     return 0;
  220. }
复制代码
  C语言版本b+树

  

  • 数据结构

    • BPTreeNode 表现 B+树的节点,包罗 keys、children 和 next(叶子节点链表指针)。
    • 支持叶子节点链表结构,优化范围查询。

  • 主要功能

    • 插入:自动处理节点分裂,支持叶子和非叶子节点分裂。
    • 搜索:递归搜索键值。
    • 遍历:从叶子节点链表输出全部键值。
    • 内存释放:递归释放全部节点,避免内存泄漏。

  • 结实性

    • 检查内存分配是否乐成,失败时终止步伐。
    • 处理空树和根节点分裂等边界情况。

  • 测试功能

    • 插入一组数据,输出全部键值。
    • 搜索特定键值,输出是否找到。

   
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

千千梦丶琪

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表