【堆与优先队列】“数据金字塔”与“我是VIP”

一、概述

1. 案例介绍

Heap)和 优先队列Priority Queue)是紧密相关的数据结构,都用于高效地管理元素并根据优先级(通常是最大值或最小值)进行访问。优先队列通常使用堆来实现。 案例会介绍堆和优先队列的基本概念、常用方法,最终使用堆和优先队列完成一个简单的系统开发。 本案例相关实验将在华为云开发者空间云主机进行,开发者空间云主机为开发者提供了高效稳定的云资源,确保用户的数据安全。云主机当前已适配完整的C/C++开发环境,支持VS Code等多种IDE工具安装调测。

2. 适用对象

  • 个人开发者
  • 高校学生

3. 案例时间

本案例总时长预计60分钟。

4. 案例流程

说明:

  1. 开通开发者空间,搭建C/C++开发环境;
  2. 打开VS Code,编写代码运行程序。

资源总览

本案例预计花费0元。

资源名称规格单价(元)时长(分钟)
云主机鲲鹏通用计算增强型 kC2 | 4vCPUs | 8G | Ubuntu免费60
VS Code1.97.2免费60

二、配置实验环境

1. 开发者空间配置

面向广大开发者群体,华为开发者空间提供一个随时访问的“开发桌面云主机”、丰富的“预配置工具集合”和灵活使用的“场景化资源池”,开发者开箱即用,快速体验华为根技术和资源。

如果还没有领取开发者空间云主机,可以参考免费领取云主机文档领取。

领取云主机后可以直接进入华为开发者空间工作台界面,点击打开云主机 > 进入桌面连接云主机。

2. 配置实验环境

参考案例中心《基于开发者空间,定制C&C++开发环境云主机镜像》“2. 实验环境搭建”、“3. VS Code安装部署”章节完成开发环境、VS Code及插件安装。

三、堆和优先队列的基本操作

1. 基本概念

Heap)是一种特殊的完全二叉树,可以简洁地使用数组表示和维护。堆适合于需要维护最值的场景,它满足父节点的值总是不大于或不小于其子节点的值。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

优先级队列PriorityQueue)是0个或多个元素的集合,每个元素都有一个优先权;对优先级队列执行的操作有查找、插入、删除。一般情况下,查找操作用来搜索优先权最大的元素,删除操作用来删除该元素。对于优先权相同的元素,可按先进先出次序处理或按任意优先权进行。

2. 堆的操作

1)堆的插入(Insert / Push / Offer):

  • 将新元素添加到数组末尾(堆的最后一个叶子节点位置)。
  • 上浮 (Percolate Up / Sift Up / Swim): 比较新节点与其父节点的值。 最大堆:如果新节点值 > 父节点值,则交换它们的位置。 最小堆:如果新节点值 < 父节点值,则交换它们的位置。
  • 重复步骤上浮,直到新节点找到合适的位置(满足堆序性),或者到达根节点。
  • 时间复杂度:O(log n) (树的高度)。

下面我们实现一个最小堆的插入操作,代码如下:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class MinHeap {
private:
    vector<int> heap;
    // 上浮调整(从当前节点向根节点调整)
    void siftUp(int index) {
        while (index > 0) {
            // 计算父节点索引
            int parentIndex = (index - 1) / 2; 
            if (heap[parentIndex] <= heap[index]) 
                break; // 如果父节点更小,满足最小堆性质
            // 交换父子节点
            swap(heap[parentIndex], heap[index]); 
            // 继续向上检查
            index = parentIndex;  
        }
    }

public:
    // 插入新元素
    void insert(int value) {
        // 1. 将新元素添加到堆末尾
        heap.push_back(value);  
        // 2. 从新元素位置进行上浮调整    
        siftUp(heap.size() - 1);    
    }

    // 打印堆内容(辅助函数)
    void printHeap() {
        for (int val : heap) 
            cout << val << " ";
        cout << endl;
    }
};

int main() {
    MinHeap heap;
    // 插入示例
    heap.insert(10);
    heap.insert(5);
    heap.insert(15);
    heap.insert(2);
    heap.insert(7);
    cout << "最小堆内容: ";
    heap.printHeap();  // 输出应为:2 5 15 10 7
    return 0;
}

