链表|顺序表代码实现(跑路人笔记)


文章目录

  • 前言
  • 结构体类型
  • 使用
  • 初始化
  • 顺序表摧毁
  • 后插&扩容函数?
    • 扩容函数
  • 前插&前删&后删
    • 前插
    • 前删
    • 后删
  • 插入 ???
    • 首先检查问题
    • size_t带来的一些细节
  • 删除指定下标的元素
  • 查找元素为x的下标位置并返回
  • 结尾

前言 顺序表之前竟然没有实现,顺序表的实现部分还是有一些坑的所以干脆还是实现一下。
数据结构本身我们需要锻炼的就是我们的代码能力调试能力以及代码思维,所以我就逐个函数的讲一下坑和可能被咱们遇到的bug。
结构体类型 首先直接使用动态开辟的顺序表。
typedef int SLDataType; // 动态的顺序表 typedef struct SeqList { SLDataType* date; //存储元素 int size; // 存储数据个数 int capacity; // 存储空间大小 }SL, SeqList;

名字使用SL还是SeqList都可。
存储元素的内容可以使任何变量包括结构体也可。正常的使用中结构体作为储存元素应该是比较常见(猜的=-=),储存元素的类型最好是指针这样不仅可以省下空间也方便我们后续的动态内存开辟。
这样的一个顺序表类型我们就创建好了。
使用 我们在使用时是在main函数中建立一个变量作为顺序表的引子。然后用储存变量中指针的特性来进行动态开辟。
初始化
void SeqListInit(SeqList* psl) { assert(psl); psl->date = NULL; psl->size = 0; psl->capacity = 0; }

初始化可以用两种方法上述是一种或者使用下述
void InitList(SeqList* psl) { ps1->capacity = 10; ps1->size = 0; ps1->date = malloc(sizeof(LSTtype) * 10); }

方法一:
是先将顺序表内元素都初始化为空然后在后续插入数据时进行检查如果为NULL时进行另一种操作(如果没懂在后面讲后插时我会专门提醒)。
方法二:
这种直接将顺序表内安排好的我认为是更合理也更方便的,无论是后续想调试扩容函数还是扩容函数的实现都会简单一些。
无论是方法一还是二我们都要记得要传址而不是传值.
【链表|顺序表代码实现(跑路人笔记)】不过具体使用那种方法都是可以的因为数据结构并没有对实现细节进行严格规定所以大家可以根据自己喜好进行使用。
顺序表摧毁 大家只要注意好顺序表的摧毁一定要毁的彻底一些
void SeqListDestroy(SeqList* psl) { assert(psl); free(psl->date); psl->date = NULL; psl->capacity = psl->size = 0; }

千万不要仅仅只是对储存元素空间进行free记得将其置空以及容量和元素的置空。
后插&扩容函数?
void SeqListPushBack(SeqList* psl, SLDataType x) { assert(psl); SeqListCheckCapacity(psl); psl->a[psl->size] = x; psl->size++; }

