stl,vector, list,双向链表,map


本文只是一个搬运工:给大家找到了一些合理的东西!!!

超感谢:http://www.cnblogs.com/imAkaka/archive/2012/03/20/2407877.html

    http://www.oschina.net/question/234345_48876

2,下列关于stl的说法正确的是()

  A,map的迭代器的key是const类型,无法对其进行修改

  B,list是双向链表实现,插入元素的复杂度是O(1)

  C,vector的大小会增大或者减少,但容量只会增大而不会减少

  D,stl的排序算法一般比较传统的快速排序块是因为其选取中值的算法好


答案:a,b,c


  解析:

A选项:

1、map简介

map是一类关联式容器。它的特点是增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。对于迭代器来说,可以修改实值,而不能修改key。

2、map的功能

自动建立Key - value的对应。key 和 value可以是任意你需要的类型。
根据key值快速查找记录,查找的复杂度基本是Log(N),如果有1000个记录,最多查找10次,1,000,000个记录,最多查找20次。
快速插入Key - Value 记录。
快速删除记录
根据Key 修改value记录。
遍历所有记录。
3、使用map

使用map得包含map类所在的头文件

#include <map> //注意,STL头文件没有扩展名.h

map对象是模板类,需要关键字和存储对象两个模板参数:

std:map<int, string> personnel;

这样就定义了一个用int作为索引,并拥有相关联的指向string的指针.

为了使用方便,可以对模板类进行一下类型定义,

typedef map<int, CString> UDT_MAP_INT_CSTRING;

UDT_MAP_INT_CSTRING enumMap;

4、在map中插入元素

改变map中的条目非常简单,因为map类已经对[]操作符进行了重载

enumMap[1] = "One";

enumMap[2] = "Two";

.....

这样非常直观,但存在一个性能的问题。插入2时,先在enumMap中查找主键为2的项,没发现,然后将一个新的对象插入enumMap,键是2,值是一个空字符串,插入完成后,将字符串赋为"Two"; 该方法会将每个值都赋为缺省值,然后再赋为显示的值,如果元素是类对象,则开销比较大。我们可以用以下方法来避免开销:

enumMap.insert(map<int, CString> :: value_type(2, "Two"))


B选项:  

双(向)链表中有两条方向不同的链,即每个结点中除next域存放后继结点地址外,还增加一个指向其直接前趋的指针域prior。双向链表在查找时更方便 特别是大量数据的遍历

双向链表(C语言)详解 及 例程 - 海边风 - 鸭梨栅搭

双向链表(C语言)详解 及 例程 - 海边风 - 鸭梨栅搭

注意:
     ①双链表由头指针head惟一确定的。
     ②带头结点的双链表的某些运算变得方便。
     ③将头结点和尾结点链接起来,为双(向)循环链表。

2、双向链表的结点结构和形式描述
①结点结构(见上图a)
  
②形式描述 
    typedef struct dlistnode{
         DataType data;(数据类型按自己要求更改)
         struct dlistnode *prior,*next;
      }DListNode;
    typedef DListNode *DLinkList;
    DLinkList head;

     由于双链表的对称性,在双链表能能方便地完成各种插入、删除操作。
①双链表的前插操作


ps:注意箭头 没有直入框内 而是整体 代表指向的是整个结点包括 prior data next

    void DInsertBefore(DListNode *p,DataType x)
      {//在带头结点的双链表中,将值为x的新结点插入*p之前,设p≠NULL
        DListNode *s=malloc(sizeof(DListNode));//①(为链表结点动态分配内存)
        s->data=x;//② (将数据X的值赋给s->data)
        s->prior=p->prior;//③ (将结点p的前驱的值赋给s的前驱 使s的前驱指向原来p之前的结点)
        s->next=p;//④ (使s的后驱指向p 经过2.3.4步结点s各个部分赋值完毕)
        p->prior->next=s;//⑤ (原来p之前的结点的后驱指向s)
        p->prior=s;//⑥  (使p的前驱指向s)
       }

   PS: 第⑤⑥步的顺序不能改变,想想为什么呢?

  ②双链表上删除结点*p自身的操作

 

    void DDeleteNode(DListNode *p)
      {//在带头结点的双链表中,删除结点*p,设*p为非终端结点
          p->prior->next=p->next;//① (使p的前一个结点的后驱直接指向 原来的p的后驱)
          p->next->prior=p->prior;//② (使p的后一个结点的前驱 直接为原来p的前一个结点)
          free(p);//③ (释放p的内存 这个很重要 特别是处理大量数据时)
      } 