点击编辑器左上角运行按钮直接运行。

输出内容截图如下:

2)堆排序 (Heap Sort): 堆排序是一种高效的比较类排序算法,时间复杂度为O(n log n),并且具有原地排序(空间复杂度O(1))的优点。下面我将实现一个完整的堆排序程序。 关键函数:heapify。

void heapify(vector<int>& arr, int n, int i) {
    int largest = i;        // 初始化最大元素为根节点
    int left = 2 * i + 1;   // 左子节点
    int right = 2 * i + 2;  // 右子节点

    // 比较左子节点
    if (left < n && arr[left] > arr[largest])
        largest = left;

    // 比较右子节点
    if (right < n && arr[right] > arr[largest])
        largest = right;

    // 如果最大值不是根节点
    if (largest != i) {
        swap(arr[i], arr[largest]);  // 交换
        heapify(arr, n, largest);    // 递归调整子树
    }
}

完整代码如下:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 堆调整函数(大顶堆)
void heapify(vector<int>& arr, int n, int i) {
    int largest = i;        // 初始化最大元素为根节点
    int left = 2 * i + 1;   // 左子节点
    int right = 2 * i + 2;  // 右子节点

    // 如果左子节点存在且大于根节点
    if (left < n && arr[left] > arr[largest])
        largest = left;

    // 如果右子节点存在且大于当前最大值
    if (right < n && arr[right] > arr[largest])
        largest = right;

    // 如果最大值不是根节点
    if (largest != i) {
        swap(arr[i], arr[largest]);  // 交换
        heapify(arr, n, largest);    // 递归调整子树
    }
}

// 堆排序主函数
void heapSort(vector<int>& arr) {
    int n = arr.size();

    // 1. 构建大顶堆(从最后一个非叶子节点开始)
    for (int i = n / 2 - 1; i >= 0; i--)
        heapify(arr, n, i);

    // 2. 逐个提取元素(交换堆顶和末尾元素,然后调整堆)
    for (int i = n - 1; i > 0; i--) {
        swap(arr[0], arr[i]);  // 将当前最大值移到数组末尾
        heapify(arr, i, 0);    // 调整剩余元素组成的堆
    }
}

// 打印数组
void printArray(const vector<int>& arr) {
    for (int num : arr)
        cout << num << " ";
    cout << endl;
}

int main() {
    vector<int> arr = {12, 11, 13, 5, 6, 7, 20, 1, 8};

    cout << "排序前数组: ";
    printArray(arr);

    heapSort(arr);

    cout << "堆排序后数组: ";
    printArray(arr);

    // 测试不同情况
    vector<int> empty = {};
    vector<int> single = {5};
    vector<int> sorted = {1, 2, 3, 4, 5};
    vector<int> reverse = {9, 8, 7, 6, 5};
    vector<int> duplicates = {3, 1, 4, 1, 5, 9, 2, 6, 5};

    cout << "\n测试空数组: ";
    heapSort(empty);
    printArray(empty);

    cout << "测试单元素数组: ";
    heapSort(single);
    printArray(single);

    cout << "测试已排序数组: ";
    heapSort(sorted);
    printArray(sorted);

    cout << "测试逆序数组: ";
    heapSort(reverse);
    printArray(reverse);

    cout << "测试重复元素数组: ";
    heapSort(duplicates);
    printArray(duplicates);

    return 0;
}

点击编辑器左上角运行按钮直接运行。

输出内容截图如下:

3)删除堆顶元素 (DeleteMax / DeleteMin / Poll):

  • 移除根节点元素(即数组的第一个元素)。
  • 将数组的最后一个元素(最后一个叶子节点)移动到根节点位置。
  • 下沉 (Percolate Down / Sift Down / Heapify / Sink): 比较新的根节点与其较大的(最大堆)或较小的(最小堆)子节点的值。
  • 重复下沉,直到该节点找到合适的位置(满足堆序性),或者到达叶子节点。
  • 时间复杂度:O(log n) (树的高度)。

