数据排序(基础算法)

目录

【第二节:数组排序】

0.预告

1.选择排序

[算法解析]

例2.1(选择排序)输入n个数,将n个数按从小到大的顺序输出(n <= 10000)

[输入样例]

[输出样例]

[算法分析]

2.冒泡排序

[算法解析]

例2.2 (冒泡排序)车厢重组

[问题描述]

[输入文件]

[输出文件]

[输入样例]

[输出样例]       

[算法分析]     

3.插入排序

[算法分析]     

4.桶排序  

[算法分析]

[程序实现]

[运行结果]

[样例输入]

[样例输出]

例2.3:明明的随机数(NOIP2006)

[问题描述]

[输入文件]

[输出文件]

[输入样例]

[输出样例]

[分析]   

[参考代码]         

5.快速排序

[算法分析]

6.归并排序

[算法分析]

7.逆序对

[算法分析]

8.各种排序算法的比较(注意按照比较来选取适当排序)

(1)稳定性比较

(2)时间复杂性比较

(3)辅助空间的比较

(4)其他比较

9.总结


【第二节:数组排序】

0.预告

大家好,小编和大家又见面了。

今天给大家带来的是数据排序这一算法。

话不多说直接开始。

—————————————————正片开始———————————————————

信息获取后通常需要处理,我们毕竟需要处理的信息来便于使用(否则全是0101的心态要崩溃)。

信息处理方法有很多,一般都是数组的排序、查找、插入、删除、归并等操作。相信大家已经接触了这方面的知识,我们今天就来介绍一下数组排序的几种很重要的方法。

1.选择排序
[算法解析]

这个排序方法主要就是从每一趟待排序的数据元素中选出最小(大)的的一个元素,按顺序放在待排序的数列的最前,直到全部待排序的元素排完即可。

我们举个例子:

初始数组:[49 38 65 97 76 13 27 49]

第一次:13 [38 65 97 76 49 27 49]

第二次:13 27 [65 97 76 49 38 49]

第三次:13 27 38 [97 76 49 65 49]

第四次:13 27 38 49 [76 97 65 49]

第五次:13 27 38 49 49 [97 65 76]

第六次:13 27 38 49 49 65 [97 76]

第七次:13 27 38 49 49 65 76 [97]

结果:13 27 38 49 49 65 76 97

大概就是这样,接下来看这个方法的例题:

例2.1(选择排序)输入n个数,将n个数按从小到大的顺序输出(n <= 10000)
[输入样例]

8                                                                                                                                                         

49 38 65 97 76 13 27 49

[输出样例]

13 27 38 49 49 65 76 97

[算法分析]

归纳一下我们刚刚说的排序的过程,我们可以得出以下的步骤:

(1)将读入数据放至数组a.

(2)在a[1] ~ a[n]中选出最小的元素,与第1位置的值交换,即将最小值放入a[1]中。

(3)在a[2] ~ a[n]中选出最小的元素,与第2位置的值交换,即将最小值放入a[2]中。

                                                                ......

(n)直到a[n-1]与a[n]比较完后输出数组。

我们可以用两层循环完成算法,外层i控制当前序列最小值存放的数组位置,内层j控制从i + 1到n序列中找出最小的元素a[k]。程序如下:

#include <bits/stdc++.h>
using namespace std;

const int N = 10001;

int main(){
	int n,k;
	double temp,a[N];
	cin >> n;
	for(int i = 1;i <= n;i++)
		cin >> a[i];                  //输入n个数
	for(int i = 1;i <= n;i++)         //i控制当前序列中最小值存放的数据位置
	{
		k = i;
		for(int j = i + 1;j <= n;j++) //在当前无序区a[i ~ n]中选最小的元素a[k]
		{
			if(a[j] < a[k]) k = j;
		}
		if(k != i)                    //交换a[i]和a[k],将当前最小值放到a[i]位置
		{
			temp = a[i];
			a[i] = a[k];
			a[k] = temp;
		}
	}
	
	for(int i = 1;i <= n;i++)
		cout << a[i] << " ";
	return 0;
}

这样便完成了。

2.冒泡排序
[算法解析]