注意:
     与单链表上的插入和删除操作不同的是,在双链表中插入和删除必须同时修改两个方向上的指针。
     上述两个算法的时间复杂度均为O(1)。


C:

Vector是一个可以容纳动态长度的容器。为了高效地运用Vector,应该了解Vector大小(size)和容量(capacity)的关系:Vector之中用于操作大小的函数有size()、empty()等,另一个与大小有关的函数是capacity(),它返回Vector实际能够容纳的元素数量。如果超出这个数量,Vector就有必要重新配置内部存储器。

 

    Vector的容量之所以重要,主要有两个方面的原因:

    1. 一旦内存重新配置,和Vector元素相关的所有reference、pointers、iterators都会失效;

    2. 内存重新配置很耗时间。

 

    当然,你可以通过使用reserve()函数预留相当大的容量,以避免一再重新配置内存。只要保留的容量足够大,就不用担心references等会失效。

     std::vector<int> v;

     v.reserve(100);

     另一种避免重复配置内存的方法是:std::vector<T> v(5);不过这种方法,对基本型别效率和上一相似,如果元素型别是自定义的类型,此类型就必须提供一个default构造函数(自定义型别在初始化操作的时候很耗时, 不如reserve)。

 

    几点注意事项:

    1. Vector的容量不会缩减,即使使用clear()函数,清空Vector的所有元素,Vector的真正占用内存不会减少。不过有一个缩减Vector容量的小窍门:两个Vector交换内容后,两者的容量也会互换。现有Vector V;

    std::vector<int> tmp;

    V.swap(tmp);                       // 这样tmp就和V交换了容量

    或者:

    V.swap(std::vector<int>() );   // 和一个临时Vector交换


2. 慎用Vector:Vector本身就占用一定的内存,即使不向其中添加任何元素。

    std::vector<int> tmp;

    int a = sizeof(tmp);               // 可以看到a的值为20(当然这个值根据不同的系统与环境有关,我的VS2008+win7)

    甚至,在许多实作方案中,容量的增长幅度比我们料想的还大。事实上为了防止内存破碎,在许多实作方案中即使不调用reserve(),当第一插入元素时也会一口气配置整块内存(如2K)。如果有一大堆Vectors,每个Vector的实作元素却寥寥无几,那么浪费的内存会相当可观。切记!!慎用Vector



在c++中,vector是一个十分有用的容器,下面对这个容器做一下总结。

1 基本操作

(1)头文件#include<vector>.

(2)创建vector对象,vector<int> vec;

(3)尾部插入数字:vec.push_back(a);

(4)使用下标访问元素,cout<<vec[0]<<endl;记住下标是从0开始的。

(5)使用迭代器访问元素.

vector<int>::iterator it;
for(it=vec.begin();it!=vec.end();it++)
    cout<<*it<<endl;

(6)插入元素:    vec.insert(vec.begin()+i,a);在第i+1个元素前面插入a;

(7)删除元素:    vec.erase(vec.begin()+2);删除第3个元素

vec.erase(vec.begin()+i,vec.end()+j);删除区间[i,j-1];区间从0开始

(8)向量大小:vec.size();

(9)清空:vec.clear();

2

vector的元素不仅仅可以使int,double,string,还可以是结构体,但是要注意:结构体要定义为全局的,否则会出错。下面是一段简短的程序代码:

复制代码
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<iostream>
using namespace std;

typedef struct rect
{
    int id;
    int length;
    int width;

  //对于向量元素是结构体的,可在结构体内部定义比较函数,下面按照id,length,width升序排序。
  bool operator< (const rect &a)  const
    {
        if(id!=a.id)
            return id<a.id;
        else
        {
            if(length!=a.length)
                return length<a.length;
            else
                return width<a.width;
        }
    }
}Rect;