删除代码样例如下,代码中添加了详细的输出信息,帮助理解求解过程:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class MinHeap {
private:
    vector<int> heap;

    // 获取父节点索引
    int parent(int i) { return (i - 1) / 2; }

    // 获取左子节点索引
    int leftChild(int i) { return 2 * i + 1; }

    // 获取右子节点索引
    int rightChild(int i) { return 2 * i + 2; }

    // 从指定位置开始向下调整堆
    void heapifyDown(int i) {
        int smallest = i;          // 初始化最小值位置
        int left = leftChild(i);    // 左子节点索引
        int right = rightChild(i);  // 右子节点索引
        int size = heap.size();

        // 与左子节点比较
        if (left < size && heap[left] < heap[smallest])
            smallest = left;

        // 与右子节点比较
        if (right < size && heap[right] < heap[smallest])
            smallest = right;

        // 如果最小值不是当前节点,则交换并递归调整
        if (smallest != i) {
            swap(heap[i], heap[smallest]);
            heapifyDown(smallest);  // 递归调整子树
        }
    }

public:
    // 删除堆顶元素
    void deleteTop() {
        if (heap.empty()) {
            cerr << "Heap is empty!" << endl;
            return;
        }

        // 将最后一个元素移到堆顶
        heap[0] = heap.back();
        heap.pop_back();

        // 从根节点开始向下调整
        heapifyDown(0);
    }

    // 插入元素
    void insert(int value) {
        heap.push_back(value);  // 添加到末尾
        int i = heap.size() - 1;

        // 向上调整
        while (i != 0 && heap[i] < heap[parent(i)]) {
            swap(heap[i], heap[parent(i)]);
            i = parent(i);
        }
    }

    // 获取堆顶元素
    int getTop() {
        if (heap.empty()) {
            cerr << "Heap is empty!" << endl;
            return -1; // 返回错误值
        }
        return heap[0];
    }

    // 打印堆
    void printHeap() {
        for (int val : heap)
            cout << val << " ";
        cout << endl;
    }
};

int main() {
    MinHeap heap;

    // 构建堆 [5, 3, 8, 1, 2]
    heap.insert(5);
    heap.insert(3);
    heap.insert(8);
    heap.insert(1);
    heap.insert(2);

    cout << "初始堆: ";
    heap.printHeap();  // 输出: 1 2 8 5 3(实际结构可能不同)

    // 删除堆顶
    heap.deleteTop();
    cout << "删除堆顶后: ";
    heap.printHeap();  // 输出: 2 3 8 5(堆结构)

    // 再删除一次
    heap.deleteTop();
    cout << "再删除堆顶后: ";
    heap.printHeap();  // 输出: 3 5 8(堆结构)

    return 0;
}

输出内容截图如下:

3. 优先队列的操作

1)优先队列的入队和出队(Enqueue / Push / Offer & Dequeue / Pop / Poll): 我们以push做入队操作,以pop做出队操作,分别以最大堆和最小堆实现优先队列的入队和出队。代码如下:

#include <iostream>
#include <queue>
#include <vector>
#include <functional> // 用于greater<int>
using namespace std;

int main() {
    cout << "========== C++优先级队列操作演示 ==========\n" << endl;

    // 案例1: 最大堆(默认)的入队和出队操作
    cout << "案例1: 最大堆操作" << endl;
    priority_queue<int> maxHeap;

    // 入队操作
    cout << "入队操作: 添加元素 30, 10, 50, 20, 40" << endl;
    maxHeap.push(30);
    maxHeap.push(10);
    maxHeap.push(50);
    maxHeap.push(20);
    maxHeap.push(40);

    cout << "当前队列内容: ";
    while (!maxHeap.empty()) {
        cout << maxHeap.top() << " "; // 查看队首元素
        maxHeap.pop(); // 出队操作
    }
    cout << "\n(注意: 元素从大到小出队)\n" << endl;

    // 案例2: 最小堆的入队和出队操作
    cout << "案例2: 最小堆操作" << endl;
    priority_queue<int, vector<int>, greater<int>> minHeap;

    // 入队操作
    cout << "入队操作: 添加元素 30, 10, 50, 20, 40" << endl;
    minHeap.push(30);
    minHeap.push(10);
    minHeap.push(50);
    minHeap.push(20);
    minHeap.push(40);

    cout << "当前队列内容: ";
    while (!minHeap.empty()) {
        cout << minHeap.top() << " "; // 查看队首元素
        minHeap.pop(); // 出队操作
    }
    cout << "\n(注意: 元素从小到大出队)\n" << endl;

    return 0;
}

