15哈夫曼树/哈夫曼编码

这篇具有很好参考价值的文章主要介绍了15哈夫曼树/哈夫曼编码。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

哈夫曼树的基本概念

哈夫曼树又称为最优树,作用是找到一种效率最高的判断树。

  1. 路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。

  2. 结点的路径长度:两结点间路径上的分支树

  • 如图 a :从 A - D 的路径长度就是是 2。从 A 到 B C D E F G F I 的路径长度分别为 1 1 2 2 3 3 4 4
  • 如图 b:从 A 到 B C D E F G H I 的路径长度分别为 1 1 2 2 2 2 3 3。

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

  1. 树的路径长度:从树根到每一个结点的路径长度之和称为树的路径长度。
    • 记作:TL(根节点到它自身的结点路径长度为0)
    • 结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树,但是路径长度最短的不一定是完全二叉树。

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

  1. 权(weight):将树中结点赋个一个有着某种含义的数值,则称这个数值为该

结点的权。

  1. **结点的带权路径长度:**从根结点到该结点之间的路径长度与该结点的权的乘积。
  • 如前言中的树中的 小于60分的人数占比 X 到该分支的判断次数( 5 % X 3,)5%是权,3 是路径长度。
  1. 树的带权路径长度:树中所有叶子结点带权路径之和。

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

  1. **哈夫曼树:**树的带权路径长度(WPL)最短的树。
    • 带权路径长度最短是在树的度相同的树中比较而得到的结果,因此有最优二叉树,最优三叉树之称等等。
  • 最优二叉树:树的带权路径长度(WPL)最短的二叉树。

哈夫曼树的特点

权值越大的结点离根节点越近,权值越小的结点离根节点越远,能使总路径越短。

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

  • 满二叉树不一定是哈夫曼树。
  • 具有相同带权节点的哈夫曼树不唯一,如上图的 3树和4树,都是哈夫曼树但并不是同一棵树。

哈夫曼树的构造算法

贪心算法

  • 构造哈夫曼树时首选选择权值小的叶子结点。
  • 这样就可以将权值大的叶子放到后面构造,大的留在最后就可以离根近一些。

1. 哈夫曼树的构造过程

  1. 根据 n 个给定的权值 {W₁,W₂,W₃…,Wn} 构成n棵二叉树的森林 F = {T₁,T₂,T₃…,Tn},其中 Ti只有一个带权威 Wi的根节点。
    • ==构造的森林全是根。==这个森林中,给了几个权值就有几棵树。
  2. 在森林 F 中选取两棵根节点的权值最小的树作为左右子树,构造一棵新的二叉树,且设置新的二叉树的根节点的权值为其左右子树上根结点的权值之和。
    • 选用两小造新树
  3. 在森林 F 中删除这两棵树,同时将新得到的二叉树加入森林中。
    • 删除两小添新人
  4. 重复上面的 步骤2 和 步骤3,直到森林中只有一棵树为止,这棵树即为哈夫曼树。
    • 重复 2、3 剩单根

例如:

有 4 个结点分别是 a b c d 权值分别为 7 5 2 4,试构造哈夫曼树。

  1. 构造的森林全是根。

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

  1. 选用两小造新树。

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

  1. 删除两小添新人。

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

  1. 重复 2、3 剩单根
  2. 哈夫曼树编码,数据结构C语言,算法,数据结构,图论

因为构造哈夫曼树的时候是,选用两小造新树。

  • 所以:哈夫曼树的结点的度为 0 或 2,没有度为 1 的结点。
  • 包含 n 个叶子结点的哈夫曼树中共有 2n -1 个结点。

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

总结

  1. 在哈夫曼算法中,初始时有 n 棵二叉树,要经过 n-1 次合并最终成为哈夫曼树。
  2. 经过 n-1 次合并产生 n-1个新结点,且这 n-1 个新结点都是具有两个孩子的分支结点
  3. 可见:哈夫曼树中共有 n+n-1 = 2n-1 个结点,且其所有的分支结点的度均不为1

