九大排序实现与比较(万字总结)

目录

插入排序:

直接插入排序

希尔排序

选择排序:

选择排序

堆排序

交换排序:

冒泡排序

快速排序

       三数取中优化:

        小区间优化:

        快速排序非递归写法        

归并排序:

归并排序

        归并排序的非递归写法

非比较排序:

计数排序

基数排序


总结下九大排序(升序排序)

常见排序算法:

插入排序:直接插入排序,希尔排序

选择排序:选择排序,堆排序

交换排序:冒泡排序,快速排序

归并排序:归并排序

非比较排序: 计数排序,基数排序

插入排序:

直接插入排序

思想:将待排序的数逐个插入到有序区间相应的位置。

实现:升序

单趟:插入一个值为key的数,将大于key的数往后移一位,将key放入空位中。

例如将2插入有序区间1,5,7,8,9

#include<stdio.h>
void InsertSort(int *a,int n) {
	int i = 5;
	int key = a[i];
	int j = i - 1;
	for (; j >= 0; j--) {
		if (key < a[j])
			a[j + 1] = a[j];
		else
			break;
	}
	a[j+1] = key;
}
int main() {
	int a[10] = {1,5,7,8,9, 2,4,10,3,6};
	//int a[10] = { 7,8,6,9,3,4,5,10,1,2 };
	int n = sizeof(a) / sizeof(a[0]);
	for (int i = 0; i < n; i++) printf("%d ", a[i]);
	printf("\n");
	InsertSort(a, n);
	for (int i = 0; i < n; i++) printf("%d ", a[i]);
	printf("\n");
	return 0;
}

整体:从下标为1的数开始往前面的区间插入,直到n-1。

void InsertSort(int *a,int n) {
	for (int i = 1; i < n; i++) {
		int key = a[i];
		int j = i - 1;
		for (; j >= 0; j--) {
			if (key < a[j])
				a[j + 1] = a[j];
			else
				break;
		}
		a[j+1] = key;
	}
}

在最坏的情况下(逆序),下标为i的元素,需要移动i次,总的移动次数为1+2+3+...+n-1=n*(n-1)/2次,故时间复杂度为O(N^2),空间复杂度为O(1),若数据接近有序则总的移动次数将减少。插入时,相同的数可以在插入时不发生后移,从而保持原始顺序,故是稳定的排序。

希尔排序

插入排序在数组接近有序时效率很高,希尔将数据分组预排,从而让数据尽可能接近有序,从而优化了插入排序。

例如将一组数据分成,下标差距为2一组,如下:

 对组一进行插入排序:

void ShellSort1(int* a, int n) {
	int i = 2;
	while (i < n) {
		int key = a[i];
		int j = i - 2;
		while (j>=0&&a[j] > key)
		{
			a[j + 2] = a[j];
			j -= 2;
		}
		a[j + 2] = key;
		i += 2;
	}
}

 对组二进行插入排序:

void ShellSort2(int* a, int n) {
	int i = 3;//从第四个位置开始插入就行了
	while (i < n) {
		int key = a[i];
		int j = i - 2;
		while (j >= 0 && a[j] > key)
		{
			a[j + 2] = a[j];
			j -= 2;
		}
		a[j + 2] = key;
		i += 2;
	}
}

对整体进行插入排序,就能得到有序,这里的下标间隔称为gap,通常可取gap=n/3+1,希尔的时间复杂度大致为O(n^1.3),数组有序或接近有序时效率更高。

整体代码:

void ShellSort(int* a, int n) {
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = gap; i < n; i++) 
		{
			int key = a[i];
			int j = i - gap;
			while (j >= 0 && a[j] > key)
			{
				a[j + gap] = a[j];
				j -= gap;
			}
			a[j + gap] = key;
		}
	}
}

希尔排序进行了分组预排,这时相同的数可能被分到了不同组进行预排序,故该排序是不稳定排序,空间复杂度O(1)。

选择排序:

选择排序

