十大排序算法(慢慢更新)

冒泡排序(升序):

基本思想:冒泡排序是一种简单的排序算法,其核心思想是通过相邻元素的比较和交换,使得每一轮遍历都能将未排序部分中的最大元素“冒泡”到未排序部分的末尾,最终使数组按升序排列。经过多次这样的遍历,数组会变得有序。直接上代码,这个还是很好理解的,个人感觉。

        结合下面的代码和输出,手动推一遍就没问题了。代码中加了很多便于定位的输出,使用的时候删掉这些输出就可以了,应该是问题不大的。

#include <iostream>
#include <vector>
using namespace std;
// 打印数组
void printVector(const vector<int>& vec) {
    for (const auto& elem : vec) {
        cout << elem << " ";
    }
    cout << endl;
}
// 冒泡排序算法
void bubbleSort(vector<int>& nums) {
    int n = nums.size();
    for (int i = 0; i < n - 1; i++) {
		cout<<"开始第"<<i+1<<"轮排序:"<<endl;
        // 用于标记本轮是否发生交换
		bool swapped = false; 
		//从索引为0开始,相邻数据依次比较,大的换到前面去
        for (int j = 0; j < n - i - 1; j++) {
           cout<<"  "<<"排序中......"<<endl; 
			if (nums[j] > nums[j + 1]) {
                swap(nums[j], nums[j + 1]);
                swapped = true;
				cout<<"  ";
				printVector(nums);
            }
        }
		cout<<"  "<<"第"<<i+1<<"轮排序后的数组:";
		printVector(nums);
        // 如果本轮没有发生交换,说明数组已经有序,可以提前结束排序
        if (!swapped) {
            break;
        }
    }
}
int main() {
    vector<int> nums = {5, 3, 8, 1, 2, 7};
    cout << "排序前的数组:";
    printVector(nums);
    bubbleSort(nums);
    cout << "冒泡排序后的数组:";
    printVector(nums);

    return 0;
}

 上面代码的输出:【显示“排序中......”之后却没有显示数据说明 这次比较没有进行数据交换】

排序前的数组:5 3 8 1 2 7 
开始第1轮排序:
  排序中......
  3 5 8 1 2 7 
  排序中......
  排序中......
  3 5 1 8 2 7 
  排序中......
  3 5 1 2 8 7 
  排序中......
  3 5 1 2 7 8 
  第1轮排序后的数组:3 5 1 2 7 8 
开始第2轮排序:
  排序中......
  排序中......
  3 1 5 2 7 8 
  排序中......
  3 1 2 5 7 8 
  排序中......
  第2轮排序后的数组:3 1 2 5 7 8 
开始第3轮排序:
  排序中......
  1 3 2 5 7 8 
  排序中......
  1 2 3 5 7 8 
  排序中......
  第3轮排序后的数组:1 2 3 5 7 8 
开始第4轮排序:
  排序中......
  排序中......
  第4轮排序后的数组:1 2 3 5 7 8 
冒泡排序后的数组:1 2 3 5 7 8 

快速排序(升序):

基本思想见文章:排序——快速排序(Quick sort)-CSDN博客

        感觉很好理解,但是感觉它的代码和他讲的思想有点对不上,所以自己按照文章讲的思想重新写了一下,对照上面的思想,还有下面的代码和输出,一步一步推导写一下应该很快就可以掌握。

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

//输出vector数组函数
void printVector(const vector<int>& vec) {
    for (const auto& elem : vec) {
        cout << elem << " ";
    }
    cout << endl;
}
//快速排序算法【输出cout可删】
void quickSort(vector<int>& nums, int left, int right) {
    // 左端点大于等于右端点 递归结束
    if (left >= right) {
        return;
    }
    cout << "进行快速排序前的边界分别是left=" << left << "right=" << right<< endl;
    cout << "进行快速排序前的数组:" << endl;
    printVector(nums);
    //  确定边界和关键值
    int key = nums[left];
    cout << "确定的key的值为:" << key << endl;
    int i = left, j = right;
    //体现思想的主体代码
    while (i < j) {
        while (nums[j] >= key && i < j) {
            j--;
        }
        if (i < j) {
            nums[i] = nums[j];
            i++;
        }
        while (nums[i] <= key && i < j) {
            i++;
        }
        if (i < j) {
            nums[j] = nums[i];
            j--;
        }
    }
    nums[i] = key;
    cout << "排序后i的值:" << i << endl;
    cout << "边界索引分别是left=" << left << "right=" << right << endl;
    cout << "排序后的数组:" << endl;
    printVector(nums);
    cout << endl;
    
    quickSort(nums, left, i - 1);
    quickSort(nums, i + 1, right);
}

