641-哈夫曼树和哈夫曼编码

哈夫曼树理论

最佳判定树,最优二叉树
在这里插入图片描述
W1:第一个叶子节点的权值
L1:第一个叶子节点到根路径的长度

哈夫曼树的构建过程

举个例子:
比如说,我们有这些带权值的节点:
权值:出现的频率或者是出现的次数,权值越大,说明这个节点代表的情况是出现的频率越高,出现的次数越多。
在这里插入图片描述
接下来,我们要构建二叉树了,形成最佳判定树,使所有叶子节点的带权路径是最短的:
步骤1:从权值集合中,拿出最小的两个权值出来
我们拿出3和5
我们会求出拿出的这2个权值的和
3+5=8
让3作为8的左孩子,5作为8的右孩子
在这里插入图片描述
然后把8为根节点的这个小二叉树入到权值集合中
在这里插入图片描述
在这里插入图片描述
步骤2:从集合中再选取权值最小的2个元素,重复步骤1的操作,直到集合中只剩下1个节点
我们取出8为根节点的这个小二叉树和10出来
在这里插入图片描述
8+10=18
在这里插入图片描述
然后把这个18为根节点的小二叉树归到集合中
在这里插入图片描述
然后在集合中再选出2个最小的权值的节点
我们取出18为根节点的小二叉树和15
其实我们构建这个集合的时候用小根堆就可以了,每次取出的就是集合最小的节点
18+15=33
在这里插入图片描述
然后把33为根节点的二叉树放回集合,此时集合中就剩下2个元素了。
33+19=52
在这里插入图片描述
最终生成好的这棵树就是哈夫曼树(最优二叉树)
因为这是带权值的叶子节点:3,,5,10,,15,,19
它们的带全权路径的长度的和是最短的
在这里插入图片描述

我们再举个例子:如果上面集合中的19是17的话,
我们构建到这里的时候:
在这里插入图片描述
此时集合中权值最小的两个节点是15和17
我们取出15和17,15+17=32
构建一棵二叉树,然后返回集合中
在这里插入图片描述
此时集合中就剩下18和32这两个节点了
18+32=50
在这里插入图片描述
最终构建好的这棵树叫做哈夫曼树(最优二叉树)
叶子节点:3,5,10,15,17
这几个叶子节点的带权路径和是最短的。
在这里插入图片描述
它的这个思想就是:总是在形成二叉树的时候,先从集合中拿出权值最小的两个节点来构建二叉树,后面才拿出权值最大的节点来构建二叉树,也就是说,权值最小的出现频率,出现次数是最小的,离根节点是最远的,因为它是权值最小的先构建的,先构建的离根节点是最远的,权值越大的越后构建,自然里根节点就越近了。
当然,权值高的出现频率高次数多的节点,离根节点越近,判断的就快,判断的次数就少,离根节点远的,出现的频率低,出现的次数少,相对来说判断的花费的时间就长,操作就慢。
这样构建出来的一棵树,在进行条件判断的时候,当然是最佳判定的树,最优的二叉树。
基于这棵哈夫曼树,我们主要是用它来构建哈夫曼编码的

哈夫曼编码

假如说,我们有下面这一个原始的字符串序列:
想对它进行哈夫曼编码,进行数据压缩存储,甚至通过网络发送存储我们的某一个设备中。
在这里插入图片描述
我们要对这个原始的字符串进行哈夫曼编码,我们首先要构建一棵最佳判定树!
我们在字符进行编码的时候,最好是把出现频率高的编码短一点,把出现频率低的编码长一点,因为出现频率高的意味着在原始序列中这个字符出现的次数多,所以编码短一点,有助于最后生成的压缩文件,总体上来说文件大小是比较小的,出现频率低的字符的编码长一点,这个也无所谓了,因为字符出现的次数本来就偏低,不会大量占用文本的空间位置。
以这样构建出来的和哈夫曼树,最后想得到的这个原始字符串序列的哈夫曼编码,是最优的。
构建一棵哈夫曼树,我们首先需要知道:
通过遍历,统计每个字符出现的次数:权值
在这里插入图片描述
知道每个字符的权值之后,我们就要构建哈夫曼树了:
在这里插入图片描述
这个就是权值的集合了。
构建哈夫曼树我们刚才已经详细的叙述了:
因为这个集合有很多个一样的最小权值(2),我们假设取出E和G先来构建二叉树。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
权值最大的(A:3)离根节点最近。
我们计算一下带权路径长度和

