经典排序算法

各种排序算法

1、经典排序算法 - 快速排序Quick sort

原理,通过一趟扫描将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

举个例子

如无序数组[6 2 4 1 5 9]

a),先把第一项[6]取出来,

用[6]依次与其余项进行比较,

如果比[6]小就放[6]前边,2 4 1 5都比[6]小,所以全部放到[6]前边

如果比[6]大就放[6]后边,9比[6]大,放到[6]后边,//6出列后大喝一声,比我小的站前边,比我大的站后边,行动吧!霸气十足~

一趟排完后变成下边这样:

排序前 6 2 4 1 5 9

排序后 2 4 1 5 6 9


b),对前半拉[2 4 1 5]继续进行快速排序

重复步骤a)后变成下边这样:

排序前 2 4 1 5

排序后 1 2 4 5

前半拉排序完成,总的排序也完成:

排序前:[6 2 4 1 5 9]

排序后:[1 2 4 5 6 9]

 

2、经典排序算法 - 桶排序Bucket sort

补充说明三点

1,桶排序是稳定的

2,桶排序是常见排序里最快的一种,比快排还要快…大多数情况下

3,桶排序非常快,但是同时也非常耗空间,基本上是最耗空间的一种排序算法

我自己的理解哈,可能与网上说的有一些出入,大体都是同样的原理

无序数组有个要求,就是成员隶属于固定(有限的)的区间,如范围为[0-9](考试分数为1-100等)

例如待排数字[6 2 4 1 5 9]

准备10个空桶,最大数个空桶

[6 2 4 1 5 9]           待排数组

[0 0 0 0 0 0 0 0 0 0]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

1,顺序从待排数组中取出数字,首先6被取出,然后把6入6号桶,这个过程类似这样:空桶[ 待排数组[ 0 ] ] = 待排数组[ 0 ]

[6 2 4 1 5 9]           待排数组

[0 0 0 0 0 0 6 0 0 0]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

2,顺序从待排数组中取出下一个数字,此时2被取出,将其放入2号桶,是几就放几号桶

[6 2 4 1 5 9]           待排数组

[0 0 2 0 0 0 6 0 0 0]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

3,4,5,6省略,过程一样,全部入桶后变成下边这样

[6 2 4 1 5 9]           待排数组

[0 1 2 0 4 5 6 0 0 9]   空桶

[0 1 2 3 4 5 6 7 8 9]   桶编号(实际不存在)

 

0表示空桶,跳过,顺序取出即可:1 2 4 5 6 9

3、经典排序算法 – 插入排序Insertion sort 
插入排序就是每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕。

4、经典排序算法 - 基数排序Radix sort

原理类似桶排序,这里总是需要10个桶,多次使用

首先以个位数的值进行装桶,即个位数为1则放入1号桶,为9则放入9号桶,暂时忽视十位数

例如

待排序数组[62,14,59,88,16]简单点五个数字

分配10个桶,桶编号为0-9,以个位数数字为桶编号依次入桶,变成下边这样

|  0  |  0  | 62 |  0  | 14 |  0  | 16 |  0  |  88 | 59 |

|  0  |  1  |  2  |  3  |  4 |  5  |  6  |  7  |  8  |  9  |桶编号

将桶里的数字顺序取出来,

输出结果:[62,14,16,88,59]

再次入桶,不过这次以十位数的数字为准,进入相应的桶,变成下边这样:

由于前边做了个位数的排序,所以当十位数相等时,个位数字是由小到大的顺序入桶的,就是说,入完桶还是有序

|  0  | 14,16 |  0  |  0  |  0  | 59 | 62  | 0  | 88  |  0  |

|  0  |  1      |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |桶编号


因为没有大过100的数字,没有百位数,所以到这排序完毕,顺序取出即可

最后输出结果:[14,16,59,62,88]

5、经典排序算法 - 鸽巢排序Pigeonhole sort

原理类似桶排序,同样需要一个很大的鸽巢[桶排序里管这个叫桶,名字无所谓了]

鸽巢其实就是数组啦,数组的索引位置就表示值,该索引位置的值表示出现次数,如果全部为1次或0次那就是桶排序