int main() {
    // 测试用例
    vector<int> nums = {3, 2, 1, 5, 6, 4};
    int right = nums.size();
    quickSort(nums, 0, right - 1);
    cout << "快速排序后的数组:" << endl;
    printVector(nums);
    return 0;
}

        这个的输出如下:简单解释一下,输出的第一块是main函数引用quickSort之后,进行第一次排序,此时做右端点是main种传入的参数0, right-1也就是0,5,key值这里设置的是左端点的值,即 key=3,按照代码进行排序后 i 的值是2,也就是最后key值放入的位置,排序后的数组是:1 2 3 5 6 4,此时数组以key值为中点分成了左右两个部分,左边的数都比key值小,右边的值都比key值大。以key为分界点将左右两个部分再分别进行快速排序,也就是代码:

quickSort(nums, left, i - 1);
quickSort(nums, i + 1, right);

 输出的第二块是对上面输出数组的左边部分【0 1】进行排序

输出的第三块是对上面输出数组的右边部分【5 6 4】进行排序

进行快速排序前的边界分别是left=0right=5
进行快速排序前的数组:
3 2 1 5 6 4 
确定的key的值为:3
排序后i的值:2
边界索引分别是left=0right=5
排序后的数组:
1 2 3 5 6 4 

进行快速排序前的边界分别是left=0right=1
进行快速排序前的数组:
1 2 3 5 6 4 
确定的key的值为:1
排序后i的值:0
边界索引分别是left=0right=1
排序后的数组:
1 2 3 5 6 4 

进行快速排序前的边界分别是left=3right=5
进行快速排序前的数组:
1 2 3 5 6 4 
确定的key的值为:5
排序后i的值:4
边界索引分别是left=3right=5
排序后的数组:
1 2 3 4 5 6 

快速排序后的数组:
1 2 3 4 5 6 

插入排序(升序):

基本思想:插入排序是一种简单直观的排序算法,其基本思想是构建一个有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。对于少量或者部分有序的数据,插入排序是一种快速有效的排序方法。 下面用一组数据来说明具体排序过程,应该还是比较好理解的。

当使用插入排序对数组 nums = {5, 3, 8, 1, 2, 7} 进行排序时,
默认第一个数字是排好序的,后面的数字是无序的,将后面的数字根据大小插入对应的位置。
每一次操作的过程如下:

初始数组: nums = {5, 3, 8, 1, 2, 7}

第一轮迭代:
当前数组状态: {5, 3, 8, 1, 2, 7}
将 3 插入到正确的位置:
3 比 5 小,交换位置: {3, 5, 8, 1, 2, 7}
第一轮迭代后前两个数据就是排好序的了

第二轮迭代:
当前数组状态: {3, 5, 8, 1, 2, 7}
将 8 插入到正确的位置:
8 比 5 大,保持位置不变: {3, 5, 8, 1, 2, 7}
第二轮迭代后前三个数据就是排好序的了【以此类推】

第三轮迭代:
当前数组状态: {3, 5, 8, 1, 2, 7}
将 1 插入到正确的位置:
1 比 8 小,交换位置: {3, 5, 1, 8, 2, 7}
1 比 5 小,交换位置: {3, 1, 5, 8, 2, 7}
1 比 3 小,交换位置: {1, 3, 5, 8, 2, 7}

第四轮迭代:
当前数组状态: {1, 3, 5, 8, 2, 7}
将 2 插入到正确的位置:
2 比 8 小,交换位置: {1, 3, 5, 2, 8, 7}
2 比 5 小,交换位置: {1, 3, 2, 5, 8, 7}
2 比 3 小,交换位置: {1, 2, 3, 5, 8, 7}