代码实现

  • 采用顺序存储结构——一维结构体数组
  • 结点类型定义
typedef struct
{
		int weight;//结点的权重
		int parent;//结点的双亲
		int lchild,rchild;//结点的左、右孩子下标

	}HuffmanTree;//动态分配数组存储哈夫曼树
  • 哈夫曼树一共有n个叶子结点,和n-1个非叶子结点,因此结点总数为2n-1个。如果使用顺序存储方式来实现哈夫曼树,那么需要开辟一个大小为2n的数组来存储所有的结点,其中0下标不用。

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

  • 有 8 个权值为 W ={7,19,2,6,32,3,21,10} 的叶子结点,构造哈夫曼树。
  • 这棵哈夫曼树则有 2n-1=2x8-1=15 个结点。
  • 那么现在就应该构造有 2n = 2x8= 16 个元素的数组。

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

假设有如下n个权值:7、5、2、4、8、3。

  1. 开辟一个大小为2n-1的数组ht,用于存储所有的节点,初始化所有节点的parent、left、right属性为0。
  2. 初始化前n个节点为叶子节点,并把它们的权值存入权值数组weights中。
  3. 对于所有的叶子节点,创建一个长度为n的编码数组hc,并将所有元素初始化为0。
  4. 依次选择权值最小的两个节点,将它们合并成一个新的节点,并将它们的父节点设置为新的节点,并把新节点的权值设置为两个子节点的权值之和。
  5. 将新节点存入数组ht中,并将新节点的下标作为其左右孩子节点的父节点。
  6. 重复步骤4和5,直到所有节点都合并成一个根节点为止。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
/*这个是用了第0下标的代码*/
#define MaxSize 1000   //定义最大结点数

//结点结构体
typedef struct Node{
    int weight ;  //权值
    int parent ;  //父节点下标
    int left_child ;    //左子节点下标
    int right_child;    //右子结点下标
}Node;

//哈夫曼树结构体
typedef struct HuffmanTree{
    int n;   //节点数
    Node nodes[MaxSize];   //结点数组
}HuffmanTree;

//选择两个权值最小的结点
void select_min_two(HuffmanTree *ht,int *p1, int *p2)
{
    int i;
    int w1=INT_MAX,w2=INT_MAX;   //初始化为最大值
    *p1=*p2=-1;   //初始化为无效值
    for (i = 0;  i< ht->n; i++)
    {     // 如果当前节点是没有父节点的,且它的权重比当前最小值还要小
        if (ht->nodes[i].parent == -1)  //未被选择
        {
            if(ht->nodes[i].weight<w1)     //比当前最小值还小
            {
                *p2=*p1;   // 把最小值赋值给次小值
                w2=w1;
                *p1=i;     // 更新最小值下标
                w1=ht->nodes[i].weight;    // 更新最小值
            }
            else if(ht->nodes[i].weight<w2)   //比当前次小值还小
            {
                *p2=i;    // 更新次小值下标
                w2=ht->nodes[i].weight;    // 更新次小值
            }
        }
    }
}