例如

var pigeonHole = new int[100];

pigeonHole[0]的值表示0的出现次数...

pigeonHole[1]的值表示1的出现次数...

pigeonHole[2]的值表示2的出现次数...

举例

var pigeonHole = new int[10];//10个位置

var A = new int[] { 6, 6, 2, 2, 2, 4, 1, 1, 1, 5, 5, 5, 5, 9 };//待排序数组

foreach (var item in A)

            {

                pigeonHole[item]++;//如pigeonHole[6]++会执行两次,结果为2

            }

鸽巢索引:0 1 2 3 4 5 6 7 8 9

索引次数:0 3 3 0 1 4 2 0 0 1

//顺序输出

for (int i = 0; i < pigeonHole.Length; i++)//索引i就是值

            {

//pigeonHole[i]处的值是数字i出现的次数

//如6出现了两次,那么pigeonHole[6] = 2

//0没出现,那么pigeonHole[0] = 0,表示待排数组里没有这个值:0

for (int j = 0; j < pigeonHole[i]; j++)//i出现就次就输出几个

                {

Console.WriteLine(i);//1,1,1,2,2,2,4,5,5,5,5,6,6,9

                }

            }

6、经典排序算法 - 归并排序Merge sort

原理,把原始数组分成若干子数组,对每一个子数组进行排序,

继续把子数组与子数组合并,合并后仍然有序,直到全部合并完,形成有序的数组

举例

无序数组[6 2 4 1 5 9]

先看一下每个步骤下的状态,完了再看合并细节

第一步 [6 2 4 1 5 9]原始状态

第二步 [2 6] [1 4] [5 9]两两合并排序,排序细节后边介绍

第三步 [1 2 4 6] [5 9]继续两组两组合并

第四步 [1 2 4 5 6 9]合并完毕,排序完毕

输出结果[1 2 4 5 6 9]

合并细节

详细介绍第二步到第三步的过程,其余类似

第二步:[2 6] [1 4] [5 9]

两两合并,其实仅合并[2 6] [1 4],所以[5 9]不管它,

原始状态

第一个数组[2 6]

第二个数组[1 4]

--------------------

第三个数组[...]

 

第1步,顺序从第一,第二个数组里取出一个数字:2和1

比较大小后将小的放入第三个数组,此时变成下边这样

第一个数组[2 6]

第二个数组[4]

--------------------

第三个数组[1]

 

第2步,继续刚才的步骤,顺序从第一,第二个数组里取数据,2和4,

同样的比较大小后将小的放入第三个数组,此时状态如下

第一个数组[6]

第二个数组[4]

--------------------

第三个数组[1 2]

 

第3步,再重复前边的步骤变成,将较小的4放入第三个数组后变成如下状态

第一个数组[6]

第二个数组[...]

--------------------

第三个数组[1 2 4]

 

第4步,最后将6放入,排序完毕

第一个数组[...]

第二个数组[...]

--------------------

第三个数组[1 2 4 6]

 

[ 1 2 4 6 ]与[ 5 9 ]的合并过程与上边一样,不再分解

7、经典排序算法 - 冒泡排序Bubble sort

原理是临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换,

这样一趟过去后,最大或最小的数字被交换到了最后一位,

然后再从头开始进行两两比较交换,直到倒数第二位时结束,其余类似看例子

例子为从小到大排序,

原始待排序数组| 6 | 2 | 4 | 1 | 5 | 9 |


第一趟排序(外循环)

第一次两两比较6 > 2交换(内循环)

交换前状态| 6 | 2 | 4 | 1 | 5 | 9 |

交换后状态| 2 | 6 | 4 | 1 | 5 | 9 |

 

第二次两两比较,6 > 4交换

交换前状态| 2 | 6 | 4 | 1 | 5 | 9 |

交换后状态| 2 | 4 | 6 | 1 | 5 | 9 |


第三次两两比较,6 > 1交换

交换前状态| 2 | 4 | 6 | 1 | 5 | 9 |

