排序算法

简介编辑

随着科技的不断发展, 计算机的应用领域越来越广,但由于计算机硬件的速度和存储空间的有限性,如何提高计算机速度并节省存储空间一直成为 软件编制人员努力的方向,在众多措施中, 排序操作成为程序设计人员考虑的因素之一,排序方法选择得当与否直接影响程序执行的速度和辅助存储空间的占有量,进而影响整个软件的性能。

2概念编辑

排序(Sorting) 是 计算机程序设计中的一种重要操作,它的功能是将一个 数据元素(或记录)的任意序列,重新排列成一个关键字有序的序列。
稳定度(稳定性)
一个 排序算法是 稳定的,就是当有两个相等记录的关键字 RS,且在原本的列表中 R出现在 S之前,在排序过的列表中 R也将会是在 S之前。
当相等的元素是无法分辨的, 比如像是整数,稳定度并不是一个问题。然而,假设以下的数对将要以他们的第一个数字来 排序
(4,1)(3,1)(3,7)(5,6)在这个状况下,有可能产生两种不同的结果,一个是依照相等的键值维持相对的次序,而另外一个则没有:
(3,1)(3,7)(4,1)(5,6) (维持次序)
(3,7)(3,1)(4,1)(5,6) (次序被改变)
不稳定 排序算法可能会在相等的键值中改变纪录的相对次序,但是稳定排序算法从来不会如此。不稳定 排序算法可以被特别地实现为稳定。作这件事情的一个方式是人工扩充键值的比较,如此在其他方面相同键值的两个对象间之比较,就会被决定使用在原先数据次序中的条目,当作一个同分决赛。然而,要记住这种次序通常牵涉到额外的空间负担。

3分类编辑

计算机科学所使用的 排序算法通常被分类为:
(a)计算的 复杂度(最差、 平均、和 最好性能),依据列表(list)的大小(n)。
一般而言,好的性能是 O( nlog n),且坏的性能是 O( n^2)。对于一个 排序理想的性能是 O( n)。
而仅使用一个抽象关键比较运算的 排序算法总 平均上总是至少需要 O( nlog n)。
(b) 存储器使用量( 空间复杂度)(以及其他电脑资源的使用)
(c)稳定度: 稳定的 排序算法会依照相等的关键(换言之就是值)维持纪录的相对次序。
(d)一般的方法:插入、交换、选择、合并等等。 交换排序包含 冒泡排序快速排序插入排序包含 希尔排序选择排序包括堆排序等。

4算法列表编辑

简介

在这个 表格中,n是要被 排序的纪录数量以及k是不同键值的数量。

稳定的

冒泡排序(bubble sort) — O(n^2)
鸡尾酒排序(Cocktail sort,双向的 冒泡排序) — O( n^2)
插入排序(insertion sort)— O( n^2)
桶排序(bucket sort)— O( n); 需要 O( k) 额外空间
计数排序(counting sort) — O( n+ k); 需要 O( n+ k) 额外空间
合并排序(merge sort)— O( nlog  n); 需要 O( n) 额外空间
原地 合并排序— O( n^2)
二叉排序树排序 (Binary tree sort) — O( nlog  n)期望时间; O( n^2)最坏时间; 需要 O( n) 额外空间
鸽巢排序(Pigeonhole sort) — O( n+ k); 需要 O( k) 额外空间
基数排序(radix sort)— O( n· k); 需要 O( n) 额外空间
Gnome 排序— O( n^2)
图书馆排序— O( nlog  n) with high probability,需要 (1+ε) n额外空间

不稳定的

选择排序(selection sort)— O( n^2)
希尔排序(shell sort)— O( nlog  n) 如果使用最佳的现在版本
组合排序— O( nlog  n)
堆排序(heapsort)— O( nlog  n)
平滑排序— O( nlog  n)
快速排序(quicksort)— O( nlog  n) 期望时间,O( n^2) 最坏情况; 对于大的、乱数列表一般相信是最快的已知排序
Introsort— O( nlog  n)
Patience sorting— O( nlog  nk) 最坏情况时间,需要 额外的 O( nk) 空间,也需要找到最长的递增子串行(longest increasing subsequence)