2*2+3*2+2*3+2*3+2*3+2*3=带权路径最短的!!!

3、获取字符的哈夫曼编码
对于这棵哈夫曼树来说,往左走,标识0,往右走,标识1
在这里插入图片描述
然后从根节点开始走到这个字符叶子节点上的路径的所有标识依次连起来,就是该字符的哈夫曼编码了。
在这里插入图片描述

哈夫曼编码:立刻可解码性

在这里插入图片描述
也就是说,如果我读到某一个编码串,我就能立即知道这是哪个字符的编码!----立刻可解码性

哈夫曼编码的另一个特点:变长编码
在这里插入图片描述
但是,哈夫曼编码是变长编码:
在这里插入图片描述

数据压缩

因为哈夫曼编码是用来数据压缩的,数据压缩以后,文件的长度越小越好,编码越短的字符说明离根节点越近,离根节点越近,说明这个字符出现的权值越大(频率高),所以编码越短一点,使得压缩的文件的大小是最短的
在这里插入图片描述
编码以后都是0或者1组成的,0-1码,不可能用一个字符来存一个0或者1的,如果记录在文件或者通过网络发送,我们直接当做二进制进行存储。
如果我们存储的是整数,4个字节,8个位1个字节,32位
总共34个位,34/32,我们用5个字节存储这个二进制序列就够了。
在这里插入图片描述
而原始的字符串序列,一共13个字符,一共字符1个字节,总共就是14个字节(加上\0)
在这里插入图片描述
在这里插入图片描述
我们来遍历原始的序列。
首先我们有一颗哈夫曼树,一个指针指向根节点,一边遍历哈夫曼编码,一边遍历哈夫曼树。
我们从根节点和哈夫曼编码串的首元素,一起遍历
第一个是0,0就是往左走,然后遍历到1,往右走,就发现这个节点的左右子树都为空了,就是到叶子节点了,也就是说,根据立刻可解码性,不用等待01后面还有什么,01除了字符A,不会再是其他字符编码的前缀了。

在这里插入图片描述
然后把指针重新指向根节点,然后从哈夫曼编码串的01后的那个数字,继续一起遍历。以此类推。
在这里插入图片描述
哈夫曼树结合哈夫曼编码串,可以把原始的数据序列解码出来。
权值也可以存的是百分比哦。

哈夫曼编码和解码C++代码实现

#include <iostream>
#include <unordered_map>
#include <string>
#include <queue>
#include <functional>
using namespace std;

using uint = unsigned int;

//实现哈夫曼树
class HuffmanTree
{
public:
	HuffmanTree()
		: minHeap_([](Node* n1, Node* n2)->bool
			{return n1->weight_ > n2->weight_; })
		, root_(nullptr)
	{}

	//创建哈夫曼树
	void create(string str)
	{
		//先统计字符的权值
		unordered_map<char, uint> dataMap;
		for (char ch : str)
		{
			dataMap[ch]++;
		}

		//生成节点,放入小根堆中
		for (auto& pair : dataMap)
		{
			minHeap_.push(new Node(pair.first, pair.second));
		}

		while (minHeap_.size() > 1)
		{
			//获取两个权值最小的
			Node* n1 = minHeap_.top();
			minHeap_.pop();

			Node* n2 = minHeap_.top();
			minHeap_.pop();

			//生成父节点
			Node* node = new Node('\0', n1->weight_ + n2->weight_);
			node->left_ = n1;
			node->right_ = n2;

			minHeap_.push(node);
		}

		root_ = minHeap_.top();
		minHeap_.pop();
	}

	//encode
	string encode(string str)
	{
		getHuffmanCode();

		string encode_str;
		for (char ch : str)
		{
			encode_str.append(codeMap_[ch]);
		}
		return encode_str;
	}