int main()
{
    vector<Rect> vec;
    Rect rect;
    rect.id=1;
    rect.length=2;
    rect.width=3;
    vec.push_back(rect);
    vector<Rect>::iterator it=vec.begin();
    cout<<(*it).id<<' '<<(*it).length<<' '<<(*it).width<<endl;    

return 0;

}

 3  算法

(1) 使用reverse将元素翻转:需要头文件#include<algorithm>

reverse(vec.begin(),vec.end());将元素翻转(在vector中,如果一个函数中需要两个迭代器,

一般后一个都不包含.)

(2)使用sort排序:需要头文件#include<algorithm>,

sort(vec.begin(),vec.end());(默认是按升序排列,即从小到大).

可以通过重写排序比较函数按照降序比较,如下:

定义排序比较函数:

bool Comp(const int &a,const int &b)
{
    return a>b;
}
调用时:sort(vec.begin(),vec.end(),Comp),这样就降序排序。


来看程序:

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

bool Comp(int a,int b)//sort中的写法,返回值为bool,参数int
{
        return a>b;
}
int main()
{
        int i;
        int a[] = {1,3,2,4,5};

        vector<int> array;
        cout<<"sizeof(array):"<<sizeof(array)<<endl;//vector的容量为24,大小不一样的。
        for(i = 0; i< 5; i++)
                array.push_back(a[i]);
        cout<<array.size()<<"  ";
        cout<<endl;
        reverse(array.begin(),array.end()); //翻转字符//第一个和最后一个,依次相互交换
        for(i = 0; i < 5; i++)
                cout<<array[i]<<" ";
        cout<<endl;
        cout<<"array.capacity:"<<array.capacity()<<endl;//容量问题
        cout<<"升序"<<endl;
        sort(array.begin(), array.end());  //排序,默认是升序
        for(i = 0; i< 5; i++)
                cout<<array[i]<<" ";
        cout<<endl;
        //排序,改为降序
        cout<<"降序"<<endl;
        sort(array.begin(), array.end(), Comp);
        for(i = 0; i< 5; i++)
                cout<<array[i]<<endl;
        cout<<endl;
        return 0;
}
运行结果:



sort函数的用法

做ACM题的时候,排序是一种经常要用到的操作。如果每次都自己写个冒泡之类的O(n^2)排序,不但程序容易超时,而且浪费宝贵的比赛时间,还很有可能写错。STL里面有个sort函数,可以直接对数组排序,复杂度为n*log2(n)。使用这个函数,需要包含头文件。
   这个函数可以传两个参数或三个参数。第一个参数是要排序的区间首地址,第二个参数是区间尾地址的下一地址。也就是说,排序的区间是[a,b)。简单来说,有一个数组inta[100],要对从a[0]到a[99]的元素进行排序,只要写sort(a,a+100)就行了,默认的排序方式是升序。
   拿我出的“AC的策略”这题来说,需要对数组t的第0到len-1的元素排序,就写sort(t,t+len);
   对向量v排序也差不多,sort(v.begin(),v.end());
   排序的数据类型不局限于整数,只要是定义了小于运算的类型都可以,比如字符串类string。
   如果是没有定义小于运算的数据类型,或者想改变排序的顺序,就要用到第三参数——比较函数。比较函数是一个自己定义的函数,返回值是bool型,它规定了什么样的关系才是“小于”。想把刚才的整数数组按降序排列,可以先定义一个比较函数cmp
bool cmp(int a,int b)//这里之所以没有用&或者*,是因为内部可能已经实现好了
{
    returna>b;
}
  排序的时候就写sort(a,a+100,cmp);

  假设自己定义了一个结构体node
struct node{
    int a;
    int b;
    doublec;
}
   有一个node类型的数组nodearr[100],想对它进行排序:先按a值升序排列,如果a值相同,再按b值降序排列,如果b还相同,就按c降序排列。就可以写这样一个比较函数:

以下是代码片段:
bool cmp(node x,node y)
{
    if(x.a!=y.a) return x.a

if(x.b!=y.b) returnx.b>y.b;
    return return x.c>y.c;
   排序时写sort(arr,a+100,cmp);

qsort(s[0],n,sizeof(s[0]),cmp);
int cmp(const void *a,const void *b)
{
    return *(int*)a-*(int *)b;
}

 

一、对int类型数组排序  

intnum[100];  

Sample:  

int cmp ( const void *a , const void *b)  
 
return *(int *)a - *(int*)b;  
 

qsort(num,100,sizeof(num[0]),cmp);  

二、对char类型数组排序(同int类型)  

charword[100];  

Sample:  

int cmp( const void *a , const void *b)  
 
return *(char *)a - *(int*)b;  
 

qsort(word,100,sizeof(word[0]),cmp);  

三、对double类型数组排序(特别要注意)  

doublein[100];  

int cmp( const void *a , const void *b)  
 
return *(double *)a > *(double *)b ? 1 :-1;  
 

qsort(in,100,sizeof(in[0]),cmp);  

四、对结构体一级排序  

structIn  
 
double data;  
int other;  
}s[100]  

//按照data的值从小到大将结构体排序,关于结构体内的排序关键数据data的类型可以很多种,参考上面的例子写  

int cmp( const void *a ,const void*b)  
 
return ((In *)a)->data - ((In*)b)->data;  
 

qsort(s,100,sizeof(s[0]),cmp);  

五、对结构体 

structIn  
 
int x;  
int y;  
}s[100];  

//按照x从小到大排序,当x相等时按照y从大到小排序  

int cmp( const void *a , const void *b)  
 
struct In *c = (In *)a;  
struct In *d = (In *)b;  
if(c->x != d->x) returnc->x -d->x;  
else return d->y -c->y;  
 

qsort(s,100,sizeof(s[0]),cmp);  

六、对字符串进行排序  

structIn  
 
int data;  
char str[100];  
}s[100];  

//按照结构体中字符串str的字典顺序排序  

int cmp ( const void *a , const void *b)  
 
return strcmp( ((In *)a)->str , ((In*)b)->str);  
 

qsort(s,100,sizeof(s[0]),cmp);  

七、计算几何中求凸包的cmp  

int cmp(const void *a,const void *b)//重点cmp函数,把除了1点外的所有点,旋转角度排序  
 
struct point *c=(point*)a;  
struct point *d=(point*)b;  
if( calc(*c,*d,p[1]) < 0) return1;  
else if( !calc(*c,*d,p[1]) &&dis(c->x,c->y,p[1].x,p[1].y)<dis(d->x,d->y,p[1].x,p[1].y))//如果在一条直线上,则把远的放在前面  
return 1;  
else return -1;  
}



D,stl的排序算法一般比较传统的快速排序块是因为其选取中值的算法好

关于D选项

STL sort源码剖析

STL的sort()算法,数据量大时采用Quick Sort,分段递归排序,一旦分段后的数据量小于某个门槛,为避免Quick Sort的递归调用带来过大的额外负荷,就改用Insertion Sort。如果递归层次过深,还会改用Heap Sort。本文先分别介绍这个三个Sort,再整合分析STL sort算法(以上三种算法的综合) --Introspective Sorting(内省式排序)

一、Insertion Sort

Insertion Sort是《算法导论》一开始就讨论的算法。它的基本原理是:将初始序列的第一个元素作为一个有序序列,然后将剩下的N-1个元素按关键字大小依次插入序列,并一直保持有序。这个算法的复杂度为O(N^2),最好情况下时间复杂度为O(N)。在数据量很少时,尤其还是在序列“几近排序但尚未完成”时,有着很不错的效果
Insertion Sort

上述函数之所以命名为unguarded_x是因为,一般的Insertion Sort在内循环原本需要做两次判断,判断是否相邻两元素是”逆转对“,同时也判断循环的行进是否超过边界。但由于上述所示的源代码会导致最小值必然在内循环子区间的边缘,所以两个判断可合为一个判断,所以称为unguarded_。 省下一个判断操作,在大数据量的情况下,影响还是可观的

二、Quick Sort

Quick Sort是目前已知最快的排序法,平均复杂度为O(NlogN),可是最坏情况下将达O(N^2)。
Quick Sort算法可以叙述如下。假设S代表将被处理的序列:
1、如果S的元素个数为0或1,结束。
2、取S中的任何一个元素,当做枢轴(pivot) v。
3、将S分割为L、R两段,使L内的每一个元素都小于或等于v,R内的每一个元素都大于或等于v。
4、对L、R递归执行Quick Sort。
Median-of-Three(三点中值)
因为任何元素都可以当做枢轴(pivot),为了避免元素输入时不够随机带来的恶化效应,最理想最稳当的方式就是取整个序列的投、尾、中央三个元素的中值(median)作为枢轴。这种做法称为median-of-three partitioning。
Quick Sort