第五轮迭代:
当前数组状态: {1, 2, 3, 5, 8, 7}
将 7 插入到正确的位置:
7 比 8 小,交换位置: {1, 2, 3, 5, 7, 8}
排序完成:最终数组为 {1, 2, 3, 5, 7, 8}

  代码如下:

#include <iostream>
#include <vector>
using namespace std;
// 打印数组
void printVector(const vector<int>& vec) {
    for (const auto& elem : vec) {
        cout << elem << " ";
    }
    cout << endl;
}
// 插入排序算法
void insertionSort(vector<int>& nums) {
    int len = nums.size();
    for (int i = 1; i < len; i++) {
        int key = nums[i];
        int j ;
		for(j = i-1 ; j>=0 ; j--){
			if(nums[j] >key){
				nums[j+1] = nums[j] ;	
			}else{
				//break一定要有,不然j会在for循环中一直减到0
				break ;
			}
		}
        nums[j + 1] = key;
    }
}
int main() {
    vector<int> nums = {5, 3, 8, 1, 2, 7};
    cout << "排序前的数组:";
    printVector(nums);
    insertionSort(nums);
    cout << "插入排序后的数组:";
    printVector(nums);
    return 0;
}

        还有一种用while语句写的,个人比较习惯用for语句,但是while语句可能会更简单一点,不会踩到break的坑。

// 插入排序算法
void insertionSort(vector<int>& nums) {
    int len = nums.size();
    for (int i = 1; i < len; i++) {
		cout<<"第"<<i<<"次插入:"<<endl ;
        int key = nums[i];
        int j = i - 1;
        // 将比 key 大的元素向右移动
        while (j >= 0 && nums[j] > key) {
            nums[j + 1] = nums[j];
            j--;
        }
        nums[j + 1] = key;
        cout << "key的值:" << key << endl;
        printVector(nums);
		cout<<endl ;
    }
}

PS:在一次写题过程中我将代码写成了以下这个形式,结果报错了,然后我百思不得其解。甚至还用实例推了一下,最后发现问题是:如果待插入的数据比前面所有数据都要小,比如 key = 0 ,然后前面排好序的数据是 1 2 3 4 ,这样 j 从 3 开始递减一直到递减到 -1 就直接跳出内层 for 循环了,而由于 插入 key 的过程是在内层 for 语句的 else 里面,就会导致排序错误,所以为避免这种情况的发生,key 的插入必须在内层 for 循环之后。但是!!!!!这里就又出现了一个问题,因为我是在内层 for 循环里定义的变量 j ,在循环外就不能再使用这个变量了,所以这里定义 j 的语句要放在内层 for 语句的前面!

int j ;

for ( j = i - 1 ;  j >= 0 ; j--)

void insertsort(vector<int>& nums) {
    int len = nums.size();
    for (int i = 1; i < len; i++) {
        int key = nums[i];
        for (int j = i - 1; j >= 0; j--) {
            if (key < nums[j]) {
                nums[j + 1] = nums[j];
            } else {
                nums[j] = key;
                break;
            }
        }
    }
}

 

希尔排序(升序):

基本思想:希尔排序是插入排序的一种改进版本,也被称为“缩小增量排序”。它通过将数据集分成多个较小的子集进行排序,然后逐渐减少子集的大小,最后使用插入排序完成整个数据集的排序。这样做的目的是减少数据的交换次数和移动次数,从而提高效率。

实现步骤:

  • 选择增量序列:选择一个增量序列,通常从较大的增量开始逐步缩小到1。常见的增量序列是希尔增量(初始增量一般为数组长度的一半,然后逐步减半)。
  • 分组排序:根据当前增量将数组分成多个子数组,对每个子数组进行插入排序。
  • 逐步缩小增量:减小增量,重复步骤2,直到增量为1。此时,对整个数组进行一次插入排序。

PS:插入排序用了两个for循环,希尔排序用了3个,因为其在插入排序的基础上增加了一个“增量gap循环”

复杂度分析:

  • 时间复杂度:希尔排序的时间复杂度取决于所选择的增量序列。平均情况下,希尔排序的时间复杂度在 O(nlog⁡2n) 到 O(n^{3/2}) 之间。对于希尔增量序列,时间复杂度为 O(n^2) 。一些优化的增量序列可以将时间复杂度降低到 O(nlog⁡n) 。
  • 空间复杂度:希尔排序是一个原地排序算法,因此它的空间复杂度为 O(1) 。
  • 稳定性:希尔排序不是一个稳定的排序算法,因为元素的比较和交换不局限于相邻元素。

