1:顺序表是一种线性表的数据结构,比较像一维数组,优点是可以随机访问元素,根据下标可以随机访问元素,存储密度比较高,不需要额外的指针来表示元素之间的关系,所有的空间都用于存储数据元素。缺点是插入和删除的效率比较低,每插入或删除一个元素都需要移动这个位置后面的所有元素。 但如果单纯用一维数组来实现的话,后期不好维护,并且数组的大小是一开始就固定的,并不能很好的适应我们的需求,有可能出现空间过大而造成浪费,或者空间过小造成数据无法存储在里面,所以为了后期方便维护这个顺序表,我们可以定义一个表头。 表头里面存储着指向顺序表的数据区的指针,有一个len变量来表示顺序表里有效元素的个数,方便后续的增删改查,有capacity变量来表示数据区的大小,表示这个顺序表的容量。 typedef int Elenment;//定义这个表的数据 // 1. 顺序表的表头定义 typedef int Element; typedef struct { Element *data; // 指向顺序表的数据区 int len; // 该数据区的有效元素个数(0,1...)[待插入的位置索引号] int capacity; // 数据区的边界条件(容量) }SeqList; 2:有了表头接下来就要创建表头,由于不知道数据元素的数量有多少,那么就需要动态的申请空间的大小,所以申请空间必须在堆空间中申请,可以根据实际需求动态的调整顺序表的大小,不在栈空间申请的原因是栈空间通常较小,如果数据元素数量较大可能超出栈的容量限制,导致栈溢出错误。 既然在堆空间中申请空间那么就需要在使用过后对这片空间进行释放,如果不释放有可能造成内存泄漏,申请的空间越来越多但不释放内存容量会逐渐不足,并且不释放的话那这片空间就无法被其他程序使用,降低了系统资源的利用率。 // 2. 表头结构的操作行为 , 创建表头,表的数据区也申请好,必须告知初始化的大小,将创建好的表头的地址返回给上层,表头一定是指向堆空间 SeqList *createSeqList(int n); { SeqList *seqList = NULL;//初始化顺序表的指针为空,让指针有一个明确的初始状态,方便后 //续判断是否成功申请到了空间,提高了代码的可读性 seqList = (SeqList*)malloc(sizeof(SeqList));//为顺序表结构体分配内存空间,malloc //用于在内存的动态存储区中分配一块指 //定大小的连续空间 if(seqList == NULL){//如果没申请成功这片空间,那么这个指针一直会指向NULL不变,可以 //以此来判断是否申请成功 return NULL; } seqList->data = (Elenment*)malloc(sizeof(Elenment)*n);//这个表的数据有n个 //Elenment,动态的根据 //Elenment的个数申请空间 seqList->len = 0;//刚申请一个新的表,表中没有有效的数据,所以len为0 seqList->capacity = n;//由于传入的n表示要申请的大小,那么边界capacity就为n } void releaseSeqList(SeqList *seq){ //如果seq这个表存在 if(seq){ //如果这个表中有数据 if(seq->data){ free(seq->data);//释放这些在堆空间中的数据 seq->data=NULL;//让这些数据指向空,这是最保险的释放空间的方法 } free(seq);//将表中的数据释放完成后,最后释放表头 } } 3:对数据区的插入操作 //有可能数据过于庞大导致顺序表的容量不够,这时候就需要扩容 扩容策略,2倍扩容,避免了系统开销 static int enLargerSeq(SeqList *seq){ //1,先申请一片更大的空间,空间的大小是原来的两倍 Elenment *tmp = malloc(sizeof(Elenment) * seq->capacity * 2);//新空间的类型是 //Elenment类型的指针,并将其储存在tmp中 if(tmp == NULL){//如果申请不成功,那么tmp依旧为NULL,返回-1 return -1; } //2,把原空间的内容拷贝到新空间 for(int i = 0,i < seq->len , ++i){ tmp[i] = seq->data[i]; } //3,释放原空间,更新表头结构 free(seq->data);//将原来数据区的数据释放 seq->data = tmp;//指向新的数据区 seq->capacity *= 2;//容量扩大为原来的2倍 //数据不变所以len不变 return 0; } 3.1:尾插法 创建完表之后就要往表里写入数据,尾插法的效率是最高的,不用移动其他的数据。从任意位置插入的效率是最低的,插入前还得移动插入位置后面的数据向后移动。 int pushBackSeq(SeqList *seq,Elenment val){//要想往表里插入数据,就要有表头和待插入的数据 //如果空间满了,需要扩容 if((seq->len >= seq->capacity) && enLargerSeq(seq)){//如果len比capacity大就执行 //enLargerSeq(seq)这个操作,如果这个操作 //返回非0值就代表扩容失败,返回0值就代表成功 return -1; } //因为len表示的是有效元素的个数,但是表是从0开始,所以顺序表到len这个位置的时候,这 //个位置是空的,可以直接在这个位置插入元素 seq->data[seq->len] = val;//将新元素放到len这个位置上 ++seq->len;//插入完数据后数据多了一个,所以len要相应的增加 return 0; } 3.2:任意位置插入 任意位置插入,需要先将待插入位置及其后面的元素都向后移动一位,然后再在空余出来的位置拷贝待插入元素,需要考虑待插入位置是否合理,是否小于0或者大于这片空间,需要考虑空间是否足够 int insertSeq(SeqList *seq,int pos,Elenment val){//要给表头,给插入的位置,和插入的元素 //1,范围校验 if(pos<0 || pos > seq->len){//超过顺序表的容量,插入失败返回-1 return -1; } //2,是否扩容 if(seq->len >= seq->capacity && enLargerSeq(seq)){ return -1; } //3,拷贝插入位置(包含)后面的值,往后挪 for(int i = seq->len - 1,i > pos,i--){ seq->data[i+1] = seq->data[i]; } //4,放到新元素,更新表头 seq->data[pos] = val; ++seq->len; return 0; } 4:删除操作 找到待删除的位置,将后一个元素覆盖到前一个位置,实现删除操作,同时数据减少,将len相应的减少 int deleteSeq(SeqList *seq,Elenment val){ //1,找到val所在的下标 int pos = findSeq(seq,val); if(pos == -1){ return -1; } //2,按照pos所在的位置,依次向前覆盖 for(int i = pos + 1,i < seq->len,i++){ seq->data[i - 1] = seq->data[i]; } --seq->len; return 0; } 4.1:查找元素的所在空间的索引号 通过循环匹配判断顺序表内的元素是否与要寻找的元素相等,相等就返回改元素对应的位置i int findSeq(SeqList *seq,Elenemnt val){ for(int i = 0,i < seq->len,i++){ if(seq->data[i] == val){ return i; } } } ----------------这是头文件的部分----------------
顺序表的实现
最新推荐文章于 2024-10-16 00:00:43 发布