不实用的排序算法

Bogo排序— O( n×  n!) 期望时间,无穷的最坏情况。
Stupid sort— O( n^3); 递归版本需要 O( n^2) 额外 存储器
珠排序(Bead sort) — O( n) or O(√ n),但需要特别的硬件
Pancake sorting— O( n),但需要特别的硬件
stooge sort——O(n^2.7)很漂亮但是很耗时

5排序算法编辑

简介

排序的算法有很多,对空间的要求及其时间效率也不尽相同。下面列出了一些常见的 排序算法。这里面 插入排序冒泡排序又被称作 简单排序,他们对空间的要求不高,但是时间效率却不稳定;而后面三种排序相对于简单排序对空间的要求稍高一点,但时间效率却能稳定在很高的水平。 基数排序是针对 关键字在一个较小范围内的排序算法。

插入排序

插入排序是这样实现的:
1、首先新建一个空列表,用于保存已 排序的有序数列(我们称之为"有序列表")。
2、从原数列中取出一个数,将其插入"有序列表"中,使其仍旧保持有序状态。
3、重复2号步骤,直至原数列为空。
插入排序的 平均 时间复杂度为平方级的,效率不高,但是容易实现。它借助了"逐步扩大成果"的思想,使有序列表的长度逐渐增加,直至其长度等于原列表的长度。

冒泡排序

冒泡排序是这样实现的:
1、从列表的第一个数字到倒数第二个数字,逐个检查:若某一位上的数字大于他的下一位,则将它与它的下一位交换。
2、重复1号步骤,直至再也不能交换。
冒泡排序平均 时间复杂度插入排序相同,也是平方级的,但冒泡排序是原地排序的,也就是说它不需要额外的存储空间。

选择排序

选择排序是这样实现的:
1、设 数组内存放了n个待排数字,数组下标从1开始,到n结束。
2、初始化i=1
3、从 数组的第i个元素开始到第n个元素,寻找最小的元素。
4、将上一步找到的最小元素和第i位元素交换。
5、i++,直到i=n-1算法结束,否则回到第3步
选择排序平均 时间复杂度也是O(n^2)的。
举例:
564
比如说这个,我想让它从小到大 排序,怎么做呢?
第一步:从第一位开始找最小的元素,564中4最小,与第一位交换。结果为465
第二步:从第二位开始找最小的元素,465中5最小,与第二位交换。结果为456
第三步:i=2,n=3,此时i=n-1,算法结束
完成

快速排序

现在开始,我们要接触高效 排序算法了。实践证明, 快速排序是所有排序算法中最高效的一种。它采用了分治的思想:先保证列表的前半部分都小于后半部分,然后分别对前半部分和后半部分 排序,这样整个列表就有序了。这是一种先进的思想,也是它高效的原因。因为在 排序算法中,算法的高效与否与列表中数字间的比较次数有直接的关系,而"保证列表的前半部分都小于后半部分"就使得前半部分的任何一个数从此以后都不再跟后半部分的数进行比较了,大大减少了数字间不必要的比较。但查找数据得另当别论了。

各算法的时间复杂度

插入排序 O(n^2)
冒泡排序 O(n^2)
选择排序 O(n^2)
快速排序 O(n log n)
堆排序 O(n log n)
归并排序 O(n log n)
基数排序 O(n)
希尔排序 O(n^1.25)

6复杂度编辑

简单排序算法

由于程序比较简单,所以没有加什么注释。所有的程序都给出了完整的运行代码,并在我的VC环境
下运行通过。因为没有涉及MFC和WINDOWS的内容,所以在BORLAND C++的平台上应该也不会有什么
问题的。在代码的后面给出了运行过程示意,希望对理解有帮助。

冒泡法

这是最原始,也是众所周知的最慢的算法了。他的名字的由来因为它的工作看来象是冒泡:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<iostream>
 
usingnamespacestd;
 
voidBubbleSort( int *pData,intCount)
 