实现代码:

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

// 打印数组
void printVector(const vector<int>& vec) {
    for (const auto& elem : vec) {
        cout << elem << " ";
    }
    cout << endl;
}

// 希尔排序算法
void shellSort(vector<int>& nums) {
    int len = nums.size();
    for (int gap = len / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < len; i++) {
            int key = nums[i];
            int j;
            for (j = i; j >= gap && nums[j - gap] > key; j = j - gap) {
                nums[j] = nums[j - gap];
            }
            nums[j] = key;
        }
    }
}

int main() {
    vector<int> nums = {5, 3, 8, 1, 2, 7, 9, 9, 6, 4};
    cout << "排序前的数组:";
    printVector(nums);
    shellSort(nums);
    cout << "希尔排序后的数组:";
    printVector(nums);
    return 0;
}

为了详细了解希尔排序的执行过程,我们将用增量序列 {3, 1} 对数组 {5, 3, 8, 1, 2, 7} 进行排序。

初始数组:5, 3, 8, 1, 2, 7
第一步:使用增量 3 进行排序
    分组情况:
      第1组: {5, 1}
      第2组: {3, 2}
      第3组: {8, 7}
分别对每一组进行插入排序。
      组1: {5, 1}  插入排序后:{5, 1} ,那么原数组:1, 3, 8, 5, 2, 7
      组2: {3, 2}  插入排序后:{2, 3} ,那么原数组:1, 2, 8, 5, 3, 7
      组3同理,原数组就变成了:1, 2, 7, 5, 3, 8

第二步:使用增量 1 进行排序(就是插入排序)
      数组:1, 2, 7, 5, 3, 8,对这个数组进行插入排序后就得到了正确的结果:1, 2, 3, 5, 7, 8


选择排序(升序):

基本思想:选择排序(Selection Sort)是一种简单直观的排序算法。它的基本思想是每次从未排序的部分中选出最小(或最大)的元素,并将其放到已排序部分的末尾,直到所有元素都排序完成。

具体步骤如下:

  1. 从待排序序列中找到最小(最大)元素,将其放到序列的起始位置。
  2. 从剩余未排序元素中继续寻找最小(最大)元素,然后放到已排序序列的末尾。
  3. 重复上述步骤,直到所有元素都排序完成。

时间复杂度:

选择排序的时间复杂度为 O(n2),其中 n 是数组的长度。具体来说:

  • 最佳情况时间复杂度:O(n2)
  • 最坏情况时间复杂度:O(n2)
  • 平均情况时间复杂度:O(n2)

空间复杂度为 O(1),因为选择排序是原地排序,不需要额外的存储空间。

代码如下:好像没什么好讲的,遍历找到最小值,然后交换就可以了。值得注意的是,需要交换索引,也就是nums[i]nums[minindex],而不是交换某两个数字。

#include <iostream>
#include <vector>
using namespace std;
// 打印数组
void printVector(const vector<int>& vec) {
    for (const auto& elem : vec) {
        cout << elem << " ";
    }
    cout << endl;
}
// 选择排序算法
void selectionSort(vector<int>& nums) {
    int len = nums.size();
    int i = 0 ;
	for(i = 0 ; i<len-1 ;i++){
		int minindex = i ;
		for(int j = i+1 ;j<len ;j++){
			if(nums[j] < nums[minindex]){
                //这里一定是要找到最小值的在的《位置》,也就是数组对应的索引
				minindex = j ;
			}
		}
		swap(nums[i], nums[minindex]);
		printVector(nums);
	}
}
int main() {
    vector<int> nums = {5, 3, 8, 1, 2, 7};
    cout << "排序前的数组:";
    printVector(nums);
    selectionSort(nums);
    cout << "选择排序后的数组:";
    printVector(nums);
    return 0;
}

归并排序(升序):

基本思想:归并排序是一种基于分治思想的排序算法。其基本思想是将数组分成两个子数组,分别对这两个子数组进行排序,然后将已排序的子数组合并成一个有序的数组。