冒泡排序(这个名字后面会讲)就是以n个人站队列为例,从第一个开始,依次比较相邻的两个是否为逆序(也就是前大后小或前小后大,具体情况看题目要求),如果逆序了,就交换这两个人的位置,也就是第1个人与第2个人,如果是逆序,那么就交换直到n - 2与n - 1比较完毕。如此,进行n - 1轮后,队列就有序了。

我们从上面的分析中可以看出,每进行一轮比较后,n个数的排序便变成了n - 1个数的排序。

我们还是举个例子:例如有6个数(6,5,3,4,1,2)要排序:(a - b代表a和b交换了,a / b代表固定原排序)

第一趟:5 - 6   3   4   1   2

              5   3 - 6   4   1   2

              5   3   4 - 6   1   2

              5   3   4   1 - 6   2

              5   3   4   1   2 - [6](第一趟结束,6固定)

第二趟:3 - 5   4   1   2   6

              3   4 - 5   1   2   6

              3   4   1 - 5   2   6

              3   4   1   2 - [5  6](第二趟结束,5、6固定)

第三趟:3 / 4   1   2   5   6

              3   1 - 4   2   5   6

              3   1   2 - [4  5  6](第三趟结束,4、5、6固定)

第四趟:1 - 3   2   4   5   6

              1   2 - [3  4  5  6](第四趟结束,3、4、5、6固定)

第五趟:1 / [2  3  4  5  6](第五趟结束,2、3、4、5、6固定)

最终结果:[1  2  3  4  5  6]

五趟排序后,6个整数便排序完成。我们发现,排序过程中大数会逐渐向后移动,如同气泡上升一般,所以叫做冒泡排序。 

我们整理一下上面的步骤,可以得出:

(1)将读入数据放入数组a。

(2)比较相邻前后两个数据,根据题目要求进行前后交换。

(3)对数组的第一个数据到n个数据进行一次大遍历后,最大的一个数据就“冒”到数组第n个位置。

(4)n = n - 1,如果n不为1就重复前面2步,直到完成。

我们依然可以用两层算法,外层i控制每轮要进行多少次循环,内层j控制每轮i次比较相邻两个元素是否逆序,若逆序就交换这两个元素。

但我们发现,有些数据不一定要n-1次排完,于是,我们可以设置一个布尔值,判断一下是否有进行交换,若没有交换,说明已经排序完成,进而减少不必要的排序,那么最终结果如下:

#include <bits/stdc++.h>
using namespace std;

int n;
int temp,a[10001];
bool ok;

int main(){
	cin >> n;
	for(int i = 1;i <= n;i++)
		cin >> a[i];                  //输入n个数
	for(int i = n;i > 1;i--){         //进行n + 1轮冒泡
		ok = 1;                       //判断是否有交换
		for(int j = 1;j < i;j++){     //每轮进行i次比较
			if(a[j] > a[j + 1]){      //相邻两数比较,若逆序就交换
				swap(a[j],a[j + 1]);
				ok = false;           //没交换就退出
			}
		}
		if(ok) break;
	}
	for(int i = 1;i <= n;i++)
		cout << a[i] << " ";
	
	return 0;
}

废话不多说,直接看例题:

例2.2 (冒泡排序)车厢重组
[问题描述]

在一个旧式火车站旁有一座桥,其桥面可以绕河中心的桥墩水平旋转。一个车站的职工发现桥的长度最多能容纳两节车厢,如果将桥旋转180°,则可以把相邻两节车厢的位置交换,用这种方法可以重新排列车厢的顺序。于是他就负责用这座桥将进站的车厢按车厢号从小到大的顺序排列,他退休后,火车站决定将这一工作自动化,其中一项重要的工作就是编一个程序,输入初始的车厢顺序,计算最少用多少步就能将车厢排序完毕。

[输入文件]

输入文件有两行数据,第一行是车厢总数n(1 ≤ n ≤ 10000 ),第二行是n个不同的数表示初始的车厢排序。

[输出文件]

一个数据,即最少旋转次数。

[输入样例]

4                                                                                                                                                        

4  3  2  1

[输出样例]       

6

[算法分析]     

经典的冒泡排序,直接看代码:

#include <bits/stdc++.h>
using namespace std;
long n,t,s,a[10000];

int main(){
	cin >> n;
	for(int i = 1;i <= n;i++)
		cin >> a[i];                   //输入n个车厢号
	for(int i = 1;i <= n - 1;i++)      //冒泡排序的另一种写法,相当于sort()
		for(int j = 1;j <= n - i;j++)  
			if(a[j] >a[j + 1])         //判断车厢号是否逆序
			{
				swap(a[j],a[j + 1]);   
				s++;                   //统计车厢旋转次数
			}
	cout << s;                         //最少的旋转次数
	return 0;
}

冒泡排序结束。

这里插播一条信息哈:其实整个算法用sort即可代替,sort函数就是以下下用法:

#include <bits/stdc++.h>
using namespace std;

int a[101],n;
/*
bool cmp(int x,int y){
	return x > y;
}
这里调用cmp就是让它变为降序
*/

int main(){
	cin >> n;
	for(int i = 1;i <= n;i++){
		cin >> a[i];
	}
	sort(a + 1/*数组名,后面加x代表从a[x]开始排*/,a + n + 1/*代表末值,注意+1代表到a + n排序结束(不包尾)*/,cmp/*这里的cmp是指从什么到什么,sort默认排序为升序,用cmp函数可调降序或其他,具体自查*/);
	for(int i = 1;i <= n;i++){
		cout << a[i] << " ";
	}
	return 0;
}
3.插入排序
[算法分析]     

插入排序简单来说的话,你可以想象打牌时抓牌的场景,为了方便,抓牌时一般都是一遍抓牌一边按花色、大小插入恰当的位置当抓完所有的牌时,手中的牌便是有序的,这种排序方法便是插入排序。

当读入一个元素时,在已经排序好的序列中,搜寻他正确的位置,再放入读入的元素。但我们不该忽略一个重要的问题:再插入这个元素前,应当先将他后面的所有元素后移一位,以保证插入位置的原元素不被覆盖。

例如,设输入了8个数36 25 48 12 65 43 20 58,则插入排序就是这样的:( n 代表插入元素)

初始:[36] 25 48 12 65 43 20 58

第一:[25 36] 48 12 65 43 20 58

第二:[25 36 48] 12 65 43 20 58

第三:[12 25 36 48] 65 43 20 58

第四:[12 25 36 48 65] 43 20 58

第五:[12 25 36 43 48 65] 20 58

第六:[12 20 25 36 43 48 65] 58

第七:[12 20 25 36 43 48 58 65]             

直接看程序:

#include <bits/stdc++.h>
using namespace std;
int n,posj,posk;
double temp,a[10001];

int main(){
	cin >> n;
	for(int i = 1;i <= n;i++)
		cin >> a[i];                                //输入n个数
	for(int i = 1;i <= n;i++){
		for(posj = i - 1;posj >= 1;posj--){         //在前面有序区间中为a[i]找合适的插入位置
			if(a[posj] < a[i]) break;               //找到比a[i]小的位置就退出,插入其后
		}
		if(posj != i - 1){
			temp = a[i];                            //将比a[i]大的数后移
			for(posk = i - 1;posk > posj;posk--){
				a[posk + 1] = a[posk];              //将a[i]放在正确位置上
			}
			a[posk + 1] = temp;
		}
	}
	for(int i = 1;i <= n;i++)
		cout << a[i] << " ";                        //输出
	return 0;
}

结束不送:)

4.桶排序  
[算法分析]

桶排序是指若待排序的值在一个明显的有限范围内(整型)时,可设计有限个有序桶,待排序的值装入对应的桶(也可以传入若干值),桶号就是待排序的值,顺序输出各桶内的值,将得到有序的排列。

[程序实现]
#include <bits/stdc++.h>
using namespace std;

int b[101],n,k;

int main(){
	cin >> n;
	memset(b,0,sizeof(b));         //初始化
	for(int i = 1;i <= n;i++){
		cin >> k;
		b[k]++;                    //将等于k的值全部装入第k桶中
	}
	for(int i = 0;i <= 100;i++){   //输出排序结果
		while(b[i] > 0){           //相同整数要重复输出
			cout << i << " ";
			b[i]--;                //输出一个个数-1
		}	
	}
	cout << endl;
}
[运行结果]
[样例输入]