交换后状态| 2 | 4 | 1 | 6 | 5 | 9 |


第四次两两比较,6 > 5交换

交换前状态| 2 | 4 | 1 | 6 | 5 | 9 |

交换后状态| 2 | 4 | 1 | 5 | 6 | 9 |


第五次两两比较,6 < 9不交换

交换前状态| 2 | 4 | 1 | 5 | 6 | 9 |

交换后状态| 2 | 4 | 1 | 5 | 6 | 9 |

 

第二趟排序(外循环)

第一次两两比较2 < 4不交换

交换前状态| 2 | 4 | 1 | 5 | 6 | 9 |

交换后状态| 2 | 4 | 1 | 5 | 6 | 9 |

 

第二次两两比较,4 > 1交换

交换前状态| 2 | 4 | 1 | 5 | 6 | 9 |
交换后状态| 2 | 1 | 4 | 5 | 6 | 9 |

 

第三次两两比较,4 < 5不交换

交换前状态| 2 | 1 | 4 | 5 | 6 | 9 |
交换后状态| 2 | 1 | 4 | 5 | 6 | 9 |

 

第四次两两比较,5 < 6不交换

交换前状态| 2 | 1 | 4 | 5 | 6 | 9 |

交换后状态| 2 | 1 | 4 | 5 | 6 | 9 |


第三趟排序(外循环)

第一次两两比较2 > 1交换

交换后状态| 2 | 1 | 4 | 5 | 6 | 9 |

交换后状态| 1 | 2 | 4 | 5 | 6 | 9 |

 

第二次两两比较,2 < 4不交换

交换后状态| 1 | 2 | 4 | 5 | 6 | 9 |
交换后状态| 1 | 2 | 4 | 5 | 6 | 9 |

 

第三次两两比较,4 < 5不交换

交换后状态| 1 | 2 | 4 | 5 | 6 | 9 |
交换后状态| 1 | 2 | 4 | 5 | 6 | 9 |


第四趟排序(外循环)无交换

第五趟排序(外循环)无交换


排序完毕,输出最终结果1 2 4 5 6 9

8、经典排序算法 - 选择排序Selection sort

顾名思意,就是直接从待排序数组里选择一个最小(或最大)的数字,每次都拿一个最小数字出来,

顺序放入新数组,直到全部拿完

再简单点,对着一群数组说,你们谁最小出列,站到最后边

然后继续对剩余的无序数组说,你们谁最小出列,站到最后边

再继续刚才的操作,一直到最后一个,继续站到最后边,现在数组有序了,从小到大

举例

先说看每步的状态变化,后边介绍细节,现有无序数组[6 2 4 1 5 9]

第一趟找到最小数1,放到最前边(与首位数字交换)

交换前:| 6 | 2 | 4 | 1 | 5 | 9 |

交换后:| 1 | 2 | 4 | 6 | 5 | 9 |

第二趟找到余下数字[2 4 6 5 9]里的最小数2,与当前数组的首位数字进行交换,实际没有交换,本来就在首位

交换前:| 1 | 2 | 4 | 6 | 5 | 9 |

交换后:| 1 | 2 | 4 | 6 | 5 | 9 |


第三趟继续找到剩余[4 6 5 9]数字里的最小数4,实际没有交换,4待首位置无须交换

第四趟从剩余的[6 5 9]里找到最小数5,与首位数字6交换位置

交换前:| 1 | 2 | 4 | 6 | 5 | 9 |

交换后:| 1 | 2 | 4 | 5 | 6 | 9 |

第五趟从剩余的[6 9]里找到最小数6,发现它待在正确的位置,没有交换

排序完毕输出正确结果[1 2 4 5 6 9]

第一趟找到最小数1的细节

当前数组是| 6 | 2 | 4 | 1 | 5 | 9 |

先把6取出来,让它扮演最小数

当前最小数6与其它数一一进行比较,发现更小数就交换角色

当前最小数6与2比较,发现更小数,交换角色,此时最小数是2,接下来2与剩余数字比较

当前最小数2与4比较,不动

当前最小数2与1比较,发现更小数,交换角色,此时最小数是1,接下来1与剩余数字比较

