赞
踩
顺序表其实为线性表的一种,而线性表则代表着具有相同特性数据结构的集合。如果说顺序表是西瓜,链式表是葡萄,那么线性表则是水果,是一类含有相同特征的事物的总称。
对于顺序表来说,它的底层逻辑其实就是数组,而我们知道,数组具有着物理结构上的连续和逻辑结构上的连续。
如图所示:
所以对应到顺序表,则顺序表也同时具有以下特征:1,物理结构上为线性连续的。2,逻辑结构上也为线性连续的。
上文提到,顺序表就相当于数组,所以静态顺序表那么就相当于静态数组了。
那我们又如何理解静态一词的含义呢?其实答案很简单,静态的含义就是数组的总大小无法发生改变。所以我们便可编写出以下代码来表示一个静态顺序表。
- struct SeqList//创建一个静态顺序表的结构体
- {
- int arr[100];//创立一个定长数组
- int size;//size表示当前数组中有效元素的个数
- };
但是当我们真正使用起来静态顺序表时,我们总会发现它的缺点是致命的。1,当有效元素较少时,会造成空间的浪费。2,当需要存储的数据较多时,静态顺序表可能无法完全保存。
而这些缺点在实际应用中则会是致命的,所以为了满足我们对顺序表的使用需求,我们则会更偏向于使用另一种顺序表,叫做动态顺序表。
有了上文的静态顺序表做铺垫,那么理解动态顺序表一词就更简单了。
何为动态?so easy,动态一词的含义其实就是数组的总大小可以发生改变。所以我们便可以编写出以下列代码来表示一个动态顺序表。
- struct SeqList//创建一个动态顺序表的结构体
- {
- int* arr;//数组指针
- int size;//有效数据个数
- int capacity;//空间大小
- };
后文中,我们将会基于动态顺序表来对其进行一系列的操作,以达到对动态顺序表中元素的增删查改工作!!
首先,为了更好更有条理地实现顺序表操作函数,我们将生成三个文件。
在SeqList.h文件中我们以如下方式定义结构体,因为顺序表中的数据可以为整型也可以为字符型,所以为了方便我们更改顺序表的数据类型,我们将使用typedef函数来命名顺序表的数据类型为SLdatatype。
- typedef int SLdatatype;
- typedef struct SeqList//创建一个动态顺序表的结构体
- {
- SLdatatype* arr;//数组指针
- SLdatatype size;//有效数据个数
- SLdatatype capacity;//空间大小
- }SL;//将顺序表改名为SL
其次,为了实现顺序表的动态增长效果,我们需要实现一个函数为SLcheck来判断顺序表的空间是否足够,若不够的话我们将使顺序表的空间大小(也就是capacity)呈2倍数增长。
- //顺序表的空间申请
- void SLcheck(SL* ps)
- {
- //查看空间是否足够,不够的话2倍增加空间
- if (ps->size == ps->capacity)//有效元素大小如果等于空间大小则代表着空间需要增长
- {
- int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//三目操作符
- //使用ralloc函数实现对空间的动态改变
- SLdatatype* temp = (SLdatatype*)realloc(ps->arr, 2 * sizeof(SLdatatype) * ps->size);
- //查看内存空间是否申请成功,不然就直接退出程序
- if (temp == NULL)
- {
- perror("realloc fail!");
- exit(1);
- }
- //空间申请成功
- ps->arr = temp;
- ps->capacity = newcapacity;
- }
- }

最后,对于我们每实现一个顺序表操作函数,我们都必须在test.c中进行一次测试!以防在最后实现代码时错误过多而无法修改。
当我们拿到一个顺序表之后,我们需要对它进行初始化。而初始化则非常简单,我们只需要将顺序表中的数组指针初始化为NULL,size和capacity初始化为零就可以了。整个操作我们将于SeqList.c中进行。
- #include"SeqList.h"//包含头文件!!
- //顺序表的初始化函数
- void SLInit(SL* ps)//传址调用
- {
- ps->arr = NULL;
- ps->size = ps->capacity = 0;
- }
当然,要记住头文件的包含和传址调用。
我们先应该在SeqList.h中声明这个函数,再使用test.c对这个函数进行测试!
在test.c中,测试代码如下:
- #include"SeqList.h"
- void test()
- {
- SL s1;
- SLInit(&s1);
- }
- int main()
- {
- test();
- return 0;
- }
调试加监视代码走起来!
顺序表初始化函数实现成功!
当我们使用完一个顺序表,我们需要对他进行销毁操作。我们只需要对数组指针所指向的内容free掉,然后将size和capacity置为零就可以了。
- //顺序表的销毁函数
- void SLDestroy(SL* ps)
- {
- if (ps->arr)//判断arr是否为空指针
- {
- ps->arr = NULL;
- free(ps->arr);//free函数释放空间
- }
- ps->size = ps->capacity = 0;//置零
- }
然后在SeqList.h中声明后就可以在test.c中进行测试了。
顺序表销毁函数实现成功!
现在准备进入顺序表中烧脑的部分了,但如果尾插听懂了,其他的操作函数对你来说那简直就是不在话下了。
何为尾插?尾插就是在顺序表末尾插入一个数据。我们画图解释。
如图所示,我们在顺序表中最后插入一个x,那么就相当于在下标为size的地方加入一个x,并且同时size也要往后移动一位。但是值得注意的是,如果顺序表空间不够,那我们又拿什么来存放插入的x呢?所以我们应当先使用SLcheck函数来判断空间是否足够,后再进行数据的插入。
- //顺序表的尾插
- void SLPushBack(SL* ps, SLdatatype x)
- {
- assert(ps);//assert断言,判断ps是否为空
- SLcheck(ps);//检查顺序表空间是否足够
- ps->arr[ps->size] = x;//尾部插入x
- ps->size += 1;//size加1,表明有效元素个数加1
- }
于SeqList.h中声明后,于test.c中测试。
- #include"SeqList.h"
- void test()
- {
- SL s1;
- SLInit(&s1);
- SLPushBack(&s1, 1);
- SLPushBack(&s1, 2);
- SLPushBack(&s1, 3);
- SLPushBack(&s1, 4);
- SLDestroy(&s1);
- }
- int main()
- {
- test();
- return 0;
- }