{
 
intiTemp;
 
for (inti=0;i<Count;i++)
 
{
 
for (intj=Count-1;j>i;j--)
 
{
 
if (pData[j]<pData[j-1])
 
{
 
iTemp=pData[j-1];
 
pData[j-1]=pData[j];
 
pData[j]=iTemp;
 
}
 
}
 
}
 
}
 
voidmain()
 
{
 
intdata[7]={10,9,8,7,6,5,4};
 
BubbleSort(data,7);
 
for (inti=0;i<7;i++)
 
{
 
cout<<data[i]<< "" ;
 
}
 
cout<<endl;
 
system ( "PAUSE" );
 
}
倒序(最糟情况)
第一轮:10,9,8,7->10,9,7,8->10,7,9,8->7,10,9,8(交换3次)
第二轮:7,10,9,8->7,10,8,9->7,8,10,9(交换2次)
第一轮:7,8,10,9->7,8,9,10(交换1次)
循环次数:6次
交换次数:6次
其他:
第一轮:8,10,7,9->8,10,7,9->8,7,10,9->7,8,10,9(交换2次)
第二轮:7,8,10,9->7,8,9,10->7,8,10,9(交换1次)
(这是原撰写人的--7,8,10,9->7,8,10,9->7,8,10,9(交换0次),第二轮应该是这样的)
第三轮:7,8,9,10->7,8,9,10(交换1次)
循环次数:6次
交换次数:3次
上面我们给出了 程序段,现在我们分析它:这里,影响我们算法性能的主要部分是循环和交换,
显然,次数越多,性能就越差。从上面的程序我们可以看出循环的次数是固定的,为1+2+...+n-1。
写成公式就是1/2*(n-1)*n。
现在注意,我们给出O方法的定义:
若存在一常量K和起点n0,使当n>=n0时,有f(n)<=K*g(n),则f(n) = O(g(n))。(呵呵,不要说没学好数学呀,对于编程数学是非常重要的!!!)
现在我们来看1/2*(n-1)*n,当K=1/2,n0=1,g(n)=n*n时,1/2*(n-1)*n<=1/2*n*n=K*g(n)。所以f(n)
=O(g(n))=O(n*n)。所以我们程序循环的 复杂度为O(n*n)。
再看交换。从程序后面所跟的表可以看到,两种情况的循环相同,交换不同。其实交换本身同数据源的
有序程度有极大的关系,当数据处于倒序的情况时,交换次数同循环一样(每次循环判断都会交换),
复杂度为O(n*n)。当数据为正序,将不会有交换。 复杂度为O(0)。乱序时处于中间状态。正是由于这样的
原因,我们通常都是通过循环次数来对比算法。

交换法

交换法的程序最清晰简单,每次用当前的元素一一的同其后的元素比较并交换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream.h>
voidExchangeSort( int *pData,intCount)
{
intiTemp;
for (inti=0;i<Count-1;i++)
{ //共(count-1)轮,每轮得到一个最小值
for (intj=i+1;j<Count;j++)
{ //每次从剩下的数字中寻找最小值,于当前最小值相比,如果小则交换
if (pData[j]<pData[i])
{
iTemp=pData[i];
pData[i]=pData[j];
pData[j]=iTemp;
}
}
}
}
voidmain()
{
intdata[]={10,9,8,7,6,5,4};
ExchangeSort(data, sizeof (data)/ sizeof ( int ));
for (inti=0;i< sizeof (data)/ sizeof ( int );i++)
{
cout<<data[i]<< "" ;
}
cout<<endl;
system ( "PAUSE" );
}
第一轮: [1]  9,10,8,7->8,10,9,7->7,10,9,8(交换3次)
第二轮:7,10,9,8->7,9,10,8->7,8,10,9(交换2次)
第一轮:7,8,10,9->7,8,9,10(交换1次)
循环次数:6次
交换次数:6次
其他:
第一轮:8,10,7,9->8,10,7,9->7,10,8,9->7,10,8,9(交换1次)
第二轮:7,10,8,9->7,8,10,9->7,8,10,9(交换1次)
第一轮:7,8,10,9->7,8,9,10(交换1次)
循环次数:6次
交换次数:3次
从运行的 表格来看,交换几乎和冒泡一样糟。事实确实如此。循环次数和冒泡一样
也是1/2*(n-1)*n,所以算法的 复杂度仍然是O(n*n)。由于我们无法给出所有的情况,所以
只能直接告诉大家他们在交换上面也是一样的糟糕(在某些情况下稍好,在某些情况下稍差)。