10                                                                                                                                                        2  3  1  2  4  55  3  55  3  2

[样例输出]

1 2 2 2 3 3 3 4 55 55

例2.3:明明的随机数(NOIP2006)
[问题描述]

明明想在学校中请一些同学一起做一项问卷调查,为了实验的客观性,他先用计算机生成了n个从1到1000之间的随机整数(n ≤ 100),对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。然后再把这些数从小到大排序,按照排好的顺序做调查请你协助明明“去重”与“排序”的工作。

[输入文件]

输入文件random.in有2行,第1行为1个正整数,表示所生成的随机数的个数n。第2行有n个用空格隔开的正整数,为所产生的随机数。

[输出文件]

输出文件random.out也是2行,第1行为1个正整数m,表示不相同的随机数的个数。第2行为m个用空格隔开的正整数,为从小到大排好序的不相同的随机数。

[输入样例]

10                                                                                                                                                       

20 40 32 67 40 20 89 300 400 15

[输出样例]

8                                                                                                                                                         

15 20 32 40 67 89 300 400

[分析]   

本题有个重要的特点就是每个数都介于0~1000之间的整数,可以开设一个下标为0~1000的数组b,b[0]记录值为0的个数,b[1]记录值为1的数......b[x]记录值为x的个数,那么从小到大输出b数组不为0的b数组下标值即可。

[参考代码]         
#include <bits/stdc++.h>
using namespace std;

int b[1001],n,m = 0,x;

int main(){
	cin >> n;
	memset(b,0,sizeof(b));         //初始化
	for(int i = 1;i <= n;i++){
		cin >> x;
		if(b[x] == 0) m++;         //b[x]表示x为新的随机数,m + 1
		b[x]++;                    //将等于x的值全部装入第x桶中
	}
	cout << m << endl;			   //不相同的随机数的个数
	for(int i = 0;i <= 1000;i++){  //输出排序结果
		while(b[i] > 0){           
			cout << i << " ";         
		}	
	}
	cout << endl;
	return 0;
}

 慢走不送×2:)

5.快速排序
[算法分析]

快速排序是冒泡排序的一种改进。它的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个数列有序。

假设待排序的序列为{a[1],a[l + 1],a[l + 2],......,a[r]},首先任意选取一个记录(通常可选中间一个记作为枢轴或支点),然后重新排列其余记录,将所有关键字小于它的记录都放在左子序列中,所有关键字大于它的记录都放在右子序列中。由此可以将该“支点”记录所在位置mid作分界线,将序列分割成两个子序列和。这个过程被称作一趟快速排序(或一次划分)。

一趟快速排序的具体做法是这样的:要设两个指针(之后会在“c++语言”系列详细介绍)i、j,它们的初值分别为l和r,设枢轴记录取mid,则首先从j所指位置起向前搜索找到第一个关键字小于mid的记录,然后从i所指位置起向后搜索,直至找到第一个关键字大于mid的记录,交换后重复这两步直至i > j为止。

参考代码如下:

#include <bits/stdc++.h>
using namespace std;
int a[101];
void qsort(int l,int r){
	int mid,p,l1 = l,r1 = r;
	mid = a[(l + r) / 2];         //将当前序列在中间位置的数定义为分隔数
	do{
		while(a[l1] < mid) l1++;  //在左半部分寻找比中间小的数
		while(a[r1] > mid) r1--;  //在右半部分寻找比中间小的数
		if(l1 <= r1){             //若找到一组与排序目标不同的数对,则交换它们
			p = a[l1];
			a[l1] = a[r1];
			a[r1] = p;
			l1++;
			r1--;                 //继续
		}
	}while(l1 <= r1);             //注意是 <= 不是 <
	if(l < r1) qsort(l,r1);       //若未达到两数边界,则递归搜索(第4节)左右区间
	if(l1 < r) qsort(l1,r);
}
int main(){
	int n;
	cin >> n;
	for(int i = 1;i <= n;i++)
		cin >> a[i];
	qsort(1,n);
	for(int i = 1;i <= n;i++){
		cout << a[i] << " ";
	}
	return 0;
}