输出内容截图:

2)优先队列的判空和获取大小(empty() & size()): 我们分别以最大堆和最小堆实现判空操作获取大小。代码如下:

#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;

int main() {
    cout << "====优先级队列判空与大小操作 ====\n" << endl;

    //判空操作演示
    cout << "判空操作" << endl;

    // 创建一个空的最大堆
    priority_queue<int> maxHeap;

    // 检查队列是否为空
    cout << "初始队列是否为空? " << (maxHeap.empty() ? "是" : "否") << endl;

    // 添加一些元素
    cout << "\n添加三个元素: 50, 30, 40" << endl;
    maxHeap.push(50);
    maxHeap.push(30);
    maxHeap.push(40);

    // 再次检查队列是否为空
    cout << "添加元素后队列是否为空? " << (maxHeap.empty() ? "是" : "否") << endl;

    // 清空队列
    cout << "\n清空队列..." << endl;
    while (!maxHeap.empty()) {
        maxHeap.pop();
    }

    // 最终检查队列是否为空
    cout << "清空后队列是否为空? " << (maxHeap.empty() ? "是" : "否") << endl;
    cout << "-----------------------------\n" << endl;

    // 获取队列大小操作
    cout << "获取队列大小" << endl;

    // 创建一个最小堆
    priority_queue<int, vector<int>, greater<int>> minHeap;

    // 初始大小
    cout << "初始队列大小: " << minHeap.size() << endl;

    // 添加元素并显示大小变化
    cout << "\n添加元素: 10" << endl;
    minHeap.push(10);
    cout << "当前队列大小: " << minHeap.size() << endl;

    cout << "添加元素: 20" << endl;
    minHeap.push(20);
    cout << "当前队列大小: " << minHeap.size() << endl;

    // 移除元素并显示大小变化
    cout << "\n移除一个元素..." << endl;
    minHeap.pop();
    cout << "当前队列大小: " << minHeap.size() << endl;

    cout << "再移除一个元素..." << endl;
    minHeap.pop();
    cout << "当前队列大小: " << minHeap.size() << endl;

    // 尝试在空队列上操作
    cout << "\n尝试在空队列上移除元素..." << endl;
    if (!minHeap.empty()) {
        minHeap.pop();
    } else {
        cout << "队列为空,无法执行pop操作" << endl;
    }
    cout << "----------------------------------------\n" << endl;
    return 0;
}

输出内容文本显示:

====优先级队列判空与大小操作 ====

判空操作
初始队列是否为空? 是

添加三个元素: 50, 30, 40
添加元素后队列是否为空? 否

清空队列...
清空后队列是否为空? 是
-----------------------------

获取队列大小
初始队列大小: 0

添加元素: 10
当前队列大小: 1
添加元素: 20
当前队列大小: 2

移除一个元素...
当前队列大小: 1
再移除一个元素...
当前队列大小: 0

尝试在空队列上移除元素...
队列为空,无法执行pop操作
----------------------------------------

四、医院急诊分诊系统

下面我将使用堆(手动实现)和优先队列(STL)分别实现一个医院急诊分诊系统。该系统根据病情的严重程度对患者进行优先级排序,确保最危急的患者最先得到治疗。

#include <iostream>
#include <vector>
#include <queue>
#include <ctime>
#include <iomanip>
#include <thread> 
#include <chrono> 
using namespace std;

//患者结构体
struct Patient {
    string name;
    // 病情严重程度:1-危重(最高) 2-紧急 3-中度 4-轻度(最低)
    int severity;   
    // 到达时间    
    time_t arrivalTime; 

    Patient(string n, int s) : name(n), severity(s) {
        // 记录到达时间
        arrivalTime = time(nullptr); 
    }

    // 重载比较运算符(用于STL优先队列)
    bool operator<(const Patient& other) const {
        // 优先比较严重程度(数值越小越严重)
        if (severity != other.severity) {
            // 注意:优先队列默认最大堆,所以反转比较
            return severity > other.severity; 
        }
        // 严重程度相同时,比较到达时间(先到先服务)
        return arrivalTime > other.arrivalTime;
    }
};