思想:

例如升序:从所有数据中选出最小值,与头部元素交换,这样头部元素就有序了,在不断从剩下的数据中选出最小值,放在前面有序区间的后面(交换实现),因为在交换过程中可能打乱了原本相同数据的相对位置,故为不稳定的排序,选取最值的总次数n+n-1+n-2+n-3+...+ 2 =(n+2)*(n-1)/2,故时间复杂度为O(N^2),空间复杂度为O(1)。

实现:每次选出最小值

void SelectSort1(int *a,int n)
{
	for (int j = 0; j < n-1; j++) 
	{
		int i = j;                                                                       
		int mini = i;
		while (i < n)
		{
			if (a[i] < a[mini])
				mini = i;
			++i;
		}
		if(mini!=j)
			Swap(&a[mini], &a[j]);
	}
}

小优化:每次选出最小值和最大值

void SelectSort2(int* a, int n)
{
	for (int begin = 0,end=n-1; begin < end; begin++,end--)
	{
		int i = begin;
		int mini = i;
		int maxi = i;
		while (i <= end)
		{
			if (a[i] < a[mini])
				mini = i;
			if (a[i] > a[maxi])
				maxi = i;
			++i;
		}
		if (begin == maxi)//注意,首元素就是最大值的情况
			maxi = mini;//会与最小值交换位置,须更新maxi位置
		if (mini != begin)
			Swap(&a[mini], &a[begin]);
		if (maxi != end)
			Swap(&a[maxi], &a[end]);
	}
}

堆排序

利用堆数据结构进行排序的排序算法,首先建堆,然后排序。注意排升序建大堆,降序反之。

建堆的两种方法:

向上调整建堆:从下标为1的数开始,依次向上调整建堆。

代码实现:

void AdjustUp(int* a, int child)
{
	int parent = child / 2;
	while (child > 0 && a[parent] < a[child])
	{
		Swap(&a[parent], &a[child]);
		child = parent;
		parent = child / 2;
	}
}

 高度为h的堆,第i层,每层最多个数2^(i-1),每个节点最多调整次数(i-1),故总最多调整次数为:

又n=2^h-1

即向上调整的时间复杂度为O(N*logN)。

第二种建堆法为向下调整算法:

从数组后往前第一个非叶子节点开始向下调整建堆。

实现:

void AdjustDown(int* a, int parent,int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child])++child;
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else break;
	}
}

这个建堆法时间复杂度仅为O(N),推导:

 高度为h的堆,第i层,每层最多个数2^(i-1),每个节点最多调整次数(h-i),故总最多调整次数为:

 

 故时间复杂度为O(N)。

建完堆,将堆顶元素与最后一个元素依次交换,堆变小,堆顶元素向下调整形成新的堆,当最后两个元素交换调整完毕时,排序完成。

实现:

void HeapSort(int* a, int n)
{
	//向上调整建堆
	/*for (int i = 1; i < n; i++)
		AdjustUp(a, i);*/
	//向下调整建堆
	for (int i = (n-1) / 2; i >= 0; i--)
	{
		AdjustDown(a, i,n);
	}
	//排序
	for (int i = n-1; i > 0; i--)
	{
		Swap(&a[0], &a[i]);
		AdjustDown(a, 0, i);
	}
}

对n-1个元素交换,并向下调整建堆,可见时间复杂度为O(N*logN),因为在向上或向下调整算法中,无法保证相同元素为原来的次序,故堆排序是不稳定的排序。

交换排序:

        所谓交换排序就是根据数据大小比较,并进行交换,将值大的数据移动到后面,值小的数据移动到前面的排序。

冒泡排序

        一种简单的排序,每次将最大的数冒泡在数据尾部,从而维护尾部的有序区间。因为每趟都需要对待排序区间的数进行比较,故效率低,比较总次数为:n-1+n-2+n-3+...+2+1=n*(n-1)/2故时间复杂度为O(N^2),空间复杂度为O(1),交换的时候让相同的数据不发生交换就可保证其原来的次序,故冒泡排序是稳定的排序。