快速排序的时间复杂度为O^nlog2,n,速度快,但它是不稳定的排序方法。就平均时间而言,快速排序是目前被认为最好的一种内部排序方法。

由以上讨论,我们知道,从时间来看,快速排序的平均性优于前面讨论过的各种排序方法,但快速排序需一个栈空间来实现递归。若每一趟排序都将记录序列均匀地分割成长度相接近的两个子序列,则栈的最大深度为log(n + 1)。

好的,快速排序也是过的很快啊(doge)。

6.归并排序
[算法分析]

归并排序是建立在归并操作上的一种有效的排序方法,该算法是采用分治法(Divide and Conpuer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列排序,再使子序列段间有序。若两个有序表合并成一个有序表,称为二路归并。

例如有8个步骤需要排序:10 4 6 3 8 2 5 7(这里电脑画笔有点抖,请大家见谅)

大概就是这样(有点丑doge)。

从上表我们也可以看出归并排序主要分两步:分解、合并。

我们简单看一下合并过程:

比较a[i]和a[j]的大小,若a[i] ≤ a[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表的元素a[j]复制到r[k]中,并令j和k 分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中的剩余元素复制到r中从下标k到下标t的单元。

归并排序我们通常用递归(第4节)实现,先把待排序区间[s,t]以中点二分,接着把左、右边子区间分别排序最后两边进行一次归并操作合并为有序区间[s,t]。看一下主要代码:

void msort(int s,int t){
	if(s == t) return;                      //如果只有一个数字则返回,无需排序
	int mid = (s + t) / 2;                  
	msort(s,mid);                           //分解左序列
	msort(mid + 1,t);                       //分解右序列
	int i = s,j = mid + 1,k = s;            //合并开始
	while(i <= mid && j <= t)
	{
		if(a[i] <= a[j])
		{
			r[k] = a[i];
			k++;
			i++;
		}else{
			r[k] = a[j];
			k++;
			j++;
		}
	}
	while(i <= mid)                         //复制左序列剩余值
	{
		r[k] = a[i];
		k++;
		i++;
	}
	while(j <= t)                           //复制右序列剩余值
	{
		r[k] = a[j];
		k++;
		j++;
	}
	for(int i = s;i <= t;i++) a[i] = r[i];
}

归并排序时间复杂度为O(nlogn),速度快。同时,归并排序也是稳定的排序,即相等元素的顺序不会改变。如输入记录1(1) 3(2) 2(3) 2(4) 5(5)(括号中是记录的关键字)时输出的1(1) 2(3) 2(4) 3(2) 5(5) 中的2和2是按顺序的输入。这对要排序数据包含多个信息而要按其中某一个信息排序,要求其他信息尽量按输入的顺序排列时很重要(这也是它比快速排序优势的地方)。

7.逆序对
[算法分析]

我们上面提到了归并排序是稳定的排序,相等的元素顺序不会改变,进而可以用其解决逆序对的问题。但在讨论之前,我们要先了解一下逆序对。

逆序对可以这样理解:设A为一个有n个数字的有序集(n > 1),其中所有数字各不相同。如果存在整数i,j使得1 ≤ i < j ≤ n而且A[i] > A[j],则 < A[i],A[j] > 这个有序对称为A的一个逆序对,也称作逆序数。

例:数组{3,1,4,5,2}的逆序对有 {3,1},{3,2},{4,2},{5,2} 共4个。

所谓逆序对的问题,其实是对给定数组序列求其逆序对的数量。

从逆序对定义上分析,逆序对就是数列中任意两个数满足大的在前,小的在后的组合。如果将这些逆序对都调整成顺序(小前大后),那么整个数列就变得有序,即排序。因而容易想到冒泡排序(第2个排序算法)的机制正好是利用消除逆序来实现排序的,也就是说交换相邻两个逆序数,最终实现整个序列有序,那么交换的次数即为逆序对的数量。

冒泡排序可解决逆序对问题,但是由于冒泡排序本身效率不高,时间复杂度O(n^2),对于n比较大的情况只能望而却步。我们可以这样认为,冒泡排序求逆序对效率之所以低,是因为其在统计逆序对数量的时候是一对一对统计的,而对于范围为n的序列,逆序对的数量最多可(n + 1) * n / 2,因此效率太低,那怎样可以一下子统计多个,而不是一个一个累加呢?这时候,我们的super star———归并排序就可以帮我们拯救世界(也就是解决问题doge)。

在合并操作中,我们假设左右两个区间为:

左:{3  4  7  9}                右:{1  5  8  10}

那么合并操作的第一步就是比较3和1,然后将1取出来,放到辅助数组中,这个时候我们发现右边的区间如果是当前比较的较小值,那么其会与左边剩余的数字产生逆序关系,也就是说1和3、4、7、9都产生了逆序关系,我们可以一下子统计出有4对逆序对,接下来5和7、9产生2对,8和9产生1对,那么就共有4 + 2 + 1对,这样效率便会大大提高,便可较好地解决逆序对问题。

而在算法的实现中,我们只需略微修改原有归并排序,当右边序列的元素为较小值时,就统计其产生的逆序对数量,就可以完成逆序对的统计。主要代码如下:

void msort(int s,int t){
	if(s == t) return;
	int mid = (s + t) / 2;
	msort(s,mid);
	msort(mid + 1,t);
	int i = s,j = mid + 1,k = s;
	while(i <= mid && j <= t)
	{
		if(a[i] <= a[j])
		{
			r[k] = a[i];
			k++;
			i++;
		}else{
			r[k] = a[j];
			k++;
			j++;
			ans += mid - i + 1;      //其实就加了一个统计
		}
	}
	while(i <= mid)
	{
		r[k] = a[i];
		k++;
		i++;
	}
	while(j <= t)
	{
		r[k] = a[j];
		k++;
		j++;
	}
	for(int i = s;i <= t;i++) a[i] = r[i];
}

其中ans += mid - i + 1这句代码统计新增逆序对数量,ans作为全局变量,用于统计逆序对数量,此时ans要增加左边区间剩余元素的个数。当归并排序结束后,逆序对问题也得到解决,ans即为逆序对的数量。

8.各种排序算法的比较(注意按照比较来选取适当排序)
(1)稳定性比较

插入排序、冒泡排序、二叉树排序、二路归并及其他线性排序是稳定的。

选择排序、希尔排序、快速排序、堆排序是不稳定的。即有跨度的交换都会导致不稳定。

(2)时间复杂性比较

插入排序、冒泡排序、选择排序的时间复杂性为O(n^2),快速排序、堆排序、归并排序的时间复杂度为O(nlog2,n),桶排序的复杂度为n;

若从最好情况考虑,则直接插入排序和冒泡排序的时间复杂度最好,为O(n),其他算法的最好情况同平均情况相同;若从最坏情况考虑,则快速排序的时间复杂度为O(n^2),,直接插入排序和冒泡排序虽然平均情况相同,但系数大约增加一倍,所以运行速度将降低一半,最坏情况对直接选择排序、堆排序和归并排序影响不大。

这样看来,在最好情况下,直接插入排序和冒泡排序最快;在平均情况下,快速排序最快;最坏情况下,堆排序和归并排序最快。

(3)辅助空间的比较

桶排序、二路归并的辅助空间为O(n),快速排序的辅助空间为O(log2,n),最坏情况为O(n),其他排序的辅助空间为O(1)。

(4)其他比较

插入、冒泡排序速度较慢,但参加排序的序列局部或整体有序时,能达到较快速度。而在这种情况下,快速排序反而慢了。

当n较小时,对稳定性不作要求时宜用选择排序,对稳定性有要求时宜用插入或冒泡排序。

若待排序的记录的关键字在一个明显有限的范围内时,且空间允许时宜用桶排序。

当n较大时,关键字元素比较随机,对稳定性没要求宜用快速排序。

当n较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序。

快速排序是目前基于比较内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短。

堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况,这两种排序都是不稳定的。

9.总结

好的,这就是小编今天给大家带来的“数组排序”的内容了,制作不易,我们下一节“递推算法”见,谢谢大家!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值