	//decode
	string decode(string encode)
	{
		string decode_str;
		Node* cur = root_;

		for (char ch : encode)
		{
			if (ch == '0')
			{
				cur = cur->left_;
			}
			else
			{
				cur = cur->right_;
			}

			if (cur->left_ == nullptr && cur->right_ == nullptr)
			{
				decode_str.push_back(cur->data_);
				cur = root_;
			}
		}
		return decode_str;
	}
	
private:
	struct Node
	{
		Node(char data, uint weight)
			: data_(data)
			, weight_(weight)
			, left_(nullptr)
			, right_(nullptr)
		{}

		char data_;//字符数据
		uint weight_;//节点的权值
		Node* left_;//指向左孩子节点
		Node* right_;//指向右孩子节点
	};

private:
	//输出哈夫曼编码
	void getHuffmanCode()
	{
		string code;
		getHuffmanCode(root_, code);

		/*for (auto& pair : codeMap_)
		{
			cout << pair.first << " : " << pair.second << endl;
		}
		cout << endl;*/
	}

	void getHuffmanCode(Node* root, string code)
	{
		//VLR
		if (root->left_ == nullptr && root->right_ == nullptr)
		{
			codeMap_[root->data_] = code;
			return;
		}

		getHuffmanCode(root->left_, code + "0");
		getHuffmanCode(root->right_, code + "1");
	}

private:
	Node* root_;//指向根节点
	unordered_map<char, string> codeMap_;//存储字符对应的哈夫曼编码

	using MinHeap = priority_queue<Node*, vector<Node*>, function<bool(Node*, Node*)>>;
	MinHeap minHeap_;
};

int main()
{
	string str = "ABACDAEFDEGGAHGNNMBHJGUTUYHBBNBNBJHGSUYTUYTRFHGTHJ";

	HuffmanTree htree;
	htree.create(str);
	string encode = htree.encode(str);
	cout << "encode:" << encode << endl;

	cout << "decode:" << htree.decode(encode) << endl;
}

哈夫曼编码和解码java代码实现

package com.fixbug;

import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

/**
 * 哈夫曼树的实现
 */
class HuffmanTree{
    //统计字符的个数,即字符:权值 
    private Map<Character, Integer> charMap;
    //小根堆存储Entry节点,通过权值比较节点大小
    private PriorityQueue<Entry> queue;
    //指向哈夫曼树的根节点
    private Entry root;
    //存储字符以及最终的哈夫曼编码
    private Map<Character, String> codeMap;

    public HuffmanTree() {
        charMap = new HashMap<>();
        //生成优先级队列,自定义通过Entry节点的权值进行比较
        queue = new PriorityQueue<Entry>((a, b)->{
            return a.getCount().compareTo(b.getCount());//比较器 
        });
        codeMap = new HashMap<>();
    }

    /**
     * 生成哈夫曼树
     * @param str
     */
    public void createHuffmanTree(String str) {
        //1.统计字符串中字符出现的频率
        for(int i=0; i < str.length(); ++i){
            Integer cnt = charMap.get(str.charAt(i));//通过map集合获取这个字符出现的次数 
            if(cnt == null){//这个字符第一次出现 
                charMap.put(str.charAt(i), 1);
            } else {
                charMap.put(str.charAt(i), cnt + 1);
            }
        }

        //2.把带权值的节点都添加到优先级队列当中 构建小根堆,因为每次是取出当前的最小的权值的2个节点 
        charMap.forEach((ch, count)->{
            Entry entry = new Entry(ch, count);
            queue.offer(entry); 
        });

        //3.循环处理小根堆,每次拿两个权值最小的节点进行合并生成二叉树,不断重复
        while (queue.size() > 1){
            Entry entry1 = queue.poll();//出队 
            Entry entry2 = queue.poll();//从队列里取出2个节点,因为是优先级队列,小根堆,每次拿出来都是权值最小的 
            Entry entry = new Entry('\u0000', entry1.getCount()+entry2.getCount());//生成新的节点 
            entry.setLeft(entry1);//连接起来,构建二叉树 
            entry.setRight(entry2);
            queue.offer(entry);//入队 
        }

        //4.指向哈夫曼树的根节点
        this.root = queue.poll();
    }

