8.尾删
//尾删 void SLPopBack(SeqList* ps) { assert(ps); assert(ps->size);//如果顺序表中没有元素了仍在删除就会报错 ps->size--;//只能size--不用free被删除的空间,因为free无法释放部分动态开辟的内存空间 }
可以借用函数SeqListErase实现尾删
SeqListErase(ps, ps->size-1);
9.查找数据
//查找某个数据(找到了就返回该数据的下标,没找到就返回-1) int SeqListFind(SeqList* ps, SLDataType x) { assert(ps); int i = 0; while (i < ps->size) { if (ps->a[i] == x) { return i; } ++i; } return -1; }
目前数据量较小可以直接查找(遍历整个数组),它的时间复杂度为O(n) 如果要用二分法查找,需要先排序,所以不太推荐。
10.插入数据
//在pos所指向的数据位置插入数据 void SeqListInsert(SeqList*ps, size_t pos, int x) { assert(ps); assert(pos <= ps->size); SLCheckCapacity(ps); // 挪动数据(错误示范) /*int end = ps->size - 1; while (end >= pos) { ps->a[end + 1] = ps->a[end]; --end; }*/ //正确的做法 size_t end = ps->size; for (; end > pos; --end) { ps->a[end] = ps->a[end - 1]; } ps->a[pos] = x; ++ps->size; }
可以用这个函数来代替头插和尾插,但是顺序表一般会常用到尾插,所以还是需要实现头插和尾插。
注意:
上面错误示范中,当end = 0时存在死循环问题。因为size_t是无符号整型,当end = 0时再给end减1就会得到一个特别大的正数而非-1,就会导致程序进入死循环。 (如果只是将end的类型改为int,则在进行end与pos的比较时会发生数据类型转换,会把无符号数提升为有符号数,依然会产生这个问题)
解决办法:①把end的类型改为int,将pos的类型强转为int; ②判断用pos <end 不要用 <= 。
11.删除数据
//删除pos所指向的数据 void SeqListErase(SeqList*ps, size_t pos) { assert(ps); assert(pos < ps->size); int begin = pos; while (begin < ps->size - 1) { ps->a[begin] = ps->a[begin + 1]; ++begin; } ps->size--; }
12.修改数据
//修改pos所指向的数据 void SeqListModify(SeqList*ps, size_t pos, SLDataType x) { assert(ps); assert(pos < ps->size); ps->a[pos] = x; }
删除数据、插入数据以及修改数据都可以结合查找数据函数来使用,大家也可以试着自己来实现一下。
五、主函数(测试接口)
这是我用于测试接口的主函数代码,大家要进行测试也可以根据自己的喜好来插入数据或者删除数据,也可以在主函数实现一个菜单来调用这些接口。
#define _CRT_SECURE_NO_WARNINGS #pragma once #include"SeqList.h" void test1()//头插、尾插、头删、尾删 { SeqList s; SeqListInit(&s); SLPushBack(&s, 1);//尾插 SLPushBack(&s, 2);//尾插 SLPushBack(&s, 3);//尾插 SLPushBack(&s, 4);//尾插 SLPushBack(&s, 5);//尾插 SLPushFront(&s, 10);//头插 SLPushFront(&s, 12);//头插 SLPushFront(&s, 13);//头插 SLPushFront(&s, 41);//头插 SLPushFront(&s, 61);//头插 SLPrint(&s);//打印顺序表的数据(方便观察) SLPopFront(&s);//头删 SLPopFront(&s);//头删 SLPopFront(&s);//头删 SLPopFront(&s);//头删 SLPrint(&s);//打印顺序表的数据(方便观察) SLPopBack(&s);//尾删 SLPopBack(&s);//尾删 SLPopBack(&s);//尾删 SLPopBack(&s);//尾删 SLPopBack(&s);//尾删 SLPrint(&s);//打印顺序表的数据(方便观察) SeqListDestory(&s);//创建一个顺序表,使用后要销毁,避免内存泄漏 } void test2()//查找数据 { SeqList s; SeqListInit(&s); SLPushBack(&s, 1);//尾插 SLPushBack(&s, 2);//尾插 SLPushBack(&s, 3);//尾插 SLPushBack(&s, 4);//尾插 SLPushBack(&s, 5);//尾插 SLPrint(&s);//打印顺序表的数据(方便观察) //可以用输入数据的方式进行测试也可以直接用在相应位置写上数据进行测试 //SLDataType n = 0; //printf("请输入要查找的数据:>"); //scanf("%d", &n); //int ret = SeqListFind(&s, n);//查找某个数据 //if (ret != -1) //{ // printf("该数据的下标为%d", ret); //} //else //{ // printf("没找到!"); //} int ret = SeqListFind(&s, 1);//查找某个数据 if (ret != -1) { printf("该数据的下标为%d", ret); } else { printf("没找到!"); } SeqListDestory(&s);//创建一个顺序表,使用后要销毁,避免内存泄漏 } void test3()//在pos位置插入一个数 { SeqList s; SeqListInit(&s); SLPushBack(&s, 1);//尾插 SLPushBack(&s, 2);//尾插 SLPushBack(&s, 3);//尾插 SLPushBack(&s, 4);//尾插 SLPushBack(&s, 5);//尾插 SLPrint(&s);//打印顺序表的数据(方便观察) printf("请输入要插入数据的下标:>"); size_t n = 0; scanf("%d", &n); printf("请输入要插入的数据:>"); SLDataType m = 0; scanf("%d", &m); SeqListInsert(&s, n, m);//在pos位置插入一个数 SLPrint(&s);//打印顺序表的数据(方便观察) SeqListDestory(&s);//创建一个顺序表,使用后要销毁,避免内存泄漏 } void test4()//删除pos所指向的数据 { SeqList s; SeqListInit(&s); SLPushBack(&s, 1);//尾插 SLPushBack(&s, 2);//尾插 SLPushBack(&s, 3);//尾插 SLPushBack(&s, 4);//尾插 SLPushBack(&s, 5);//尾插 SLPrint(&s);//打印顺序表的数据(方便观察) printf("请输入要删除的数据的下标:>"); size_t n = 0; scanf("%d", &n); SeqListErase(&s, n);//删除pos所指向的数据 SLPrint(&s);//打印顺序表的数据(方便观察) SeqListDestory(&s);//创建一个顺序表,使用后要销毁,避免内存泄漏 } void test5()//修改pos所指向的数据 { SeqList s; SeqListInit(&s); SLPushBack(&s, 1);//尾插 SLPushBack(&s, 2);//尾插 SLPushBack(&s, 3);//尾插 SLPushBack(&s, 4);//尾插 SLPushBack(&s, 5);//尾插 SLPrint(&s);//打印顺序表的数据(方便观察) printf("请输入要修改的数据的下标:>"); size_t n = 0; scanf("%d", &n); printf("请输入要修改的数据的值:>"); SLDataType m = 0; scanf("%d", &m); SeqListModify(&s, n, m);//修改pos所指向的数据 SLPrint(&s);//打印顺序表的数据(方便观察) SeqListDestory(&s);//创建一个顺序表,使用后要销毁,避免内存泄漏 } int main() { //test1(); //test2(); //test3(); //test4(); test5(); return 0; }
学习数据结构时的建议(敲代码):
①建议边写边编译;
②数据结构不要上来直接写出菜单,应该分功能模块来实现;
③调试时可以一次在main函数中只调试一个函数(方便、快捷、不易出错)。
六、顺序表的问题和缺点
①中部和头部的插入和删除不方便,时间复杂度为O(n)
②顺序表的增容要申请新的空间,可能需要拷贝数据到新的空间,并且释放旧空间,会有不小的系统消耗,会降低效率
③增容一般呈两倍增长,必然会导致空间浪费。
彩蛋
1.关于扩容
当内存用满了动态顺序表是可以进行扩容的,我们一般是扩容二倍。
但是为什么要扩容二倍?
答:因为2倍比较合适。
扩容太多会导致空间浪费;扩容太少又会导致需要多次扩容,会造成效率的损失(因为扩容是有代价的,如果异地扩容消耗就会比较大)。
2.关于缩容:
realloc是可以进行缩容的,但是我们的原则是绝不缩容,原因:
①缩容要付出代价,缩容有两种情况:原地缩容和异地缩容(异地的话它有可能会重新开辟一块空间,把缩容后的数据放进去,再返回新地址),异地扩容会造成系统消耗的
②如果缩容之后有需要插入数据这时候又需要再次扩容,就会造成系统消耗,导致效率降低
总结:缩容就是用时间换空间的做法,不缩容就是用空间换时间的做法
总结
以上就是今天要讲的内容,本文介绍了数据结构中的线性表,主要介绍了顺序表的定义以及实现。
本文作者也是一个正在学习编程的萌新,目前也只是刚开始接触数据结构这方面的内容,如果有什么内容方面的错误或者不严谨,欢迎大家在评论区指出。
最后,如果本篇文章对你有所启发的话,也希望可以支持支持作者,谢谢大家!