时间复杂度:

        归并排序的时间复杂度为 O(nlog⁡n),其中 n 是数组的长度。其空间复杂度为 O(n),因为需要额外的数组来存放合并过程中的中间结果。

代码实现:

堆排序(升序):

基本思想:堆排序是一种基于堆数据结构的比较排序算法。堆是一个完全二叉树,可以用数组表示。有大根堆(Max Heap)和小根堆(Min Heap)两种类型。在大根堆中,每个节点的值都大于或等于其子节点的值。换句话说,根节点包含堆中的最大值。在小根堆中,每个节点的值都小于或等于其子节点的值。换句话说,根节点包含堆中的最小值。

基本步骤:

  • 堆构造:将数组构造成一个最大堆。(升序)
  • 将堆顶元素(最大值)与数组末尾元素交换。
  • 堆调整:调整堆结构,使其重新成为最大堆。
  • 重复步骤 2 和 3,直到数组有序。

复杂度:

  • 最优时间复杂度: O(n log n)
  • 最坏时间复杂度: O(n log n)
  • 平均时间复杂度: O(n log n)
  • 空间复杂度: O(1)

代码实现:

#include <iostream>
#include <vector>
using namespace std;
// 打印数组
void printVector(const vector<int>& vec) {
    for (const auto& elem : vec) {
        cout << elem << " ";
    }
    cout << endl;
}
// 堆排序辅助函数:堆化
void heapify(vector<int>& nums, int n, int i) {
    int largest = i; // 初始化最大值为根节点
    int left = 2 * i + 1; // 左子节点
    int right = 2 * i + 2; // 右子节点
    // 如果左子节点大于根节点
    if (left < n && nums[left] > nums[largest])
        largest = left;
    // 如果右子节点大于当前最大值
    if (right < n && nums[right] > nums[largest])
        largest = right;
    // 如果最大值不是根节点
    if (largest != i) {
        swap(nums[i], nums[largest]);
        // 递归堆化子树
        heapify(nums, n, largest);
    }
}

// 堆排序
void heapSort(vector<int>& nums) {
    int n = nums.size();
    // 构建初始堆
    for (int i = n / 2 - 1; i >= 0; i--)
        heapify(nums, n, i);
	cout<<"初始堆构建结束之后:";printVector(nums);
    // 一个个从堆中取出元素
    for (int i = n - 1; i > 0; i--) {
        // 移动当前根到数组末尾
        swap(nums[0], nums[i]);
        // 调整堆结构
        heapify(nums, i, 0);
		cout<<"调整"<<n-i<<"次后:";printVector(nums);
    }
}
int main() {
    vector<int> nums = {5, 3, 8, 1, 2, 7};
    cout << "排序前的数组:";
    printVector(nums);
    heapSort(nums);
    cout << "堆排序后的数组:";
    printVector(nums);
    return 0;
}

PS:构建初始堆时的逻辑

        也就是这一段代码的逻辑

// 构建初始堆
    for (int i = n / 2 - 1; i >= 0; i--)
        heapify(nums, n, i);

       在堆排序中,构建初始堆时的目标是将数组调整为一个最大堆。最大堆的定义是每个父节点的值都大于或等于其子节点的值。要构建这个初始堆,需要从最后一个非叶节点开始进行堆化,一直向上调整到根节点。

在完全二叉树中,如果我们用数组来表示堆,那么对于一个节点 i

  • 左子节点的位置是 2 * i + 1
  • 右子节点的位置是 2 * i + 2

叶节点是那些没有子节点的节点。在一个数组表示的堆中,叶节点总是位于数组的后半部分。对于一个包含 n 个元素的数组,最后一个非叶节点的位置可以通过以下方式找到:

  • 数组的最后一个元素的索引是 n - 1【它一定是叶节点,即它在树的最下方】
  • 由上面的公式可以推出,它的父节点的位置是 (n - 1 - 1) / 2,也就是 n / 2 - 1

桶排序(升序):

基本思想:桶排序(Bucket Sort)是一种基于分配的排序算法,其基本思想是将数组元素分配到若干个桶中,再对每个桶分别进行排序,最后将各个桶中的元素合并,得到排序后的数组。桶排序适用于数据分布均匀的情况,可以在近似线性的时间内完成排序。