调试加监视,代码走起来,结果如下。
顺序表尾插函数实现成功!
头插头插,顾名思义其实就是在顺序表的头部插入一个数据x。我们画图解释。
所以由图可知,每当我们想要在顺序表头部插入一个数据x的时候,我们需要先判断空间是否足够,再将顺序表的数据挨着挨着从后面开始依次向后移动一位,arr[1]=arr[0]则代表着最后一次元素的移动。最后再于arr[0]中插入x,同时size加1即可。所以代码如下。
- //顺序表的头插
- void SLPushFront(SL* ps, SLdatatype x)
- {
- assert(ps);//assert断言,ps是否为空
- SLcheck(ps);//判断空间是否足够
- for (int i = ps->size; i > 0; i--)
- {
- ps->arr[i] = ps->arr[i - 1];//arr[1]=arr[0];
- }
- ps->arr[0] = x;//顺序表头部插入x
- ps->size += 1;
- }
在SeqList.h中声明后,于test.c中测试。
- #include"SeqList.h"
- void test()
- {
- SL s1;
- SLInit(&s1);
- SLPushBack(&s1, 1);
- SLPushBack(&s1, 2);
- SLPushFront(&s1, 3);//头插
- SLPushFront(&s1, 4);//头插
- SLDestroy(&s1);
- }
- int main()
- {
- test();
- return 0;
- }

调试加监视,代码走起来,结果如图。
顺序表头插函数实现成功!
尾删函数则十分简单,因为根本不需要进行空间是否足够的判断和顺序表元素的移位操作,我们只需要判断ps和ps->arr是否为空,然后让size减1就可以了,所以画图如下:
所以对于该函数,代码如下:
- //顺序表的尾部删除
- void SLDeleteBack(SL* ps)
- {
- assert(ps);//判断顺序表是否为空
- assert(ps->size);//判断顺序表是否有有效元素
- ps->size-=1;//顺序表有效元素减一
- }
在SeqList.h中声明,后于test.c中进行测试。
- #include"SeqList.h"
- void test()
- {
- SL s1;
- SLInit(&s1);
- SLPushBack(&s1, 1);
- SLPushFront(&s1, 4);
- SLDeleteBack(&s1);//顺序表的尾删
- SLDestroy(&s1);
- }
- int main()
- {
- test();
- return 0;
- }
调试加监视,代码走起来!结果如图:
顺序表尾删函数实现成功!
顺序表的头删,就是删除顺序表的第一个元素。具体方式我们画图解释。
所以由图可知,我们只需要从前往后依次将前面的元素向前移动一位,最后一次移位arr[size-2]=arr[size-1],再最后size减1。所以我们的顺序表头删函数如下:
- //顺序表的头部删除
- void SLDeleteFront(SL* ps)
- {
- assert(ps);
- assert(ps->arr);
- for (int i = 0; i < ps->size - 1; i++)
- {
- ps->arr[i] = ps->arr[i + 1];//arr[size-2]=arr[size-1]标志着最后一次移位;
- }
- ps->size -= 1;有效元素个数减1
- }
在SeqList.h函数中声明,于test.c中测试
- #include"SeqList.h"
- void test()
- {
- SL s1;
- SLInit(&s1);
- SLPushBack(&s1, 1);
- SLPushFront(&s1, 4);
- SLDeleteFront(&s1);//顺序表的头删
- SLDestroy(&s1);
- }
- int main()
- {
- test();
- return 0;
- }
调试加监视,代码走起来,结果如下:
顺序表头删函数实现成功!
该方法其实也就包括了顺序表的头插和尾插,该函数可以做到顺序表指定位置插入数据。还是老样子,咱们画图解释!
由图知,该函数有着一个新的参数pos(表示想要插入的位置的下标),当然一定不能忘记pos有大小限制,在写函数的时候我们应该对pos进行限制。其次,每当我们插入一个数的时候,我们应当首先判断空间是否足够,然后再将顺序表从最后到pos的元素一个一个向右移动一位,直到最后一次移位为arr[pos+1]=arr[pos]。最后再插入x(别忘了size要加1),这样,一个顺序表操作的新函数就被我们刨析地十分清楚了。那么我们的函数如下:
- //顺序表中指定位置之前插入数据
- void SLInsert(SL* ps, int pos, SLdatatype x)
- {
- assert(ps);
- assert(ps->arr);
- assert(pos >= 0 && pos < ps->size);//对pos的值进行限定!
- SLcheck(ps);//判断空间是否足够
- for (int i = ps->size; i > pos; i--)
- {
- ps->arr[i] = ps->arr[i - 1];//arr[pos+1]=arr[pos];
- }
- ps->arr[pos] = x;//插入x
- ps->size += 1;//有效元素个数加1
- }
老样子,先声明再测试:
- #include"SeqList.h"
- void test()
- {
- SL s1;
- SLInit(&s1);
- SLPushBack(&s1, 1);
- SLPushBack(&s1, 2);
- SLPushBack(&s1, 3);
- SLPushBack(&s1, 4);
- SLInsert(&s1, 1, 6);//在下标为1的地方插入数据x=6
- SLDestroy(&s1);
- }
- int main()
- {
- test();
- return 0;
- }