选择法

现在我们终于可以看到一点希望:选择法,这种方法提高了一点性能(某些情况下)
这种方法类似我们人为的 排序习惯:从数据中选择最小的同第一个值交换,在从剩下的部分中
选择最小的与第二个交换,这样往复下去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<iostream.h>
voidSelectSort( int *pData,intCount)
{
intiTemp;
intiPos;
for (inti=0;i<Count-1;i++)
{
iTemp=pData[i];
iPos=i;
for (intj=i+1;j<Count;j++)
{
if (pData[j]<iTemp)
{
iTemp=pData[j];
iPos=j;
}
}
pData[iPos]=pData[i];
pData[i]=iTemp;
}
}
voidmain()
{
intdata[]={10,9,8,7,6,5,4};
SelectSort(data,7);
for (inti=0;i<7;i++)
cout<<data[i]<< "" ;
cout<< "\n" ;
}
倒序(最糟情况)
第一轮:10,9,8,7->(iTemp=9)10,9,8,7->(iTemp=8)10,9,8,7->(iTemp=7)7,9,8,10(交换1次)
第二轮:7,9,8,10->7,9,8,10(iTemp=8)->(iTemp=8)7,8,9,10(交换1次)
第一轮:7,8,9,10->(iTemp=9)7,8,9,10(交换0次)
循环次数:6次
交换次数:2次
其他:
第一轮:8,10,7,9->(iTemp=8)8,10,7,9->(iTemp=7)8,10,7,9->(iTemp=7)7,10,8,9(交换1次)
第二轮:7,10,8,9->(iTemp=8)7,10,8,9->(iTemp=8)7,8,10,9(交换1次)
第一轮:7,8,10,9->(iTemp=9)7,8,9,10(交换1次)
循环次数:6次
交换次数:3次
遗憾的是算法需要的循环次数依然是1/2*(n-1)*n。所以 算法复杂度为O(n*n)。
我们来看他的交换。由于每次外层循环只产生一次交换(只有一个最小值)。所以f(n)<=n
所以我们有f(n)=O(n)。所以,在数据较乱的时候,可以减少一定的交换次数。

插入法

插入法较为复杂,它的基本工作原理是抽出牌,在前面的牌中寻找相应的位置插入,然后继续下一张
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream.h>
voidInsertSort( int *pData,intCount)
{
intiTemp;
intiPos;
for (inti=1;i<Count;i++)
{
iTemp=pData[i]; //保存要插入的数
iPos=i-1; //被插入的数组数字个数
while ((iPos>=0)&&(iTemp<pData[iPos]))
{ //从最后一个(最大数字)开始对比,大于它的数字往后移位
pData[iPos+1]=pData[iPos];
iPos--;
}
pData[iPos+1]=iTemp; //插入数字的位置
}
}
voidmain(){
intdata[]={10,9,8,7,6,5,4};
InsertSort(data,7);
for (inti=0;i<7;i++)
cout<<data<< "" ;
cout<< "\n" ;
}
其他:
第一轮:8,10,7,9->8,10,7,9(交换0次)(循环1次)
第二轮:9,10,8,7->8,9,10,7(交换1次)(循环2次)
第一轮:8,9,10,7->7,8,9,10(交换1次)(循环3次)
循环次数:6次
交换次数:3次
其他:
第一轮:8,10,7,9->8,10,7,9(交换0次)(循环1次)
第二轮:8,10,7,9->7,8,10,9(交换1次)(循环2次)
第一轮:7,8,10,9->7,8,9,10(交换1次)(循环1次)
循环次数:4次
交换次数:2次
上面结尾的行为分析事实上造成了一种假象,让我们认为这种算法是简单算法中 最好的,其实不是,
因为其循环次数虽然并不固定,我们仍可以使用O方法。从上面的结果可以看出,循环的次数f(n)<=
1/2*n*(n-1)<=1/2*n*n。所以其 复杂度仍为O(n*n)(这里说明一下,其实如果不是为了展示这些简单
排序的不同,交换次数仍然可以这样推导)。现在看交换,从外观上看,交换次数是O(n)(推导类似
选择法),但我们每次要进行与内层循环相同次数的‘=’操作。正常的一次交换我们需要三次‘=’
而这里显然多了一些,所以我们浪费了时间。
最终,我个人认为,在 简单排序算法中, 选择法最好的。