// 手动实现的堆(最小堆)
class MinHeap {
private:
    vector<Patient> heap;

    // 获取父节点索引
    int parent(int i) { return (i - 1) / 2; }

    // 获取左子节点索引
    int leftChild(int i) { return 2 * i + 1; }

    // 获取右子节点索引
    int rightChild(int i) { return 2 * i + 2; }

    // 堆化向上调整
    void heapifyUp(int i) {
        while (i > 0) {
            int p = parent(i);
            // 比较严重程度
            if (heap[p].severity > heap[i].severity || 
                (heap[p].severity == heap[i].severity && 
                 heap[p].arrivalTime > heap[i].arrivalTime)) {
                swap(heap[p], heap[i]);
                i = p;
            } else {
                break;
            }
        }
    }

    // 堆化向下调整
    void heapifyDown(int i) {
        int smallest = i;
        int left = leftChild(i);
        int right = rightChild(i);
        int size = heap.size();

        // 找出最紧急的患者(严重程度最小,相同则最早到达)
        if (left < size) {
            if (heap[left].severity < heap[smallest].severity ||
                (heap[left].severity == heap[smallest].severity && 
                 heap[left].arrivalTime < heap[smallest].arrivalTime)) {
                smallest = left;
            }
        }

        if (right < size) {
            if (heap[right].severity < heap[smallest].severity ||
                (heap[right].severity == heap[smallest].severity && 
                 heap[right].arrivalTime < heap[smallest].arrivalTime)) {
                smallest = right;
            }
        }

        // 如果需要交换,则递归调整
        if (smallest != i) {
            swap(heap[i], heap[smallest]);
            heapifyDown(smallest);
        }
    }

public:
    // 添加患者
    void addPatient(const Patient& p) {
        heap.push_back(p);
        heapifyUp(heap.size() - 1);
    }

    // 治疗下一个患者
    Patient treatNext() {
        if (heap.empty()) {
            throw runtime_error("没有患者等待治疗");
        }

        Patient next = heap[0];
        heap[0] = heap.back();
        heap.pop_back();

        if (!heap.empty()) {
            heapifyDown(0);
        }

        return next;
    }

    // 查看下一个患者
    Patient peekNext() const {
        if (heap.empty()) {
            throw runtime_error("没有患者等待治疗");
        }
        return heap[0];
    }

    // 检查是否为空
    bool isEmpty() const {
        return heap.empty();
    }

    // 获取等待患者数量
    int patientCount() const {
        return heap.size();
    }

    // 打印当前等待患者
    void printQueue() const {
        if (heap.empty()) {
            cout << "当前没有患者等待" << endl;
            return;
        }

        cout << "当前等待患者 (" << heap.size() << "人):" << endl;
        cout << "==========================================" << endl;
        cout << left << setw(15) << "姓名" << setw(10) << "严重程度" << "到达时间" << endl;
        cout << "------------------------------------------" << endl;

        for (const auto& p : heap) {
            cout << left << setw(15) << p.name;
            cout << setw(10);
            switch (p.severity) {
                case 1: cout << "危重"; break;
                case 2: cout << "紧急"; break;
                case 3: cout << "中度"; break;
                case 4: cout << "轻度"; break;
            }
            // 修正时间显示
            cout << ctime(&p.arrivalTime); 
        }
        cout << "==========================================" << endl;
    }
};

// 打印时间差(用于显示等待时间)
void printWaitTime(time_t arrival) {
    time_t now = time(nullptr);
    double diff = difftime(now, arrival);
    int minutes = static_cast<int>(diff) / 60;
    cout << "(等待时间: " << minutes << "分钟)";
}

