在数据结构的学习中,顺序表是我们见到的第一个新的数据结构。是我们必须掌握的数据结构之一,下面我们来学习一下顺序表。
1.动态顺序表的主要结构
(1)顺序表它是类似于数组的可以存储数据的,他是具有n个相同的数据元素的有限序列,物理结构是连续的,逻辑结构也是连续的,这里的物理结构连续主要体现在内存中是连续,在内存中一个接着一个。而在物理结构上连续在,逻辑结构上一定也是连续的。而且功能上我们可以让他实现自定扩容;这也是顺序表和数组的最大区别。我们在实现顺序表的时候也是紧挨着这两个特点来实现的。
#define SEQ_INIT_SUZE 10//初始容量 #define SEQ_INC_SIZE 2 //每次扩容的倍数; typedef int ElemType;类型的重新定义, typedef struct { ElemType* data; int capacity;//容量; int cursize;//元素个数; }Seqlist;
#define ESQ_INIT_SUZE 10 这里使用宏定义来定义我们的顺序表的初始容量为10,这里使用宏定义也是方便我们修改,假如下次我们初始容量需要20,我们就不必在下面的代码中去改动,只需要在宏定义中去修改就可以了。
#define SEQ_INC_SIZE 2,这里使用宏定义的原因也是方便修改。
typedef int ElemType;这里将int重定义为ElemType,也是为了方便修改,当我们下次需要其他类型的时候,就可以直接将int修改掉。而下面代码中的也就全部被修改了。
关于结构体,解体的成员分别是一个int类型的指针,后面我们将使用这个指针,去指向我们动态开辟malloc的空间,第二个成员是用来记录顺序表的容量(capacity),最后一个成员我们用来记录我们顺序表当前有多少数据在我们的顺序表中;并用typedef重定义,方便我们引用是结构体,总体来说就是使用指针data来引领动态开辟的空间,我们可以向顺序表中增 添删除 数据,并 且 使 用capacity来表示当前的顺序表容量,同时使用cursize记录顺序表中当前有多少数据。这就是我们一个顺序表的大致结构。
(2)下面我们对顺序表结构体进行初始化:
首先主函数中创建结构体变量,并且调用函数Init();
int main() { Seqlist plist = { 0 }; Init(&plist); return 0; }
这里使用传址调用,是为了函数中的操作能够改变结构体,如果使用传值就不能改变了。
void Init(Seqlist* plist)//结构体初始化; { assert(plist); //断言,如果plist为NULL,程序就直接结束。 plist->capacity = SEQ_INIT_SUZE;//初始容量设置成10; plist->cursize = 0;//由于还没有存入数据,所以当前顺序表中数据为0; plist->data = (ElemType*)malloc(sizeof(ElemType) * plist->capacity); //动态为顺序表开辟可以容纳10个int类型的空间. if (plist->data == NULL) //如果开辟失败 ,malloc就会返回NULL(空指针); { //如果顺序表空间都没有开辟出来,后面就更不用操作,我们直接使用 exit(-1); //exit(-1);暴力结束; } }
到了这里,我们的顺序表的初始结构就搭建完毕了,现在我们的顺序表已经有了10个int大小的空间,可以存储数据了。
数据存储示例:
void Depo_data(Seqlist* plist)//顺序表填充数据; { assert(plist); for (int i = 0; i < 6; i++) { plist->data[i] = i; plist->cursize++; //每次增填一个数据,当前的数据数量就会增加一。 } }
这里可以实现一个打印函数,打印出我们当前顺序表中的数据,和当前顺序表数据数量,以及顺序表的容量。
示例:
void print(Seqlist* plist) { assert(plist); for (int i = 0; i < plist->cursize; i++) { printf("%d ", plist->data[i]); } printf("\n"); printf("现存元素数:%d\n", plist->cursize); printf("顺序表容量:%d\n", plist->capacity); }
看效果:
2.增添数据的实现
(1)顺序表尾部增加
void Insert_data_back(Seqlist* plist,int num)//末尾插入;num是我们需要插入的数; { assert(plist); //我们的最后一个数据的下表是cursize(当前的数据数量)-1,在最后一个后面添加一个数据下标就是 cursize(当前的数据数量) plist->data[plist->cursize] = num; plist->cursize++; //插入一个数据cursize(当前的数据数量)也需要加一 Judge_add_capacity(plist);//这里需要判断数据个数是否会超过容量 }
Judge_add_capacity(plist);后面会重点说到,
调用Insert_data_back()并且打印出,看效果:
2.顺序表头部插入
我们需要将所有的数据全部往后挪一个位置,将第一个位置空出来,再把我们的数据放进去。
void Insert_data_head(Seqlist* plist,int num)//首行插入; { assert(plist); for (int i = plist->cursize - 1; i >= 0; i--)//从最后一个数据开始挪动 { plist->data[i + 1] = plist->data[i];//将前面的一个数据赋值给后面的一个数据; } plist->data[0] = num;//插入数据 plist->cursize++; Judge_add_capacity(plist); }
调用函数Inser_data_head(),并且打印出来,看效果:
3.顺序表中间插入
我们需要把插入的那个位置空出开,方法类似 头部插入,就是把需要插入的数据以及以后的数据全部往后挪动一个位置,将需要插入数据的位置空出来。看图示例:
看代码:
int Insert_data(Seqlist* plist,int n,int num)//插入一个数据; { //n——插入数据的位置,num——需要插入数据的位置; assert(plist); if (n > plist->cursize - 1 || n < 0) {//判断,我们插入的位置是不是在我们的有效数据的范围里面; printf("插入位置错误\n");//如果插入错误,我们就提示出,并结束这个函数。 return 0; } for (int i = plist->cursize - 1; i >= n; i--) { plist->data[i + 1] = plist->data[i]; } plist->data[n] = num; plist->cursize++; Judge_add_capacity(plist); }
看代码执行效果:
Insert_data(&plist, 2, 100);//在下标为2的位置插入100;
4.扩容
当我们的有效数据达到顺序表的容量的时候,就需要给我们的顺序表扩充容量。我们先把关系整理清楚,什么时候需要扩容?当我们的有效数据数量等于顺序表的容量的时候就需要扩容了,扩容我们直接使用realloc就可以了。示例:
判断有效数据是否达到顺序表的容量:
int re_capacity(Seqlist* plist)//返回容量; { assert(plist); return plist->capacity; } int re_cursize(Seqlist* plist)//返回现存元素个数; { assert(plist); return plist->cursize; } int add_capacity(Seqlist* plist)//判断是否增容; { assert(plist); if (re_capacity(plist) == re_cursize(plist)) { return 1;//需要扩容 } else { return 0;//不需要扩容; } }
扩容实现:
void Judge_add_capacity(Seqlist* plist)//判断是否增容; { if (add_capacity(plist))//0———不扩容;1———扩容; { //使用realloc扩容,扩充之后的容量是原来容量的SEQ_INC_SIZE(2)倍; ElemType* data = (ElemType*)realloc(plist->data, plist->capacity * SEQ_INC_SIZE); plist->capacity *= SEQ_INC_SIZE; if (NULL == data) { printf("增容失败");//如果扩容失败就提示出来,并且推出函数; return 0; } }
int main() { Seqlist plist = { 0 }; Init(&plist); Depo_data(&plist);//填充 0,1,2,3,4,5;cursize=6 print(&plist); Insert_data_back(&plist, 100);//尾插100,cursize=7 print(&plist); Insert_data_head(&plist, 100);//头插100,cursize=8 print(&plist); Insert_data(&plist, 2, 100);//中间插100,cursize=9 print(&plist); Insert_data(&plist, 2, 100);//中间插100,cursize=10,达到扩充条件! print(&plist); return 0; }
代码执行效果:
5.指定坐标的数据删除
这里需要注意的是,我们将数据删除以后,还要保证顺序表的连续性,看图示例:
代码演示:
int dele_data(Seqlist* plist,int pos)//删除指定坐标的数据; { assert(plist); //检查被删除的数据下表是否在我们的有效数据下标内; if (pos > plist->cursize || pos < 0) { //如果不在就提示出来,并且退出函数; printf("删除错误\n"); return 0; } for (int i = pos; i < plist->cursize - 1; i++) { plist->data[i] = plist->data[i + 1]; } plist->cursize--;//删除一个数据,有效数据数量减一; }
调用:
int main() { Seqlist plist = { 0 }; Init(&plist); Depo_data(&plist);//填充 0,1,2,3,4,5;cursize=6 print(&plist); Insert_data_back(&plist, 100);//尾插100,cursize=7 print(&plist); Insert_data_head(&plist, 100);//头插100,cursize=8 print(&plist); Insert_data(&plist, 2, 100);//中间插100,cursize=9 print(&plist); Insert_data(&plist, 2, 100);//中间插100,cursize=10,达到扩充条件! print(&plist); dele_data(&plist, 2);//删除下标为2的数据; print(&plist); return 0; }
执行效果:
6.查找数据
当我们去查找一个数据,如果这个数据在我们的顺序表里面然后得到这个数据的下标,看代码演示:
int seek_data(Seqlist* plist,int num)//查找一个数据; { assert(plist); int i = 0; for (i = 0; i <= plist->cursize - 1; i++) { if (plist->data[i] == num) { return i;//找到返回下标; } } return -1;//找不到返回-1; }
调用函数:
int tep=seek_data(&plist, 3);//查找3的下标; printf("下标为:%d\n", tep);
看代码执行效果:
7. 改动数据
我们可以配合查找数据,可以先找到被被改动数据的下标;在利用顺序表 data [ 下标 ] 进行改动;
这样我们就把顺序表结构的增删查改介绍完了;
本人数据结构小白,有写的不好的地方,欢迎大家斧正。