后插其实倒是没啥讲的也没有易错点只是要注意一下调用扩容函数即可
扩容函数 扩容函数就有部分要多
void SeqListCheckCapacity(SeqList* psl) { assert(psl); // 如果满了,我们要扩容 if (psl->size == psl->capacity) { size_t newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2; SLDataType* tmp = realloc(psl->a, sizeof(SLDataType)*newCapacity); if (tmp == NULL) { printf("realloc fail\n"); exit(-1); } else { psl->a = tmp; psl->capacity = newCapacity; } } }

使用不同的初始化方式我们就要用不同的扩容方式
上述为方法一的扩容
首先使用newcapacity 来进行检查我们是已经有了数据还是开始时的无数据状态.
然后用tmp来预防realloc开辟空间失败
void chackcapcity(SeqList* phead) { if (phead->size == phead->capacity) { SLDataType* tmp = (SLDataType*)realloc((void*)phead->date, sizeof(SLDataType) * phead->capacity * 2); if(tmp!=NULL) phead->date = tmp; if (phead->date==NULL) { printf("扩容失败"); exit(-1); } phead->capacity *= 2; } }

方法二的我们就可以省略newcapacity的检查直接进行扩容.
前插&前删&后删 这部分没啥难点所以直接合在一起讲了算了.
前插
void SeqListPushFront(SeqList* psl, SLDataType x) { assert(psl); SeqListCheckCapacity(psl); // 挪动数据,腾出头部空间 int end = psl->size - 1; while (end >= 0) { psl->date[end + 1] = psl->date[end]; --end; } psl->date[0] = x; psl->size++; }

前插在实现时要先将元素后移=-=(这也是顺序表的一大缺点这个前插的效率实在是很低).当然我们要从后向前移不然会出问题(我相信大家动动脑子可以想到).
前删
void SeqListPopFront(SeqList* psl) { assert(psl); // 挪动数据覆盖删除 if (psl->size > 0) { int begin = 1; while (begin < psl->size) { psl->date[begin - 1] = psl->date[begin]; ++begin; } --psl->size; } }

前删我们需要让下标为1的覆盖0的数据即可=-=没啥难的.
后删
void SeqListPopBack(SeqList* psl) { //psl->date[psl->size - 1] = 0; if (psl->size > 0) { psl->size--; } }

在被注释的部分是因为之前认为要将被删除的数据清空为0不过后面想想其实完全没有必要这样=-=.
然后就是注意一下ps1->size他的值不能小于零所以加个if即可
而且其实我们的计算机的删除数据也是这样直接将数据空间内存的使用空间的权限去除即可所以才会有了删除比下载快的场景
插入 ???
//在下标为pos的位置插入值为x的值 void SeqListInsert(SeqList* psl, size_t pos, SLDataType x) { assert(psl); // 温和检查 if (pos > psl->size) { printf("pos 越界:%d\n", pos); return; //exit(-1); } // 暴力检查 //assert(pos <= psl->size); SeqListCheckCapacity(psl); //int end = psl->size - 1; //while (end >= (int)pos) //{ // psl->date[end + 1] = psl->date[end]; // --end; //} size_t end = psl->size; while (end > pos) { psl->date[end] = psl->date[end-1]; --end; } psl->date[pos] = x; psl->size++; }

首先检查问题
// 温和检查 if (pos > psl->size) { printf("pos 越界:%d\n", pos); return; //结束函数 //exit(-1); //结束程序 } // 暴力检查 //assert(pos <= psl->size); //直接报错结束程序=-=

这部分是为了检查pos是否比ps1->size大,因为我们是顺序表如果让它随意插入很可能会出现越界然后一堆bug=-=非常痛苦所以我们一定要检查一下
size_t带来的一些细节
//int end = psl->size - 1; //while (end >= (int)pos) //{ // psl->date[end + 1] = psl->date[end]; // --end; //} size_t end = psl->size; while (end > pos) { psl->date[end] = psl->date[end-1]; --end; } psl->date[pos] = x; psl->size++;

首先第一部分(上图被注释部分)
int end = psl->size - 1; while (end >= (int)pos) { psl->date[end + 1] = psl->date[end]; --end; }

在end和pos对比时这个表达式会发生整形提升变成size_t类型这样当end为-1时(及ps1->size为0时)这就很恐怖了在size_t类型里的-1那可是非常非常大的数
所以通过强制类型转换来防止此事件发生.
第二部分
size_t end = psl->size; while (end > pos) { psl->date[end] = psl->date[end-1]; --end; } psl->date[pos] = x; psl->size++;

这时可能有暴躁老哥会讲啊~这不是还有end-1吗
其实这时当end为0时根本进入不了循环也就没有可能会发生越界等情况的发生了
删除指定下标的元素
// 删除pos位置的数据 void SeqListErase(SeqList* psl, size_t pos) { assert(psl); assert(pos < psl->size); size_t begin = pos + 1; while (begin < psl->size) { psl->date[begin - 1] = psl->date[begin]; ++begin; } psl->size--; }

删除指定pos位置的元素其实还是将后面的元素直接向前移动将pos位置的内容覆盖就好
值得注意一下的是我们的pos依旧不能比ps1->size大原因之前有讲
查找元素为x的下标位置并返回
int SeqListFind(SeqList* psl, SLDataType x) { assert(psl); for (int i = 0; i < psl->size; ++i) { if (psl->a[i] == x) { return i; } } return -1; }

就查找遍历就完事
结尾 这个是补之前没有搞的坑的,来点机器人粉丝.

    推荐阅读