当前最小数1与5比较,不动

当前最小数1与9比较,不动,到达末尾

当前最小数1与当前首位数字进行位置交换,如下所示

交换前:| 6 | 2 | 4 | 1 | 5 | 9 |

交换后:| 1 | 2 | 4 | 6 | 5 | 9 |

完成一趟排序,其余步骤类似

 

9、经典排序算法 - 鸡尾酒排序Cocktail sort

鸡尾酒排序基于冒泡排序,双向循环

还是看例子吧,给定待排数组[2 3 4 5 1]

第一趟过去时的每一步

第一步迭代,2 < 3不换

[2 3 4 5 1]

 

第二步迭代,3 < 4不换

[2 3 4 5 1]

 

第三步迭代,4 < 5不换

[2 3 4 5 1]

 

第四步迭代,5 > 1交换

[2 3 4 1 5]

 

第一趟回来时的第一步,鸡尾酒一次到头后就回返回来,再到头后再过去,来回比,一个来回能排两个数字

第五步迭代,1 < 5不交换

[2 3 4 1 5]

 

第六步迭代,1 < 4交换

[2 3 1 4 5]

 

第七步迭代,1 < 3交换

[2 1 3 4 5]

 

第八步迭代,2 > 1交换

[1 2 3 4 5]

 

排序完毕,顺序输出结果即可得[ 1 2 3 4 5]

 

如何判断排序结束了?

假如一趟来回没有交换任何数字,则表示该数组已经有序了,可以设置了个变量表示有没有交换过

10、经典排序算法 - 希尔排序Shell sort

希尔排序Shell Sort是基于插入排序的一种改进,同样分成两部分,

第一部分,希尔排序介绍

第二部分,如何选取关键字,选取关键字是希尔排序的关键

第一块希尔排序介绍

准备待排数组[6 2 4 1 5 9]

首先需要选取关键字,例如关键是3和1(第一步分成三组,第二步分成一组),那么待排数组分成了以下三个虚拟组:

[6 1]一组

[2 5]二组

[4 9]三组

看仔细啊,不是临近的两个数字分组,而是3(分成了三组)的倍数的数字分成了一组,

就是每隔3个数取一个,每隔三个再取一个,这样取出来的数字放到一组,

把它们当成一组,但不实际分组,只是当成一组来看,所以上边的"组"实际上并不存在,只是为了说明分组关系

对以上三组分别进行插入排序变成下边这样

[1 6] [2 5] [4 9]

具体过程:

[6 1]6和1交换变成[1 6]

[2 5]2与5不动还是[2 5]

[4 9]4与9不动还是[4 9]

第一趟排序状态演示:

待排数组:[6 2 4 1 5 9]

排后数组:[1 2 4 6 5 9]

第二趟关键字取的是1,即每隔一个取一个组成新数组,实际上就是只有一组啦,隔一取一就全部取出来了嘛

此时待排数组为:[1 2 4 6 5 9]

直接对它进行插入排序

还记得插入排序怎么排不?复习一下

[1 2 4]都不用动,过程省略,到5的时候,将5取出,在前边的有序数组里找到适合它的位置插入,就是4后边,6前边

后边的也不用改,所以排序完毕

顺序输出结果:[1 2 4 5 6 9]

第二块希尔排序的关键是如何取关键字,因为其它内容与插入排序一样

那么如何选取关键字呢?就是分成三组,一组,这个分组的依据是什么呢?为什么不是二组,六组或者其它组嘞?

好的增量序列的共同特征:

① 最后一个增量必须为1

② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况

参见 http://baike.baidu.com/view/2217047.htm

这么关键的问题竟然没有一个公式,只给出了两个判定标准

好吧,一般10个待排数字的话,关键依次选取5 3 1即可,其它的情况只能自己判断了,然后看是否符合上述两条"好"的标准

就是说,这个关键的选择是没有规定的,怎么选都可以,仅一条,关键字要越来越小,直到1为止

 

后补:

增量的取值规则为第一次取总长度的一半,第二次取一半的一半,依次累推直到1为止,刚从下文中看到的这一段描述,感谢!

 

11、堆与堆排序


 

1.什么是堆

这里的堆(二叉堆),指得不是堆栈的那个堆,而是一种数据结构。

堆可以视为一棵完全的二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。

如下图,是一个堆和数组的相互关系

二叉堆一般分为两种:最大堆和最小堆。两种堆内部的数据都要满足自己的特点。

比如最大堆的特点是,每个父节点的元素值都不小于其孩子结点(如果存在)的元素值,因此,最大堆的最大元素值出现在根结点(堆顶)

最小堆的性质与最大堆恰好相反

由于堆排序算法使用的是最大堆,所以我们这里以最大堆为例,最小堆情况类似,可以自己推导

对于给定的某个结点的下标i,可以很容易的计算出这个结点的父结点、孩子结点的下标,而且计算公式很漂亮很简约

但是这里有一个很大的问题:目前主流的编程语言中,数组都是Zero-based,这就意味着我们的堆数据结构模型要发生改变

相应的,几个计算公式也要作出相应调整

新公式很难看,很杯具

这几个公式在C/C++中可以用宏或者内联函数实现

1 #define LEFT(x) ((x << 1) + 1)
2 #define RIGHT(x) ((x + 1) << 1)
3 #define PARENT(x) (((x + 1) >> 1) - 1)

.堆排序

堆排序是一种利用堆这种数据结构,进行原地排序的排序算法,其时间复杂度是O(nlogn),而且只和数据规模有关

堆排序算法是一种很漂亮的算法,这里需要用到三个函数:MaxHeapify、BuildMaxHeap和HeapSort

2.1MaxHeapify

MaxHeapify的作用是保持最大堆的性质,是整个排序算法的核心。

MaxHeapify函数接受三个参数,数组,检查的起始下标和堆大小。函数的代码如下

01 /*

02     输  入: Ary(int[]) - [in,out]排序数组
03             nIndex(int) - 起始下标
04             nHeapSize(int) - 堆大小(zero-based)
05     输  出: -
06     功  能: 从nIndex开始检查并保持最大堆性质
07 */
08 voidMaxHeapify(intAry[],intnIndex,intnHeapSize)
09 {
10     intnL = LEFT(nIndex);
11     intnR = RIGHT(nIndex);
12     intnLargest;
13   
14     if(nL <= nHeapSize && Ary[nIndex] < Ary[nL])
15     {
16         nLargest = nL;
17     }
18     else
19     {
20         nLargest = nIndex;
21     }
22   
23     if(nR <= nHeapSize && Ary[nLargest] < Ary[nR])
24     {
25         nLargest = nR;
26     }
27   
28     if(nLargest != nIndex)
29     {
30         // 调整后可能仍然违反堆性质
31         Swap(Ary[nLargest], Ary[nIndex]);
32         MaxHeapify(Ary, nLargest, nHeapSize);
33     }
34 }
 

由于一次调整后,堆仍然违反堆性质,所以需要递归的测试,使得整个堆都满足堆性质

MaxHeapify(A,1,9)作用过程如图所示

对于有n个元素的堆来说,MaxHeapify的运行时间最坏情况是O(logn)(可以通过主定理的得到)。而在事实上,这个复杂度和堆的高度成正比。我们可以证明,一个大小为n的最大堆,他的高度是lowerbound(logn)

MaxHeapify很简洁漂亮,但是由于递归的调用可能是某些编译器产生“比较烂”的代码。

通常来说,递归主要用在分治法中,而这里并不需要分治。而且递归调用需要压栈/清栈,和迭代相比,性能上有略微的劣势。当然,按照20/80法则,这是可以忽略的。但是如果你觉得用递归会让自己心里过不去的话,也可以用迭代,比如下面酱紫