调试加监视,代码咱们走起来!结果如图:
顺序表指定位置s插入数据函数实现成功!
该函数的作用是删除指定位置的数据,当然,它的功能肯定比头删和尾删更强大。话不多说,咱们直接上图!
通过看图后,我们可以很清楚地发现,当我们想要删除pos位置的数据时,其实我们可以直接使用arr[pos+1]位置的数据将它覆盖掉,并以此类推,将顺序表中pos之后的元素从前往后一个一个向前移动一位,最后一次移动为arr[size-2]=arr[size-1]。最后的最后一定不要忘掉size减1。那么函数代码如下:
- //顺序表中指定位置删除数据
- void SLErase(SL* ps, int pos)
- {
- assert(ps);
- assert(ps->arr);
- assert(pos >= 0 && pos < ps->size);
- for (int i = pos; i < ps->size - 1; i++)
- {
- ps->arr[i] = ps->arr[i + 1];//arr[size-2]=arr[size-1];
- }
- ps->size -= 1;//顺序表有效数据减少1个
- }
咱们先声明,再于test.c中测试:
- #include"SeqList.h"
- void test()
- {
- SL s1;
- SLInit(&s1);
- SLPushBack(&s1, 1);
- SLPushBack(&s1, 2);
- SLPushBack(&s1, 3);
- SLErase(&s1, 1);//删除顺序表中下标为1的数据
- SLDestroy(&s1);
- }
- int main()
- {
- test();
- return 0;
- }

调试加监视,代码走起来,结果如图所示:
顺序表指定位置删除数据函数实现成功!
该函数的作用是从顺序表中找到你想要的那个数据,并且返回数据的下标,如果没找到,那么就返回一个无效值(其中无效值我们可以用-1来表示,因为数组的下标肯定不会为负数)。
这么简单的函数我们就不用画图展示了,使用for循环便可以搞定我们想要的操作,直接上代码!
- //顺序表中指定数据的查找
- int SLFind(SL* ps, SLdatatype x)
- {
- assert(ps);
- assert(ps->arr);
- for (int i = 0; i < ps->size; i++)
- {
- if (ps->arr[i] == x)
- {
- //找到了
- return i;
- }
- }
- //没有找到
- return -1;
- }

老规矩,先声明再测试:
- #include"SeqList.h"
- void test()
- {
- SL s1;
- SLInit(&s1);
- SLPushBack(&s1, 1);
- SLPushBack(&s1, 2);
- SLPushBack(&s1, 3);
- int find1 = SLFind(&s1, 2);//寻找顺序表中数据为2的元素的下标
- int find2 = SLfind(&s1, 5);//寻找顺序表中数据为5的元素的下标
- SLDestroy(&s1);
- }
- int main()
- {
- test();
- return 0;
- }

调试加监视,代码走起来看结果!
find1的值为1,说明下标为1,find2的值为-1,说明该数据不存在。
那么顺序表查找函数实现成功!
六千四百多个字,再加图片加代码,手指头酸死了,希望我的blog讲解的足够详细,能对你有所帮助,有错误也欢迎指正,希望您可以给我一套三连,您的支持就是对我莫大的鼓励!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。