    /**
     * 打印哈夫曼编码
     * @return
     */
    public void showCode() {
        String str = "";
        showCode(this.root, str);//从根节点开始 

        codeMap.forEach((ch, code)->{
            System.out.println(ch + " code: " + code);
        });
    }

    private void showCode(Entry root, String str) {
        if(root.getLeft() == null && root.getRight() == null){
            codeMap.put(root.getCh(), str);
            return;
        }

        showCode(root.getLeft(), str + "0");
        showCode(root.getRight(), str + "1");
    }

    /**
     * 根据生成的哈夫曼编码,把str原始序列的编码进行返回
     * @param str
     * @return
     */
    public String encode(String str) {
        StringBuilder sbuf = new StringBuilder();
        for(int i=0; i< str.length(); ++i){
            sbuf.append(codeMap.get(str.charAt(i)));
        }
        return sbuf.toString();//转成字符串 
    }

    /**
     * encode是哈夫曼编码,根据上面的哈夫曼树,进行数据解码,返回
     * @param encode
     * @return
     */
    public String decode(String encode) {
        StringBuilder sbuilder = new StringBuilder();
        Entry cur = this.root;//指向根节点 
        for(int i=0; i < encode.length(); ++i){
            if(encode.charAt(i) == '0'){
                cur = cur.getLeft();
            } else {
                cur = cur.getRight();
            }

            //访问到根节点了
            if(cur.getLeft() == null && cur.getRight() == null){
                sbuilder.append(cur.getCh());
                cur = this.root;
            }
        }
        return sbuilder.toString();
    }

    /**
     * 哈夫曼树节点的类型
     */
    static class Entry{
        Character ch;//字符 
        Integer count;//权值
        Entry left;//指向左孩子 
        Entry right;//指向右孩子 

        public Entry(char ch, int count) {//构造函数 
            this.ch = ch;
            this.count = count;
            this.left = this.right = null;
        }

        public Character getCh() {
            return ch;
        }

        public void setCh(Character ch) {
            this.ch = ch;
        }

        public Integer getCount() {
            return count;
        }

        public void setCount(Integer count) {
            this.count = count;
        }

        public Entry getLeft() {
            return left;
        }

        public void setLeft(Entry left) {
            this.left = left;
        }

        public Entry getRight() {
            return right;
        }

        public void setRight(Entry right) {
            this.right = right;
        }
    }
}

/**
 * Hello world!
 */
public class //哈夫曼编码
{
    public static void main( String[] args ) throws Exception{
        String str = "soiyufhsjadfkndgiuyeruwighjhjsgyeugyfwhjkhggyuewhjdb";
        HuffmanTree ht = new HuffmanTree();//生成一棵哈夫曼树 
        ht.createHuffmanTree(str);//构建一棵哈夫曼树 
        ht.showCode();//打印哈夫曼编码 

        String encode = ht.encode(str);
        System.out.println(encode);

        //解码
        System.out.println(ht.decode(encode));
    }
}

在这里插入图片描述

文件压缩和解压缩代码实现

哈夫曼树的节点类型:Entry.java

package com.fixbug.compress;

/**
 * 哈夫曼树的节点类型
 */
public class Entry {
    private Character ch;// 字符数据
    private Integer count;//字符的频率
    private Entry left;
    private Entry right;

    public Entry(Character ch, Integer count) {
        this.ch = ch;
        this.count = count;
    }

    public Character getCh() {
        return ch;
    }

    public void setCh(Character ch) {
        this.ch = ch;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public Entry getLeft() {
        return left;
    }

    public void setLeft(Entry left) {
        this.left = left;
    }

    public Entry getRight() {
        return right;
    }

    public void setRight(Entry right) {
        this.right = right;
    }
}

我们要把字符串哈夫曼编码转成字节,然后把字节还原成哈夫曼编码
文件压缩和解压缩的工具类:FormatUtil.java

package com.fixbug.compress;

/**
 * 文件压缩和解压缩的工具类 'A'  "11000101" <=> byte
 */
public class FormatUtil {
    /**
     * 把字节数据data转成二进制编码串  11000101 1100010  1100010 110001 11000
     * @param data
     * @return
     */
    public static String byteToBinaryString(byte data){
        StringBuilder sbuilder = new StringBuilder();
        for(int i=0; i < 8; ++i){
            byte bit = (byte)(data >>> i & 1);
            sbuilder.append(bit);//
        }
        sbuilder.reverse();//反转 
        return sbuilder.toString();
    }