实现步骤:

  • 创建桶:根据数据范围和桶的数量,将数组中的元素分配到不同的桶中。每个桶存储一定范围内的元素。
  • 分配元素:遍历数组,将每个元素分配到对应的桶中。
  • 排序桶:对每个桶中的元素进行排序,可以使用其他排序算法(如插入排序、快速排序等)。
  • 合并桶:将各个桶中的元素依次合并,得到排序后的数组。

复杂度分析:

  • 时间复杂度
    • 最好情况:O(n+k),其中 n 是数组元素的数量,k 是桶的数量。当元素分布均匀时,桶中的元素数量较少,排序效率较高。
    • 最坏情况:O(n^2),当所有元素被分配到同一个桶时,退化为对一个桶中的元素进行排序。
    • 平均情况:O(n+k),一般情况下表现较好。
  • 空间复杂度:O(n+k),需要额外的空间来存储桶。

代码实现:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 打印数组
void printVector(const vector<int>& vec) {
    for (const auto& elem : vec) {
        cout << elem << " ";
    }
    cout << endl;
}
// 桶排序算法
void bucketSort(vector<int>& nums, int bucketCount) {
    //检查输入数据的有效性
	if (nums.empty() || bucketCount <= 0) return;
    // 找到数组中的最大值和最小值
    int minValue = *min_element(nums.begin(), nums.end());
    int maxValue = *max_element(nums.begin(), nums.end());
    // 计算每个桶的大小
    int bucketSize = (maxValue - minValue) / bucketCount + 1;
    // 创建桶-其中包含了 bucketCount 个桶(每个桶都是一个 vector<int>)
	//但是没有指定每个桶的大小
    vector<vector<int>> buckets(bucketCount);
    // 将元素分配到对应的桶中
    for (int num : nums) {
        int bucketIndex = (num - minValue) / bucketSize;
        buckets[bucketIndex].push_back(num);
    }
    // 对每个桶中的元素进行排序
    nums.clear();
    for (vector<int>& bucket : buckets) {
        sort(bucket.begin(), bucket.end()); // 使用STL中的sort函数进行排序
        nums.insert(nums.end(), bucket.begin(), bucket.end());
    }
}
int main() {
    vector<int> nums = {5, 3, 8, 1, 2, 7, 4, 6};
    cout << "排序前的数组:";
    printVector(nums);
    int bucketCount = 3; // 设置桶的数量
    bucketSort(nums, bucketCount);
    cout << "桶排序后的数组:";
    printVector(nums);
    return 0;
}

        其中,if (nums.empty() || bucketCount <= 0) return;语句是在检查输入数据的有效性,确保排序函数只有在 nums 非空且 bucketCount 大于 0 的情况下才会继续执行后续的排序操作,如果输入无效,则直接退出函数,不进行后续的排序操作。

  min_element 是标准库 <algorithm> 中的一个函数,用于找到指定范围内的最小元素。该函数将返回一个指向 nums 数组中最小元素的迭代器。‘* ’ 运算符用于解引用迭代器,获取迭代器指向的值,即数组 nums 中最小的元素值。

        语句 int bucketSize = (maxValue - minValue) / bucketCount + 1; 用于计算每个桶应该覆盖的数值范围,而不是每个桶的容量(深度)。桶的容量是动态调整的,因为在一开始我们无法准确知道每个桶会包含多少个元素。将待排序的数据范围,平均分配到每个桶上,最后的 +1 是为了确保每个桶都能容纳可能的整数取整误差。例如,如果数据范围为 9,桶数为 3,则 (9 / 3) 为 3。但如果数据恰好分布在边界上,如 0, 3, 6, 9,最后一个桶可能无法容纳最高值 9。加上 1 可以确保所有数据点都能被正确分配。

        nums.clear();语句的作用是清空原数组,因为数组中的数据已经全部放入桶中,接下来要将排序好的元素重新放入,故需要先清空 nums 。

写在最后:

发现一个好东西:十大经典排序算法(动图演示,收藏好文)

         看懂每一个排序算法的思路再看动图会比较好,更加直观并且帮助记忆。没懂排序思想的话看动图感觉还是云里雾里的。

  • 14
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值