//构造哈夫曼树
void build_huffman_tree(HuffmanTree *ht,int *weights,int n)
{
    int i,p1,p2;
    if(n>MaxSize)    // 节点数不能超过最大值
    {
        printf("Too many nodes");
        return ;
    }
    if (n == 1) { // 如果只有一个节点,构建一个单节点树
        ht->n = 1;
        ht->nodes[0].weight = weights[0];
        ht->nodes[0].parent = -1;
        ht->nodes[0].left_child = -1;
        ht->nodes[0].right_child = -1;
        return;
    }

    ht->n=n;  // 节点数等于叶子节点数
    // 初始化叶子节点
    for (i = 0; i < n; i++) {
        ht->nodes[i].weight = weights[i];
        ht->nodes[i].parent = -1;
        ht->nodes[i].left_child = -1;
        ht->nodes[i].right_child = -1;
    }
    // 初始化非叶子节点
    for (i = n; i < 2 * n - 1; i++) {
        ht->nodes[i].weight = 0;
        ht->nodes[i].parent = -1;
        ht->nodes[i].left_child = -1;
        ht->nodes[i].right_child = -1;
    }
    // 逐步合并节点,构造哈夫曼树
    for (i = n; i < 2 * n - 1; i++) {
        select_min_two(ht, &p1, &p2); // 选择权值最小的两个节点
        ht->nodes[i].weight = ht->nodes[p1].weight + ht->nodes[p2].weight;
        ht->nodes[p1].parent = i;
        ht->nodes[p2].parent = i;
        ht->nodes[i].left_child = p1;
        ht->nodes[i].right_child = p2;
    }
}

int main() {
    int weights[] = {1, 2, 3, 4, 5};
    int n = sizeof(weights) / sizeof(weights[0]);
    HuffmanTree ht;
    build_huffman_tree(&ht, weights, n);
    return 0;
}

这是从下标1开始的代码

#include <stdio.h>
#include <stdlib.h>

#define MAX_NODES 1000

// 哈夫曼树的结构体
typedef struct {
    int weight; // 权值
    int parent; // 父节点下标
    int lchild; // 左子节点下标
    int rchild; // 右子节点下标
} HuffmanTree;

void select_min_two(HuffmanTree *ht, int n, int *p1, int *p2) {
    // 找到权值最小的两个节点
    int min1 = MAX_NODES, min2 = MAX_NODES; // min1和min2分别表示最小和次小的权值
    for (int i = 1; i <= n; i++) {
        if (ht[i].weight < min1 && ht[i].parent == 0) {
            min2 = min1;
            *p2 = *p1;
            min1 = ht[i].weight;
            *p1 = i;
        } else if (ht[i].weight < min2 && ht[i].parent == 0) {
            min2 = ht[i].weight;
            *p2 = i;
        }
    }
}

void build_huffman_tree(HuffmanTree *ht, int *weights, int n) {
    // 初始化哈夫曼树
    for (int i = 1; i <= n; i++) {
        ht[i].weight = weights[i];
        ht[i].parent = 0;
        ht[i].lchild = 0;
        ht[i].rchild = 0;
    }

    // 构造哈夫曼树
    int m = 2 * n - 1;
    for (int i = n + 1; i <= m; i++) {
        int p1 = 0, p2 = 0;
        select_min_two(ht, i - 1, &p1, &p2); // 找到权值最小的两个节点
        ht[p1].parent = i;
        ht[p2].parent = i;
        ht[i].lchild = p1;
        ht[i].rchild = p2;
        ht[i].weight = ht[p1].weight + ht[p2].weight;
    }
}

哈夫曼编码

哈夫曼编码是一种可变长度编码的算法,用于将字符集中的字符映射成不同长度的二进制码。它是一种无损压缩算法,可以有效地压缩文本、音频、图像等数据。

哈夫曼编码的基本思想是,为频繁出现的字符分配较短的编码,为不频繁出现的字符分配较长的编码。这样做可以使得编码后的数据具有更高的压缩率。具体来说,哈夫曼编码的过程如下:

  1. 统计字符集中每个字符出现的频率
  2. 将每个字符及其频率构成一个叶子结点,构建一颗哈夫曼树。哈夫曼树是一种二叉树,每个节点都有两个子节点,除了叶子结点以外,每个节点的权值为其两个子节点的权值之和。
  3. 从根节点开始遍历哈夫曼树,将左子树编码为0,右子树编码为1,将每个叶子结点的编码记录下来。
  4. 将每个字符用其对应的编码替换,得到编码后的数据。

由于哈夫曼编码采用可变长度编码,使得频繁出现的字符用较短的编码表示,不频繁出现的字符用较长的编码表示,因此可以达到比固定长度编码更高的压缩率。