实现单趟冒泡:(将最大的数冒泡到了数据的尾部)

整体实现:

void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n - 1; j++) 
		for (int i = 0; i < n - 1 - j; i++)
			if (a[i] > a[i + 1])swap(a[i], a[i + 1]);
}

快速排序

Hoare于1960年发布了使他闻名于世的快速排序算法(Quick Sort),这个算法也是当前世界上使用最广泛的算法之一。

思想:取序列中某个数为基准值,将比基准值小的数放在基准值的左边,反之放在右边,然后递归基准值左边的子序列和右边的子序列重复上述过程,直到区间数据个数不大于1个即可。

基于这种思想诞生了许多版本:

hoare版本:

单趟快速排序,选择7为基准值,将比7小的数放在其左边,反之放在右边,可见如果有相同值的数,在交换的过程无法保证其原来的次序,故快速排序是不稳定的排序。

注意:当a[end]==key时也需--,避免左右找到都为key的值发生死循环。

 整体:

递归快速排序基准值7 左边和右边的区间,这样整体就有序了。

实现:

void QuickSort1(int* a, int begin,int end)
{
	if (begin >= end)return;
	int left = begin;
	int right = end;
	int key = a[begin];
	while (begin < end)
	{
		while (a[end] >= key && end > begin)--end;//选key在左边,得保证先让end先--,保证相遇值
		while (a[begin] <= key && end > begin)++begin;//小于等于key,保证基准值位置的正确性。
		if(begin!=end)swap(a[begin], a[end]);//用异或实现交换的话,相同位置会出错
	}
	if(left!=begin)swap(a[left],a[begin]);
	QuickSort1(a, left, begin - 1);
	QuickSort1(a, begin + 1, right);
}

挖坑法版本:(好理解的版本)

选择基准值key,其位置作为坑位,从右边往左找较小值填入坑位,并形成新的坑位,从左边往右找较大值填入坑位,再形成新的坑位,如此重复上述过程直到左右坐标相遇,填入key。

实现:

void QuickSort2(int *a,int begin,int end) 
{	
	if (begin >= end)return;
	int left = begin;
	int right = end;
	int key = a[begin];
	int piti = begin;
	while (left < right)
	{
		while (a[right] >= key && left < right)right--;
		a[piti] = a[right];
		piti = right;
		while (a[left] < key && left < right)left++;
		a[piti] = a[left];
		piti = left;
	}
	a[piti] = key;
	QuickSort2(a, begin, piti - 1);
	QuickSort2(a, piti + 1, end);
}

前后指针版本:(难理解一点的版本)

同样选出基准值key,用下标prev=begin,cur=begin+1开始迭代往后走,,cur位置的值小于key且++prev不等于cur就交换prev和cur位置的值,然后cur++,重复以上过程直到cur大于end,prev的位置就是key的正确位置,交换之。(可见cur和prev一直在一起,直到遇到比key大的值,他们就错开了,当cur=end+1,prev在比key值小的数的最后一个位置)

画图理解下:

实现:

void QuickSort3(int* a, int begin, int end)
{
	if (begin >= end)return;
	int prev = begin;
	int cur = begin + 1;
	while (cur <= end) 
	{
		if (a[cur]<a[begin] && ++prev != cur)
			swap(a[prev], a[cur]);
		++cur;
	}
	if(prev!=begin)swap(a[prev], a[begin]);
	QuickSort3(a, begin, prev - 1);
	QuickSort3(a, prev + 1, end);
}

       三数取中优化:

        倘若每次都将左右区间平均二分,则递归层数为logN,每层都要遍历N个数,故快速排序的时间复杂度最优为O(N^logN)。但是上述方法都是用递归实现,倘若数组有序或接近有序,则每次选择的基准值都是最值或接近最值,这样就使递归排序的左右区间长度差距很大,导致调用栈帧的层数大大增加,不仅效率降为O(N^2)(层数最多为N,求和得O(N^2)),而且还会引发栈溢出的问题。

        为此可以在选基准值上进行优化,每个区间选取左端点和中间数,右端点的中位数为基准值,这样倘若数组有序或接近有序,则左右区间能被二分或接近二分,从而使效率提高。