01 /*

02     输  入: Ary(int[]) - [in,out]排序数组
03             nIndex(int) - 起始下标
04             nHeapSize(int) - 堆大小
05     输  出: -
06     功  能: 从nIndex开始检查并保持最大堆性质
07 */
08 voidMaxHeapify(intAry[],intnIndex,intnHeapSize)
09 {
10     while(true)
11     {
12         intnL = LEFT(nIndex);
13         intnR = RIGHT(nIndex);
14         intnLargest;
15   
16         if(nL <= nHeapSize && Ary[nIndex] < Ary[nL])
17         {
18             nLargest = nL;
19         }
20         else
21         {
22             nLargest = nIndex;
23         }
24   
25         if(nR <= nHeapSize && Ary[nLargest] < Ary[nR])
26         {
27             nLargest = nR;
28         }
29   
30         if(nLargest != nIndex)
31         {
32             // 调整后可能仍然违反堆性质
33             Swap(Ary[nLargest], Ary[nIndex]);
34             nIndex = nLargest;
35         }
36         else
37         {
38             break;
39         }
40     }
41 }

显然没有上个版本的漂亮- -

 

2.2BuildMaxHeap

BuildMaxHeap的作用是将一个数组改造成一个最大堆,接受数组和堆大小两个参数

BuildMaxHeap中自下而上的调用MaxHeapify来改造数组,建立最大堆。因为MaxHeapify能够保证下标i的结点之后结点都满足最大堆的性质,所以自下而上的调用MaxHeapify能够在改造过程中保持这一性质。

如果最大堆的数量元素是n,那么BuildMaxHeap从PARENT(n)开始,往上依次调用MaxHeapify。

这基于一个定理:如果最大堆有n个元素,那么从PARENT(n)+1,PARENT(n)+2…n都是叶子结点(叶子结点指没有儿子结点的结点)

BuildMaxHeap的代码如下:

01 /*
02     输  入: Ary(int[]) - [in,out]排序数组
03             nHeapSize(int) - [in]堆大小(zero-based)
04     输  出: -
05     功  能: 将一个数组改造为最大堆
06 */
07 voidBuildMaxHeap(intAry[],intnHeapSize)
08 {
09     for(inti = PARENT(nHeapSize); i >= 0; --i)
10     {
11         MaxHeapify(Ary, i, nHeapSize);
12     }
13 }

由于MaxHeapify的最坏情况是O(logn),所以BuildMaxHeap的最坏情况是O(nlogn),虽然这个复杂度是正确的(O给出复杂度的上界),但是不够精确。

事实上,可以利用数学分析证明,BuildMaxHeap的期望复杂度是O(n)

而且,如果对一个递减排列的数组来说,MaxHeapify的复杂度是O(1),BuildMaxHeap的复杂度也达到最优的O(n),cos一个递减排列的数组本身满足最大堆

2.3HeapSort

HeapSort是堆排序的接口算法,接受数组和元素个数两个参数

HeapSort先调用BuildMaxHeap将数组改造为最大堆,然后将堆顶和堆底元素交换,之后将底部上升,最后重新调用MaxHeapify保持最大堆性质。

由于堆顶元素必然是堆中最大的元素,所以一次操作之后,堆中存在的最大元素被分离出堆

重复n-1次之后,数组排列完毕。代码如下

01 /*
02     输  入: Ary(int[]) - [in,out]排序数组
03             nCount(int) - [in]元素个数
04     输  出: -
05     功  能: 对一个数组进行堆排序
06 */
07 voidHeapSort(intAry[],intnCount)
08 {
09     intnHeapSize = nCount - 1;
10   
11     BuildMaxHeap(Ary, nHeapSize);
12   
13     for(inti = nHeapSize; i >= 1; --i)
14     {
15         Swap(Ary[0], Ary[i]);
16         --nHeapSize;
17         MaxHeapify(Ary, 0, nHeapSize);
18     }
19 }

排序的过程如图所示

然BuildMaxHeap对于不同的初始数据排列所需要的时间不同,但是这并不影响HeapSort的总体时间复杂度

堆作为数据结构,除了用于堆排序之外,更常见的用途是建立优先级队列。

由于最大/最小元素出现在堆根本,所以很容易确定队列元素的优先级。这也是堆最频繁的用途

 

 

转载于:https://my.oschina.net/ashan2012/blog/106880

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值