    /**
     * 把8个位的字符串编码转成字节 "00010100"
     * @param code
     * @return
     */
    public static byte binaryStringToByte(String code){
        byte data = 0;
        for(int i=0; i < code.length(); ++i){
            if(code.charAt(i) == '1'){
                data += Math.pow(2, code.length()-i-1);
            }
        }
        return data;
    }

    public static void main(String[] args) {
        System.out.println(byteToBinaryString((byte)20));
        System.out.println(binaryStringToByte("00010100"));
    }
}

编码:

package com.fixbug.compress;

import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

/**
 * 文件压缩类  通过构建文件内容的哈夫曼编码进行文件数据压缩
 */
public class FileCompress {
    // 存储文件内容的频率
    private Map<Character, Integer> countMap = new HashMap<>();
    // 小根堆
    private PriorityQueue<Entry> queue = new PriorityQueue<Entry>((a, b)->{return a.getCount().compareTo(b.getCount());});
    // 指向哈夫曼树的根
    private Entry root;
    // 存储字符的哈夫曼编码
    private Map<Character, String> codeMap = new HashMap<>();

    /**
     * 压缩文件并输出压缩后的文件
     * @param file
     */
    public void compress(File file) throws Exception{
        // 1. 读取文件的每一个字节数,统计它们的频率
        FileInputStream in = new FileInputStream(file);
        int data = -1;
        while(-1 != (data = in.read())){
            Integer count = countMap.get((char)data);
            if(count == null){
                countMap.put((char)data, 1);
            } else {
                countMap.put((char)data, count+1);
            }
        }
        in.close();

        // 2.生成权值节点,放入小根堆,构建哈夫曼树
        countMap.forEach((key, value)->{
            Entry entry = new Entry(key, value);
            queue.offer(entry);
        });

        while (queue.size() > 1){
            Entry entry1 = queue.poll();
            Entry entry2 = queue.poll();
            Entry entry = new Entry('\u0000', entry1.getCount() + entry2.getCount());
            entry.setLeft(entry1);
            entry.setRight(entry2);
            queue.offer(entry);
        }
        this.root = queue.poll();

        // 3.获取一下叶子节点(每一个字符)的哈夫曼编码
        String huffmanCode = "";
        getCode(this.root, huffmanCode);

        // 4.重新读取文件,获取相应字符的哈夫曼编码,进行压缩存储
        StringBuilder sbuilder = new StringBuilder();
        FileInputStream filein = new FileInputStream(file);
        DataOutputStream fileout = new DataOutputStream(new FileOutputStream(file.getName() + ".compress"));

        // 写文件头,把文件字符以及频率进行存储,用于解压缩
        fileout.write(countMap.keySet().size()); // 先写文件,记录字符的个数
        countMap.forEach((ch, count)->{
            try {
                fileout.write(ch); // 写字符内容
                fileout.writeInt(count); // 写字符频率
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        // 写文件具体的编码内容
        while(-1 != (data = filein.read())){
            String code = codeMap.get((char)data);
            sbuilder.append(code);
            while(sbuilder.length() >= 8){
                code = sbuilder.substring(0, 8);
                byte writeData = FormatUtil.binaryStringToByte(code);
                fileout.write(writeData);
                sbuilder.delete(0, 8);
            }
        }

        // sbuilderk可能还有剩余的编码 11010100      1100 => 1100 0000  4
        if(sbuilder.length() > 0){
            byte writeData = FormatUtil.binaryStringToByte(sbuilder.toString()); // 1100 0000
            writeData <<= (8 - sbuilder.length());
            fileout.write(writeData);
            fileout.write(sbuilder.length());
        }

        filein.close();
        fileout.close();
    }

    /**
     * 获取叶子节点的哈夫曼编码
     * @param root
     * @param huffmanCode
     */
    private void getCode(Entry root, String huffmanCode) {
        if(root.getLeft() == null && root.getRight() == null){
            codeMap.put(root.getCh(), huffmanCode);
            return;
        }

        getCode(root.getLeft(), huffmanCode+"0");
        getCode(root.getRight(), huffmanCode+"1");
    }

    public static void main(String[] args) throws Exception{
        FileCompress fc = new FileCompress();
        fc.compress(new File("D:\\代码\\Java Code\\20171107\\com\\sun\\corba\\se\\PortableActivationIDL\\_ActivatorStub.java"));
    }
}

解码:

package com.fixbug.compress;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

/**
 * 实现文件的解压缩操作
 */
public class FileDecompress {
    // 存储文件内容的频率
    private Map<Character, Integer> countMap = new HashMap<>();
    // 小根堆
    private PriorityQueue<Entry> queue = new PriorityQueue<Entry>((a, b)->{return a.getCount().compareTo(b.getCount());});
    // 指向哈夫曼树的根
    private Entry root;
    // 存储字符的哈夫曼编码
    private Map<Character, String> codeMap = new HashMap<>();

    /**
     * 文件的解压缩操作
     * @param file
     */
    public void decompress(File file) throws Exception{
        // 1.先读取文件头,获取字符以及频率存储到countMap中
        DataInputStream in = new DataInputStream(new FileInputStream(file));
        // 文件的第一个字节放的是字符的个数
        int size = in.read();
        // 开始读取字符以及频率
        for (int i = 0; i < size; i++) {
            Character ch = (char)in.read();
            Integer count = in.readInt();
            countMap.put(ch, count);
        }
        
        // 2.构建哈夫曼树
        // 2.生成权值节点,放入小根堆,构建哈夫曼树
        countMap.forEach((key, value)->{
            Entry entry = new Entry(key, value);
            queue.offer(entry);
        });

        while (queue.size() > 1){
            Entry entry1 = queue.poll();
            Entry entry2 = queue.poll();
            Entry entry = new Entry('\u0000', entry1.getCount() + entry2.getCount());
            entry.setLeft(entry1);
            entry.setRight(entry2);
            queue.offer(entry);
        }
        this.root = queue.poll();

        // 3.获取一下叶子节点(每一个字符)的哈夫曼编码
        String huffmanCode = "";
        getCode(this.root, huffmanCode);

        // 继续读取文件,然后解压缩内容,还原文件
        String fileName = file.getName();
        int index = fileName.lastIndexOf('.');
        fileName = fileName.substring(0, index);
        FileOutputStream out = new FileOutputStream(fileName);

        Entry cur = this.root;
        int data = -1;
        int length = 0;
        // 压缩文件的最后两个字节需要单独处理一下, 1100 0000  4
        while(-1 != (data = in.read())){
            String code = FormatUtil.byteToBinaryString((byte)data);
            length = code.length();
            if(in.available() == 1){ // 文件就剩最后一个字节了
                length = in.read(); // 读取最后一个字节,表示最后一个编码有效的哈夫曼编码的位数
            }
            for(int i=0; i < length; ++i){
                if(code.charAt(i) == '0'){
                    cur = cur.getLeft();
                } else {
                    cur = cur.getRight();
                }

                if(cur.getLeft() == null && cur.getRight() == null){
                    out.write(cur.getCh());
                    cur = this.root;
                }
            }
        }

        out.close();
        in.close();
    }

    /**
     * 获取叶子节点的哈夫曼编码
     * @param root
     * @param huffmanCode
     */
    private void getCode(Entry root, String huffmanCode) {
        if(root.getLeft() == null && root.getRight() == null){
            codeMap.put(root.getCh(), huffmanCode);
            return;
        }

        getCode(root.getLeft(), huffmanCode+"0");
        getCode(root.getRight(), huffmanCode+"1");
    }

    public static void main(String[] args) throws Exception{
        FileDecompress fd = new FileDecompress();
        fd.decompress(new File("_ActivatorStub.java.compress"));
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林林林ZEYU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值