实现:

int GetMid(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;
	if (a[mid] > a[begin])
	{
		if (a[begin] > a[end])
			return begin;
		else//a[begin]=min
		{
			if (a[mid] > a[end])
				return end;
			else
				return mid;
		}	
	}
	else //a[mid]<=a[begin]
	{
		if (a[begin] < a[end])
			return begin;
		else//a[begin]=max
		{
			if (a[mid] > a[end])
				return mid;
			else
				return end;
		}	
	}
}
void QuickSort1(int* a, int begin,int end)
{
	if (begin >= end)return;
//三数取中优化
	int mid = GetMid(a, begin, end);
	if (mid != begin)swap(a[begin], a[mid]);

	int left = begin;
	int right = end;
	int key = a[begin];
	while (begin < end)
	{
		while (a[end] >= key && end > begin)--end;
		while (a[begin] <= key && end > begin)++begin;
		if(begin!=end)swap(a[begin], a[end]);
	}
	if(left!=begin)swap(a[left],a[begin]);
	QuickSort1(a, left, begin - 1);
	QuickSort1(a, begin + 1, right);
}

        小区间优化:

        当所分区间数据量小时,再去递归会大大增加递归调用次数,这时如果用小区间排序性能较优的排序(插入排序)直接排序,能减少许多递归调用次数,效率提高。

实现:

void QuickSort1(int* a, int begin,int end)
{
	if (begin >= end)return;
	//小区间优化
	if (end-begin < 9)
	{
		InsertSort(a+begin, end-begin + 1);
	}
	else
	{
		//三数取中优化
		int mid = GetMid(a, begin, end);
		if (mid != begin)swap(a[begin], a[mid]);

		int left = begin;
		int right = end;
		int key = a[begin];
		while (begin < end)
		{
			while (a[end] >= key && end > begin)--end;
			while (a[begin] <= key && end > begin)++begin;
			if (begin != end)swap(a[begin], a[end]);
		}
		if (left != begin)swap(a[left], a[begin]);
		QuickSort1(a, left, begin - 1);
		QuickSort1(a, begin + 1, right);
	}
}

        快速排序非递归写法        

        尽管经过了三数取中优化,但是在极端的情况下,每次选取的基准值也可能是次小值或接近次小值,这样在数据量大的情况下可能会导致栈溢出的问题,于是采用非递归写法就很有必要了。

        思想:

        可以用栈或者队列这样的数据结构去实现,例如栈,先将大区间入栈,出栈并单趟排序,得基准值下标,将左区间入栈,将右区间入栈,再重复上述过程直到栈为空为止(区间长度短时不入栈了,直接插入排序)。(注:这里用到的栈手撕一个即可)

int QuickSort3(int* a, int begin, int end)
{
	int mid = GetMid(a, begin, end);
	if (mid != begin)swap(a[mid], a[begin]);

	int prev = begin;
	int cur = begin + 1;
	while (cur <= end) 
	{
		if (a[cur]<a[begin] && ++prev != cur)
			swap(a[prev], a[cur]);
		++cur;
	}
	if(prev!=begin)swap(a[prev], a[begin]);
	return prev;
}
void QuickSortNonR(int* a, int begin, int end)
{
	if (begin >= end)return;
	Stack st;
	StackInit(&st);
	StackPush(&st, end);
	StackPush(&st, begin);
	while (!StackEmpty(&st))
	{
		int left = StackTop(&st);
		StackPop(&st);
		int right = StackTop(&st);
		StackPop(&st);
		if (right - left > 9)
		{
			int keyi = QuickSort3(a, left, right);
			if (keyi + 1 < right)
			{
				StackPush(&st, right);
				StackPush(&st, keyi + 1);
			}
			if (left < keyi - 1)
			{
				StackPush(&st, keyi - 1);
				StackPush(&st, left);
			}
		}
		else
		{
			InsertSort(a + left, right - left + 1);
		}
	}
	StackDestroy(&st);
}