**例:**要传输的字符集 D = {C,A,S,T,; }(注意这个分号也是,并没有多打),每个字符出现的频率 W = {2,4,2,3,3}。

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

哈夫曼编码的两个问题

  1. 为什么哈夫曼编码能够保证是前缀编码?
    • 因为没有一片叶子是另一片叶子的祖先,所以每个叶子结点的编码就不可能是其他叶子结点编码的前缀。
  2. 为什么哈夫曼编码能够保证字符编码总长最短?
    • 因为哈夫曼树就是树的带权路径长度最短的树,所以整个字符编码的总长最短。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 哈夫曼树结构体定义
typedef struct Node {
    char character; // 字符
    int frequency; // 频率
    struct Node *left, *right;
} Node;

// 函数声明
Node *createNode(char character, int frequency);
Node **createPriorityQueue(int size);
void insertPriorityQueue(Node **queue, Node *node, int size);
Node *removeSmallestPriorityQueue(Node **queue, int size);
Node *buildHuffmanTree(char *characters, int *frequencies, int size);
void printHuffmanCodes(Node *root, int *arr, int top);

int main() {
    // 示例字符及频率
    char characters[] = {'a', 'b', 'c', 'd', 'e', 'f'};
    int frequencies[] = {5, 9, 12, 13, 16, 45};
    int size = sizeof(characters) / sizeof(characters[0]);

    // 创建哈夫曼树
    Node *root = buildHuffmanTree(characters, frequencies, size);

    // 输出哈夫曼编码
    int arr[100], top = 0;
    printf("Huffman Codes:\n");
    printHuffmanCodes(root, arr, top);

    return 0;
}
 
// 创建结点函数
Node *createNode(char character, int frequency) {
    Node *node = (Node *)malloc(sizeof(Node));
    node->character = character;
    node->frequency = frequency;
    node->left = node->right = NULL;
    return node;
}

// 创建优先级队列(按频率升序)
Node **createPriorityQueue(int size) {
    Node **queue = (Node **)malloc(size * sizeof(Node *));
    for (int i = 0; i < size; i++) {
        queue[i] = NULL;
    }
    return queue;
}

// 插入优先级队列
void insertPriorityQueue(Node **queue, Node *node, int size) {
    int i;
    for (i = 0; i < size; i++) {
        if (queue[i] == NULL || queue[i]->frequency > node->frequency) {
            break;
        }
    }

    for (int j = size - 1; j > i; j--) {
        queue[j] = queue[j - 1];
    }
    queue[i] = node;
}

// 移除优先级队列中最小元素
Node *removeSmallestPriorityQueue(Node **queue, int size) {
    Node *smallest = queue[0];
    for (int i = 0; i < size - 1; i++) {
        queue[i] = queue[i + 1];
    }
    queue[size - 1] = NULL;
    return smallest;
}

// 构建哈夫曼树
Node *buildHuffmanTree(char *characters, int *frequencies, int size) {
    // 创建优先级队列
    Node **queue = createPriorityQueue(size);
    int i;
    for (i = 0; i < size; ++i) {
        queue[i] = createNode(characters[i], frequencies[i]);
    }

    // 构建哈夫曼树
    while (i > 1) {
        // 从优先级队列中移除两个最小频率的节点
        Node *left = removeSmallestPriorityQueue(queue, i);
        Node *right = removeSmallestPriorityQueue(queue, --i);

        // 创建一个新节点,左右子节点分别是两个最小频率的节点
        Node *internalNode = createNode('\0', left->frequency + right->frequency);
        internalNode->left = left;
        internalNode->right = right;

        // 将新节点插入优先级队列
        insertPriorityQueue(queue, internalNode, i);
        --i;
    }

    // 返回哈夫曼树的根节点
    Node *root = queue[0];
    free(queue);
    return root;
}