高级排序算法

高级 排序算法中我们将只介绍这一种,同时也是目前我所知道(我看过的资料中)的最快的。
它的工作看起来仍然象一个 二叉树。首先我们选择一个中间值middle程序中我们使用 数组中间值,然后
把比它小的放在左边,大的放在右边(具体的实现是从两边找,找到一对后交换)。然后对两边分别使
用这个过程(最容易的方法—— 递归)。
1. 快速排序://这段代码编译可以通过,一运行就出错,内部的细节有些问题,我还没找到解决方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<iostream.h>
voidrun( int *pData,intleft,intright)
{
inti,j;
intmiddle,iTemp;
i=left;
j=right;
middle=pData[left];
do {
while ((pData[i]<middle)&&(i<right)) //从左扫描大于中值的数
i++;
while ((pData[j]>middle)&&(j>left)) //从右扫描大于中值的数
j--;
if (i<=j) //找到了一对值
{
//交换
iTemp=pData[i];
pData[i]=pData[j];
pData[j]=iTemp;
i++;
j--;
}
} while (i<=j); //如果两边扫描的下标交错,就停止(完成一次)
//当左边部分有值(left<j),递归左半边
if (left<j)
run(pData,left,j);
//当右边部分有值(right>i),递归右半边
if (right>i)
run(pData,i,right);
}
voidQuickSort( int *pData,intCount)
{
run(pData,0,Count-1);
}
voidmain()
{
intdata[]={10,9,8,7,6,5,4};
QuickSort(data,7);
for (inti=0;i<7;i++)
cout<<data[i]<< "" ; //原作者此处代码有误,输出因为date[i],date数组名输出的是地址
cout<< "\n" ;
}
这里我没有给出行为的分析,因为这个很简单,我们直接来分析算法:首先我们考虑最理想的情况
1. 数组的大小是2的幂,这样分下去始终可以被2整除。假设为2的k次方,即k=log2(n)。
2.每次我们选择的值刚好是中间值,这样, 数组才可以被等分。
第一层递归,循环n次,第二层循环2*(n/2)......
所以共有n+2(n/2)+4(n/4)+...+n*(n/n) = n+n+n+...+n=k*n=log2(n)*n
所以 算法复杂度为O(log2(n)*n)
其他的情况只会比这种情况差,最差的情况是每次选择到的middle都是最小值或最大值,那么他将变
成交换法(由于使用了 递归,情况更糟)。但是你认为这种情况发生的几率有多大??呵呵,你完全
不必担心这个问题。实践证明,大多数的情况, 快速排序总是 最好的。
如果你担心这个问题,你可以使用 堆排序,这是一种稳定的O(log2(n)*n)算法,但是通常情况下速度要慢
快速排序(因为要重组堆)。

其他排序