Partitioning(分割)
分割方法有很多,以下叙述既简单又有良好成效的做法。令first向尾移动,last向头移动。当*first大于或等于pivot时停下来,当*last小于或等于pivot时也停下来,然后检验两个迭代器是否交错。未交错则元素互相,然 后各自调整一个位置,再继续相同行为。若交错,则以此时first为轴将序列分为左右两半,左边值都小于或等于pivot,右边都大于等于pivot
Partitioning

三、Heap Sort

STL中有一个partial_sort()算法。
Heap Sort

partial_sort的任务是找出middle-first个最小元素,因此,首先界定出区间[first,middle),并利用make_heap()将它组织成一个max-heap,然后就可以讲[middle,last)中的每一个元素拿来与max-heap的最大值比较(max-heap的最大值就在第一个元素);如果小于该最大值,就互换位置并重新保持max-heap的状态。如此一来,当我们走遍整个[middle,last)时,较大的元素都已经被抽离出[first,middle),这时候再以sort_heap()将[first,middle)做一次排序。
由于篇幅有限,本文不再阐述堆的具体实现,建议海量Google。

四、IntroSort

不当的枢轴选择,导致不当的分割,导致Quick Sort恶化为O(N^2)。David R. Musser于1996年提出一种混合式排序算法,Introspective Sorting。其行为在大部分情况下几乎与 median-of-3 Quick Sort完全相同。但是当分割行为(partitioning)有恶化为二次行为倾向时,能自我侦测,转而改用Heap Sort,使效率维持在O(NlogN),又比一开始就使用Heap Sort来得好。大部分STL的sort内部其实就是用的IntroSort。
Intro Sort

函数一开始就判断序列大小,通过个数检验之后,再检测分割层次,若分割层次超过指定值,就改用partial_sort(),即Heap sort。都通过了这些校验之后,便进入与Quick Sort完全相同的程序。
当__introsort_loop()结束,[first,last)内有多个“元素个数少于或等于”16的子序列,每个序列有相当程序的排序但尚未完全排序(因为元素个数一旦小于 __stl_threshold,就被中止了)。回到母函数,再进入__final_insertion_sort():
View Code

必须要看清楚的是,__final_insertion_sort()之前,经过__introsort_loop()的整个序列可以看成是一个个元素个数小于或等于16的子序列(注意子序列长度是不等的),且这些子序列不但内部有相当程度排序,且更重要的是以子序列与子序列之间也是“递增”的,意思是前一个子序列中的元素都是小于后一个子序列中的元素的,所以这个时候运用insertion_sort(),效率可是相当高的。
 
/--------------------------------------- 华丽的分割线--------------------------------------/
 