// 输出哈夫曼编码
void printHuffmanCodes(Node *root, int *arr, int top) {
    if (root->left) {
        arr[top] = 0;
        printHuffmanCodes(root->left, arr, top + 1);
    }

    if (root->right) {
        arr[top] = 1;
        printHuffmanCodes(root->right, arr, top + 1);
    }

    // 到达叶子节点,打印编码
    if (!root->left && !root->right) {
        printf("%c: ", root->character);
        for (int i = 0; i < top; ++i) {
            printf("%d", arr[i]);
        }
        printf("\n");
    }
}

文件的编码和解码

等长编码

  • 如果使用的是之前的等长编码对这段明文进行编译的话,就需要占用3024bit的空间

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

哈夫曼编码

  • 而用哈夫曼编码则能够节省将近一半的空间

哈夫曼树编码,数据结构C语言,算法,数据结构,图论

  • 能通过编码将字符转换成一堆二进制码,同样的也能通过解码将这一堆二进制码转换成人能看得明白的文字

编码过程:

  1. 频率统计:读取文件内容,统计每个字符出现的频率。例如,文件内容为"ABRACADABRA",则字符频率统计结果为:A-5,B-2,C-1,D-1,R-2。
  2. 创建哈夫曼树节点:为每个字符创建一个哈夫曼树节点,并将它们按照频率从小到大放入优先队列(最小堆)中。如:C(1),D(1),B(2),R(2),A(5)。
  3. 构建哈夫曼树
    • 取出C(1)和D(1),合并为新节点CD(2),将CD(2)插入队列:B(2),R(2),CD(2),A(5)。
    • 取出B(2)和R(2),合并为新节点BR(4),将BR(4)插入队列:CD(2),A(5),BR(4)。
    • 取出CD(2)和A(5),合并为新节点CDA(7),将CDA(7)插入队列:BR(4),CDA(7)。
    • 取出BR(4)和CDA(7),合并为新节点BRCDA(11),将BRCDA(11)插入队列。
    • 此时队列中只有一个节点BRCDA(11),这就是哈夫曼树的根节点。
  4. 生成哈夫曼编码表:遍历哈夫曼树,得到每个字符的编码:
    • A: 0
    • B: 110
    • C: 1010
    • D: 1011
    • R: 111
  5. 编码文件:根据哈夫曼编码表,将原文件内容替换为相应的二进制编码:
    • ABRACADABRA -> 011011101010110010100110110

解码过程:

  1. 读取编码文件:将编码后的文件(011011101010110010100110110)加载到内存中。
  2. 解码
    • 从哈夫曼树的根节点开始遍历,根据二进制内容的每个位来决定向左(0)或向右(1)走。
    • 例如,二进制内容的第一位是0,从根节点向左走,到达A节点,将A写入解码后的文件。
    • 继续解析,接下来的二进制位是1,从根节点向右走,接着是1,继续向右走,最后是0,向左走,到达B节点,将B写入解码后的文件。
    • 重复这个过程,直到所有二进制内容都被解析完。最终得到的解码后的文件内容为"ABRACADABRA",与原始文件内容相同。

通过这个解码过程,我们成功地还原了原始文件的内容。这就是哈夫曼编码的无损解压缩过程。文章来源地址https://www.toymoban.com/news/detail-744742.html