归并排序:

归并排序

        将两小段有序区间合成一个大段有序区间就是归并排序的思想。将大区间二分至区间只有不多于一个值,认为是有序区间,然后归并排序即可。

示意图:

实现:

        归并时需要依靠额外的空间,将数据归并到临时数组tmp上,每次归并完将归并到tmp上的数据拷贝回原数组,最后一次归并后原数组就有序了。

void _MergeSort(int* a, int begin,int end,int *tmp)
{
	if (begin >= end)return;
	int mid = (begin + end) / 2;
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	_MergeSort(a, begin1, end1,tmp);
	_MergeSort(a, begin2, end2,tmp);
	int index = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])tmp[index++] = a[begin1++];
		else tmp[index++] = a[begin2++];
	}
	while (begin1 <= end1)tmp[index++] = a[begin1++];
	while (begin2 <= end2)tmp[index++] = a[begin2++];
	memcpy(a+begin, tmp+begin,sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}
	_MergeSort(a, 0, n - 1,tmp);
	free(tmp);
}

         相比于快速排序(像二叉树前序),归并排序(像二叉树的后序)所分区间是完全二分的,每层都归并N个数,有logN层,故时间复杂度为O(NlogN),空间复杂度为O(N),归并左右区间时相同的数可以按原来的次序归并到数组上,故归并排序是稳定的排序,因为归并时与数据有序与否无关,每层归并都完全二分,故归并排序最坏时间复杂度也为O(NlogN)。因为归并排序需要空间的开销,故更适合磁盘的的(在外存上)外排序,而内排序(在内存上)则是以综合性能最优的快速排序为主角。

        归并排序的非递归写法

        若第一次,直接从左到右归并区间长度为1的所有区间,第二次再归并区间长度为2的所有区间,每次归并是上次归并长度的两倍,直到区间长度大于等数组总长度的一半时,就是最后一次归并了。

实现:设区间长度为gap则,对左端点为i,归并的左右区间为[i,i+gap-1][i+gap,i+gap*2-1],对于左区间右端点和右区间越界的情况需要处理一下。

画图理解:

 代码:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += gap * 2)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + gap * 2 - 1;
			int index = i;
			if (end1 >= n)
			{
				end1 = n - 1;
				begin2 = n ;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				begin2 = n ;
				end2 = n - 1;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])tmp[index++] = a[begin1++];
				else tmp[index++] = a[begin2++];
			}
			while (begin1 <= end1)tmp[index++] = a[begin1++];
			while (begin2 <= end2)tmp[index++] = a[begin2++];
		}
		memcpy(a, tmp, sizeof(int) * n);
		gap *= 2;
	}
}

非比较排序:

以上的排序都是基于数据大小的比较来实现的排序,反之是非比较排序。

计数排序

        开辟一个(能存储所有情况,原数组最大值为max,最小值为min)range=(max-min+1)大小的数组统计数据出现的次数,再遍历统计的数组,输出到原数组,则原数组有序。若在统计时同时记录数据在原数组的位置,则在输出时相同数据按位置输出,则保证了相同数据本来的顺序,故是稳定的排序。

        若数据范围小而且集中,则开辟的空间小,效率高,但是适用范围有限。因为要遍历原数组和统计数组,故时间复杂度为O(max(N,range)),空间复杂度为O(range)。

实现:

void CountSort(int* a, int n)
{
	int min = a[0], max = a[0];
	for (int i = 0; i < n; ++i)
	{
		if (a[i] > max)max = a[i];
		if (a[i] < min)min = a[i];
	}
	int range = max - min + 1;
	int* tmp = (int*)calloc(range,sizeof(int));
	if (tmp == NULL) {
		printf("calloc fail");
		exit(-1);
	}
	for (int i = 0; i < n; ++i)tmp[a[i] - min]++;
	int index = 0;
	for (int i = 0; i < range; ++i)
	{
		while (tmp[i] > 0)
		{
			a[index++] = i+min;
			tmp[i]--;
		}
	}
	free(tmp);
}

基数排序

        可以采用LSD(Least significant digital)最低位优先法进行基数排序。

        例如:数组元素都是一位数,则按个位排序就可以得出数组的正确排序,若数组中元素既有一位数又有两位数,可以先按个位排序,这样一来一位数的数据都有序了,再按百位去排序,整体就有序了。具体操作是可以准备0-9的10个队列,将百位相同的数都进入一个队列中,依次出队,这样一位数的元素因为队列的性质就可以保证第一次按个位排序的结果不会被第二次影响了,可见相同的数也保证了其原来的排序,故是稳定排序。假设有k位数,因为要遍历k位数,每次都是全部遍历一遍,故时间复杂度为O(N*k)。(注:这里用到的队列手撕一个即可)

        实现:

#define N 1000000//若:数的范围是[0,N)
//数number从右往左第i位数=number/(10^(i-1)) %10
void RedixSort(int* a, int n)
{
	//10个队列 存储
	Queue Q[10];
	for (int i = 0; i < 10; i++)
	{
		QueueInit(&Q[i]);
	}
	//收发 各k次(最大数位数)  
	for (int i = 1; i < N; i *= 10)
	{
		//1.分发到10个队列
		for (int j = 0; j < n; ++j)
		{
			QueuePush(&Q[(a[j] / i) % 10], a[j]);
		}
		//2.回收到原数组
		int index = 0;
		for (int j = 0; j < 10; ++j)
		{
			while (!QueueEmpty(&Q[j]))
			{
				a[index++] = QueueFront(&Q[j]);
				QueuePop(&Q[j]);
			}
		}
	}
	for (int i = 0; i < 10; ++i)
	{
		QueueDestroy(&Q[i]);
	}
}

队列实现代码:

// 链式结构:表示队列 
typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;
	QNode* rear;
}Queue;

// 初始化队列 
void QueueInit(Queue* q) {
	assert(q);
	q->front = NULL;
	q->rear = NULL;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q) {
	assert(q);
	if (q->front==NULL)
		return 1;
	else
		return 0;
}
// 队尾入队列 
void QueuePush(Queue* q, QDataType data) {
	assert(q);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	assert(newnode);
	newnode->data = data;
	newnode->next = NULL;
	if (QueueEmpty(q))
		q->front = q->rear = newnode;
	else 
	{
		q->rear->next = newnode;
		q->rear = newnode;
	}
}
// 队头出队列 
void QueuePop(Queue* q) {
	assert(q);
	if (QueueEmpty(q))return;
	else {
		if (q->front->next == NULL) {
			free(q->front);
			q->front = q->rear = NULL;
		}//一个元素出队
		else {
			QNode* next = q->front->next;
			free(q->front);
			q->front = next;
		}
	}
}
// 获取队列头部元素 
QDataType QueueFront(Queue* q) {
	assert(q);
	if (QueueEmpty(q))
		return -1;
	else
		return q->front->data;
}
// 获取队列队尾元素 
QDataType QueueBack(Queue* q) {
	assert(q);
	if (QueueEmpty(q))
		return -1;
	else
		return q->rear->data;
}
// 获取队列中有效元素个数 
int QueueSize(Queue* q) {
	assert(q);
	if (QueueEmpty(q))
		return 0;
	else
	{
		int size = 0;
		QNode* cur = q->front;
		while (cur) {
			size++;
			cur = cur->next;
		}
		return size;
	}
}

// 销毁队列 
void QueueDestroy(Queue* q) {
	assert(q);
	while (q->front != NULL) {
		QueuePop(q);
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值