双向冒泡
通常的冒泡是单向的,而这里是双向的,也就是说还要进行反向的工作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<iostream.h>
inlinevoidexchange( int *a, int *b)
{
inttemp;
temp=*a;
*a=*b;
*b=temp;
}
voidbubblesort( int *array,intnum)
{
inti,j,k,flag=0;
for (i=0;i<num;i++){
printf ( "%d" ,array[i]);
}
printf ( "\n" );
for (i=0;i<num;i++){ //所有数的个数为num个
flag=0;
for (j=i;j<num-i-1;j++){ //每循环一次最底端的数的顺序都会排好,所以初始时j=i;
if (array[j]>array[j+1]){
exchange(&array[j],&array[j+1]);
flag=1;
}
}
for (k=num-1-i-1;k>i;k--){ //每循环一次最顶端的数据的顺序也会排好,所以初始时k=num-i-2
if (array[k]<array[k-1]){
exchange(&array[k],&array[k-1]);
flag=1;
}
}
if (flag==0){ //如果flag未发生改变则说明未发生数据交换,则排序完成
return ;
}
}
}
 
voidmain()
{
intdata[]={10,9,8,7,6,5,4,3,2,1,-10,-1};
bubblesort(data,12);
for (inti=0;i<12;i++)
cout<<data<< "" ;
cout<< "\n" ;
}

通用排序(模板)

这个程序我想就没有分析的必要了,大家看一下就可以了。不明白可以在论坛上问。
MyData.h文件
///
class CMyData
{
public:
CMyData(int Index,char* strData);
CMyData();
virtual ~CMyData();
int m_iIndex;
int GetDataSize(){ return m_iDataSize; };
const char* GetData(){ return m_strDatamember; };
//这里重载了操作符:
CMyData& operator =(CMyData &SrcData);
bool operator <(CMyData& data );
bool operator >(CMyData& data );
private:
char* m_strDatamember;
int m_iDataSize;
};
MyData.cpp文件
CMyData::CMyData():
m_iIndex(0),
m_iDataSize(0),
m_strDatamember(NULL)
{
}
CMyData::~CMyData()
{
if(m_strDatamember != NULL)
delete[] m_strDatamember;
m_strDatamember = NULL;
}
CMyData::CMyData(int Index,char* strData):
m_iIndex(Index),
m_iDataSize(0),
m_strDatamember(NULL)
{
m_iDataSize = strlen(strData);
m_strDatamember = new char[m_iDataSize+1];
strcpy(m_strDatamember,strData);
}
CMyData& CMyData::operator =(CMyData &SrcData)
{
m_iIndex = SrcData.m_iIndex;
m_iDataSize = SrcData.GetDataSize();
m_strDatamember = new char[m_iDataSize+1];
strcpy(m_strDatamember,SrcData.GetData());
return *this;
}
bool CMyData::operator <(CMyData& data )
{
return m_iIndex<data.m_iIndex;
}
bool CMyData::operator >(CMyData& data )
{
return m_iIndex>data.m_iIndex;
}
///
//
//主程序部分
#include <iostream.h>
#include "MyData.h"
template <class T>
void run(T* pData,int left,int right)
{
int i,j;
T middle,iTemp;
i = left;
j = right;
//下面的比较都调用我们 重载的操作符函数
middle = pData[(left+right)/2]; //求中间值
do{
while((pData<middle) && (i<right))//从左扫描大于中值的数
i++;
while((pData[j]>middle) && (j>left))//从右扫描大于中值的数
j--;
if(i<=j)//找到了一对值
{
//交换
iTemp = pData;
pData = pData[j];
pData[j] = iTemp;
i++;
j--;
}
}while(i<=j);//如果两边扫描的下标交错,就停止(完成一次)
//当左边部分有值(left<j),递归左半边
if(left<j)
run(pData,left,j);
//当右边部分有值(right>i),递归右半边
if(right>i)
run(pData,i,right);
}
template <class T>
void QuickSort(T* pData,int Count)
{
run(pData,0,Count-1);
}
void main()
{
CMyData data[] = {
CMyData(8,"xulion"),
CMyData(7,"sanzoo"),
CMyData(6,"wangjun"),
CMyData(5,"VCKBASE"),
CMyData(4,"jacky2000"),
CMyData(3,"cwally"),
CMyData(2,"VCUSER"),
CMyData(1,"isdong")
};
QuickSort(data,8);
for (int i=0;i<8;i++)
cout<<data.m_iIndex<<" "<<data.GetData()<<"\n";
cout<<"\n";
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值