到了这里,关于15哈夫曼树/哈夫曼编码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】实验十:哈夫曼编码

    实验十 哈夫曼编码 一、实验目的与要求 1 )掌握树、森林与二叉树的转换; 2 )掌握哈夫曼树和哈夫曼编码算法的实现; 二、 实验内容 1.  请编程实现如图所示的树转化为二叉树。 2.  编程实现一个哈夫曼编码系统,系统功能包括: (1)  字符信息统计:读取待编码的源文

    2024年02月15日
    浏览(48)
  • (数据结构)哈夫曼编码实现(C语言)

    哈夫曼的编码:从一堆数组当中取出来最小的两个值,按照左下右大的进行绘制,将两个权值之和,放入队列当中,然后再进行取出两个小的,以此类推,直到全部结束,在根据图根节点,到叶子节点,每一个分支来得出编码,向左0,向右1,即可得到一个结果。

    2024年02月16日
    浏览(54)
  • 【数据结构--哈夫曼编码(C语言版)】

    哈夫曼树 介绍哈夫曼树前先介绍下面几个名词: 1. 结点的路径长度 l 从根结点到该结点的路径上分支的数目 ,如下图结点 a 的 l = 3 。 2. 树的路径长度 树中所有叶子结点的路径长度之和 ,如下图该树的路径长度为 2 + 3 + 3 + 2 + 2 。 3. 结点的权 w 给每一个结点赋予一个新的数

    2024年02月04日
    浏览(51)
  • 数据结构—基础知识(15):哈夫曼树

    哈夫曼(Huffman)树 又称最优树,是一类带权路径长度最短的树,在实际中有广泛的用途。哈夫曼树的定义,涉及路径、路径长度、权等概念,下面先给出这些概念的定义,然后再介绍哈夫曼树 路径 :从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。 路

    2024年02月19日
    浏览(49)
  • C语言---数据结构实验---哈夫曼树及哈夫曼编码的算法实现---图的基本操作

    本篇实验代码非本人写,代码源自外部,经调试解决了部分warning和error后在本地vs上可以正常运行,如有运行失败可换至vs 未来会重构实现该两个实验 内容要求: 1、初始化(Init):能够对输入的任意长度的字符串s进行统计,统计每个字符的频度,并建立哈夫曼树 2、建立编码

    2024年02月13日
    浏览(59)
  • 【数据结构与算法】哈夫曼编码(最优二叉树)实现

    哈夫曼编码 等长编码:占的位置一样 变长编码(不等长编码):经常使用的编码比较短,不常用的比较短 最优:总长度最短 最优的要求:占用空间尽可能短,不占用多余空间,且不能有二义性 这里给出哈夫曼二叉树的实现: HuffmanTree.h: 测试数据(主函数): 运行结果截图

    2024年02月16日
    浏览(47)
  • 【数据结构与算法】哈夫曼编码(最优二叉树实现

    哈夫曼编码 等长编码:占的位置一样 变长编码(不等长编码):经常使用的编码比较短,不常用的比较短 最优:总长度最短 最优的要求:占用空间尽可能短,不占用多余空间,且不能有二义性 这里给出哈夫曼二叉树的实现: HuffmanTree.h: 测试数据(主函数): 运行结果截图

    2024年02月16日
    浏览(45)
  • 数据结构 实验17:Huffman树和Huffman编码——学习理解哈夫曼树

    目录 前言 实验要求 算法描述 个人想法 代码实现和思路、知识点讲解 知识点讲解 文件传输 Huffman树的存储 Huffman的构造  Huffman编码 编码和译码 代码实现 文件写入和输出 Huffman树初始化 构造Huffman树 求带权路径长度 Huffman编码 Huffman译码 结束 代码测试 测试结果 利用Huffman编

    2024年02月03日
    浏览(61)
  • 15哈夫曼树/哈夫曼编码

    哈夫曼树又称为 最优树 ,作用是找到一种效率最高的判断树。 路径 :从树中一个结点到另一个结点之间的 分支 构成这两个结点之间的路径。 结点的路径长度 :两结点间路径上的分支树 如图 a :从 A - D 的路径长度就是是 2。从 A 到 B C D E F G F I 的路径长度分别为 1 1 2 2 3

    2024年02月05日
    浏览(44)
  • 数据结构【哈夫曼树】

    最优二叉树也称哈夫曼 (Huffman) 树 ,是指对于一组带有确定权值的叶子结点,构造的具有最小带权路径长度的二叉树。权值是指一个与特定结点相关的数值。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。 涉及到的几个概念: 路径: 从树中一个结点到另一个结

    2024年02月14日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包