关于IntroSort的测试。
细看__introsort_loop(),是不是觉得他的快排的写法很怪, 为什么不能直接这样写呢?
<span style="font-size: 13px;">if (last - first > __stl_threshold){		// > 16
...
...
__introsort_loop(cut,last,value_type(first), depth_limit);
__introsort_loop(first,cut,value_type(first), depth_limit);</span>
于是,我做了一次测试,结果如下图:
测试结果发现,如果不像STL中那么写,其实在数组还比较小时,还快那么一丁点,
并且即使数组变大,在一百万条记录时也只快0.3秒,唔。。也许STL更注重于大型数据吧,
不过像他那么写,实在是牺牲了代码的可读性。
 
为什么是Insertion Sort,而不是Bubble Sort。
选择排序(Selection sort),插入排序(Insertion Sort),冒泡排序(Bubble Sort)。这三个排序是初学者必须知道的三个基本排序方式,且他们速度都不快 -- O(N^2)。选择排序就不说了,最好情况复杂度也得O(N^2),且还是个不稳定的排序算法,直接淘汰。
可冒泡排序和插入排序相比较呢?
首先,他们都是稳定的排序算法,且最好情况下都是O(N^2)。那么我就来对他们的比较次数和移动元素次数做一次对比(最好情况下),如下:
插入排序:比较次数N-1,移动元素次数2N-1。
冒泡排序:比较次数N-1,无需移动元素。(注:我所说的冒泡排序在最基本的冒泡排序基础上还利用了一下旗帜的方式,即寻访完序列未发生数据交换时则表示排序已完成,无需再进行之后的比较与交换动作)
那么,这样看来冒泡岂不是是更快,我可以把上述的__final_insertion_sort()函数改成一个__final_bubble_sort(),把每个子序列分别进行冒泡排序,岂不是更好?
事实上,具体实现时,我才发现这个想法错了,因为写这么一个__final_bubble_sort(),我没有办法确定每个子序列的大小,可我还是不甘心呐,就把bubble_sort()插在__introsort_loop()最后,这样确实是每个子序列都用bubble_sort()又排序了一次,可是测试结果太惨了,由此可以看书Bubble Sort在“几近排序但尚未完成”的情况下是没多少改进作用的。
 
为什么不直接用Heap Sort
堆排序将所有的数据建成一个堆,最大的数据在堆顶,它不需要递归或者多维的暂存数组。算法最优最差都是O(NlogN),不像快排,如果你人品够差还能恶化到O(N^2)。当数据量非常大时(百万数据),因为快排是使用递归设计算法的,还可能发出堆栈溢出错误呢。
那么为什么不直接用Heap Sort?或者说给一个最低元素阈值(__stl_threshold)时也给一个最大元素阈值(100W),即当元素数目超过这个值时,直接用Heap Sort,避免堆栈溢出呢?
对于第一个问题,我测试了一下,发现直接用Heap Sort,有时还没有Quick Sort快呢,查阅《算法导论》发现,原来虽然Quick和Heap的时间复杂性是一样的,但堆排序的常熟因子还是大些的,并且堆排序过程中重组堆其实也不是个省时的事。
 
 
VS2010版STL中的sort竟比我自己写的快这么多?
首先,上文实现的这个Introsort是参照SGI STL写的,于是,我斗胆在VS2010中拿他与std:sort比了比快慢。于是就随机产生两个百万数据的vector用来测试。结果发现,VS中sort的速度竟是我的10倍以上的效率。顿时对微软萌生敬意,可是当我仔细翻看源码时.....
原来,microsoft的sort并没有比sgi的sort快。只是在排序vector时,microsoft把vector的本质数据“萃取”出来了。
即,取消了vector在++时的边界检查语句,把vector::iterator当指针一般使用。所以才在对vector排序时会比我自己写的introsort算法快那么多呢。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 C++ STL 中,vectorlistmap 和 set 都是常用的容器,它们各自有不同的特点和适用场景。下面简要介绍一下它们的特点和使用场景,以帮助你选择合适的容器: 1. vector vector 是一个动态数组,它具有随机访问、高效的尾部插入和删除等优点。由于其底层实现是连续的内存空间,因此在插入或删除元素时可能需要移动大量的元素,这可能导致性能下降。因此,vector 适用于需要随机访问元素,并且插入和删除操作不频繁的场景。 2. list list 是一个双向链表,它具有高效的插入和删除操作,但是不支持随机访问。由于其底层实现是链表,因此在插入或删除元素时只需要修改节点的指针,不需要移动元素,因此插入和删除操作非常高效。因此,list 适用于需要频繁插入和删除元素,并且不需要随机访问元素的场景。 3. map map 是一个关联容器,它使用红黑树实现,支持按关键字排序。由于其底层实现是红黑树,因此在查找元素时非常高效,时间复杂度为 O(log n)。因此,map 适用于需要按关键字排序,并且需要快速查找元素的场景。 4. set set 是一个关联容器,它使用红黑树实现,支持按值排序。由于其底层实现是红黑树,因此在查找元素时非常高效,时间复杂度为 O(log n)。因此,set 适用于需要按值排序,并且需要快速查找元素的场景。 总之,选择合适的容器取决于你的具体需求。如果需要随机访问元素,并且插入和删除操作不频繁,应该选择 vector;如果需要频繁插入和删除元素,并且不需要随机访问元素,应该选择 list;如果需要按关键字排序,并且需要快速查找元素,应该选择 map;如果需要按值排序,并且需要快速查找元素,应该选择 set。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值