int main() {
    cout << "========== 医院急诊分诊系统 ==========\n" << endl;
    cout << "病情严重程度定义:\n";
    cout << "1-危重(最高) 2-紧急 3-中度 4-轻度(最低)\n" << endl;

    // 使用手动实现的堆
    cout << "===== 使用手动实现的堆 =====" << endl;
    MinHeap heapTriage;

    // 添加患者
    heapTriage.addPatient(Patient("张三", 3)); // 中度
    heapTriage.addPatient(Patient("李四", 1)); // 危重
    heapTriage.addPatient(Patient("王五", 2)); // 紧急
    heapTriage.addPatient(Patient("赵六", 4)); // 轻度
    heapTriage.addPatient(Patient("钱七", 1)); // 危重
    heapTriage.addPatient(Patient("孙八", 3)); // 中度

    // 显示当前队列
    heapTriage.printQueue();

    // 治疗患者
    cout << "\n开始治疗患者..." << endl;
    while (!heapTriage.isEmpty()) {
        Patient next = heapTriage.peekNext();
        cout << "治疗: " << next.name << " [严重程度: ";
        switch (next.severity) {
            case 1: cout << "危重"; break;
            case 2: cout << "紧急"; break;
            case 3: cout << "中度"; break;
            case 4: cout << "轻度"; break;
        }
        cout << "] ";
        printWaitTime(next.arrivalTime);
        cout << endl;

        heapTriage.treatNext(); // 治疗该患者

        // 模拟治疗间隔 - 使用正确的命名空间
        this_thread::sleep_for(chrono::seconds(1));
    }

    // 使用STL优先队列
    cout << "\n\n===== 使用STL优先队列 =====" << endl;
    priority_queue<Patient> pqTriage;

    // 添加患者
    pqTriage.push(Patient("张三", 3)); // 中度
    pqTriage.push(Patient("李四", 1)); // 危重
    pqTriage.push(Patient("王五", 2)); // 紧急
    pqTriage.push(Patient("赵六", 4)); // 轻度
    pqTriage.push(Patient("钱七", 1)); // 危重
    pqTriage.push(Patient("孙八", 3)); // 中度

    // 治疗患者
    cout << "\n开始治疗患者..." << endl;
    while (!pqTriage.empty()) {
        Patient next = pqTriage.top();
        cout << "治疗: " << next.name << " [严重程度: ";
        switch (next.severity) {
            case 1: cout << "危重"; break;
            case 2: cout << "紧急"; break;
            case 3: cout << "中度"; break;
            case 4: cout << "轻度"; break;
        }
        cout << "] ";
        printWaitTime(next.arrivalTime);
        cout << endl;

        pqTriage.pop(); // 治疗该患者

        // 模拟治疗间隔 - 使用正确的命名空间
        this_thread::sleep_for(chrono::seconds(1));
    }

    cout << "\n所有患者治疗完成!" << endl;
    cout << "================================" << endl;

    return 0;
}

输出内容文本显示:

========== 医院急诊分诊系统 ==========

病情严重程度定义:
1-危重(最高) 2-紧急 3-中度 4-轻度(最低)

===== 使用手动实现的堆 =====
当前等待患者 (6人):
==========================================
姓名         严重程度到达时间
------------------------------------------
李四         危重    Thu Jun 26 11:06:25 2025
钱七         危重    Thu Jun 26 11:06:25 2025
王五         紧急    Thu Jun 26 11:06:25 2025
赵六         轻度    Thu Jun 26 11:06:25 2025
张三         中度    Thu Jun 26 11:06:25 2025
孙八         中度    Thu Jun 26 11:06:25 2025
==========================================

开始治疗患者...
治疗: 李四 [严重程度: 危重] (等待时间: 0分钟)
治疗: 钱七 [严重程度: 危重] (等待时间: 0分钟)
治疗: 王五 [严重程度: 紧急] (等待时间: 0分钟)
治疗: 孙八 [严重程度: 中度] (等待时间: 0分钟)
治疗: 张三 [严重程度: 中度] (等待时间: 0分钟)
治疗: 赵六 [严重程度: 轻度] (等待时间: 0分钟)


===== 使用STL优先队列 =====

开始治疗患者...
治疗: 李四 [严重程度: 危重] (等待时间: 0分钟)
治疗: 钱七 [严重程度: 危重] (等待时间: 0分钟)
治疗: 王五 [严重程度: 紧急] (等待时间: 0分钟)
治疗: 孙八 [严重程度: 中度] (等待时间: 0分钟)
治疗: 张三 [严重程度: 中度] (等待时间: 0分钟)
治疗: 赵六 [严重程度: 轻度] (等待时间: 0分钟)

所有患者治疗完成!
================================

至此,【堆与优先队列】“数据金字塔”与“我是VIP”案例已全部完成。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值