目录

0.前言

1.线性表

2.顺序表

2.1什么是顺序表

2.2 顺序表结构体设计

2.3 顺序表的初始化

2.4 顺序表的销毁

2.5* 顺序表的尾插

2.6* 顺序表的尾删

2.7 顺序表的头插

2.8 顺序表的头删

2.9 顺序表的查找

2.10 顺序表的插入(任意位置)

2.11顺序表的删除(任意位置)

2.12 复用大说明

2.13测试大说明(应用上可以有查找删除面)

3.* 未来写数据结构注意


0.前言

本文顺序表代码以及相关思维导图文件都已上传至gitee,可自取

2顺序表/动态顺序表实现 · onlookerzy123456qwq/data_structure_practice_primer - 码云 - 开源中国 (gitee.com)https://gitee.com/onlookerzy123456qwq/data_structure_practice_primer/tree/master/2%E9%A1%BA%E5%BA%8F%E8%A1%A8/%E5%8A%A8%E6%80%81%E9%A1%BA%E5%BA%8F%E8%A1%A8%E5%AE%9E%E7%8E%B0

1.线性表

 线性表,英文名linera list,是n个具有相同特性的数据元素排成的有限线性序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表,链表,字符串,栈和队列等等。

线性表在 逻辑上线性结构,也就说是连续的一条直线。但是在 物理结构上并 不一定是连续的,线性表在 物理上存储时,通常以 数组和链式结构的形式存储。

2.顺序表

2.1什么是顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成增删查改

说人话就是,顺序表就是一个数组,但是在数组的基础上,它要求数据必须是连续存储的,不能有跳跃和间隔。

下面我具体实现一个顺序表

2.2 顺序表结构体设计

首先顺序表是有两类的,一种是可以动态扩容的,一种则是静态的。静态的顺序表的特点:如果满了,就不允许插入了。动态的顺序表是空间不够可以增容,从而继续存储。第一种的缺点是很大的,因为静态的顺序表从一开始其容量就已经决定了后续不能扩容很难确定在一开始给这个顺序表多大的空间。例如我想插入1001个数据,但是静态顺序表只能存1000个,这最后1个数据你就无法插入。而如果你只改下只想插入存储10个数据,那1000的大小的静态顺序表就会有900+的空间是浪费的。N给小了不够用,N给大了会浪费,就好纠结!

所以我们选用动态的顺序表进行实现。

我们说C语言中如何用一个struct,来描述表示一个完整的顺序表呢?

首先要开辟出一个数组实体,堆区的空间远大于栈区,所以选择在堆区存储该数组,所以我们就要在结构体中存储一个指针指向存储在堆区的该数组

然后还需要分别描述该数组以及顺序表的大小。因为我们开辟出一段空间,但是并不意味着这个数组上每个元素都存储着有效数据数组的大小是顺序表的纯物理意义上的大小而实际有效元素的个数才是真正的该顺序表的大小,这是两个概念。所以需要定义一个整形变量作为容量代表的是现在数组的大小,即现在数组能够存储元素的个数,但并不意味着现在每个地方都存储着有效数据。然后还需要定义一个整形变量作为大小,代表的是现在顺序表的大小,即现在数组中有效插入数据元素的个数。

typedef struct SeqList {int* _a;    //指向堆区数组实体int _size;  //顺序表大小int _capacity; //顺序表容量
}SeqList;

可是我们就可以发现一个问题,这个顺序表只能存储int类型的元素,我们要存储别的类型的元素如char,如long long,甚至是我们自己定义的结构体类型元素struct Student,都必须直接在SeqList结构体内部修改,为了方便后续修改存储类型,即我们这个SeqList可以有通用的一个类型,所以我们使用typedef一个通用类型的方式,进行定义,后续直接修改通用类型的代表类型就可以了。

//作为顺序表数据的通用类型
typedef int SLDataType;typedef struct SeqList {SLDataType* _a;int _size;int _capacity;
}SeqList;

2.3 顺序表的初始化

我们要对一个定义出的顺序表struct SeqList对象进行初始化。例如SLDataType* _a指针一开始总不能是一个野指针,int _size和_capacity总不能是随机值吧。所以定义出来就要对这个顺序表初始化,这点是我们必须要做的,不初始化的后果是很严重的,例如野指针非法访问,顺序表大小和容量紊乱。

然后是传参问题,我们不能在SeqListInit接口上直接传入SeqList s,因为我们知道C语言上的函数传参,函数中的参数是对传入数据的一份拷贝,应该传入的是SeqList* ps。如何体会这一点,我们进行如下实验:

//wrong example
void SeqListInit(SeqList s)
{s._a = NULL;s._size = s._capacity = 0;
}

如果直接传SeqList对象,在内存空间上就会如上图呈现,则我们在SeqList当中的逻辑,修改赋值的是s那一块空间的0xABCE,randomval1和randomval2。SeqListInit函数栈帧销毁之后我们发现原来sq的值还是没有修改。

所以我们应该传入sq的地址。

void SeqListInit(SeqList* ps)
{ps->_a = NULL;ps->_size = ps->_capacity = 0;
}

这样修改的就是sq本体成员的值了。同时,我们之后的所有接口都是需要传入sq的地址的,因为只有这样才能操作到sq实体!否则操作到的都是sq的拷贝

2.4 顺序表的销毁

我们知道我们的顺序表使用的是堆区的空间,如果我们使用完这个顺序表,直到进程最后退出都不释放这块顺序表的堆区空间的话,那就会造成严重的内存泄漏问题。同时我们也可以看到如果不把sq内部的_a指针置空,就会发生野指针的问题

所以无论如何,当用完一个顺序表,我们必须要销毁顺序表

void SeqListDestroy(SeqList* ps)
{//释放堆区空间(在此时检查越界)free(ps->_a);//避免野指针ps->_a = NULL;ps->_capacity = ps->_size = 0;
}

2.5* 顺序表的尾插

不要麻痹大意,简单的往往不简单!1.首先画图,2.然后思考各种情况,3.再上手写代码,4.写完代码须思考还有何纰漏,5.最后还要写代码测试。

就像尾插,如果直接在数组实体的尾部插入数据,只写这一句代码就完成这个函数的实现,这其实非常非常错误的。

你是否考虑过,这个顺序表的容量是否已经满了,再插入就要增容的情况,你是否考虑过,一开始这个顺序表是指向NULL的,我们需要对之开辟一段空间,然后才能对之插入。你是否考虑过,插入之后就需要更新struct SeqList对象内部的顺序大小_size这个成员变量,因为我们插入之后,顺序表存储的有效数据数量确实是增加了一个。这些都应该是我们应该考虑的事情。

事实上无论是尾插PushBack,还是头插PushFront,还是任意位置插入Insert,只要插入成功,都是需要考虑上述的增容检查问题以及顺序表大小_size更新问题

那这样我们就设计检测增容为一个通用接口,因为我们在设计涉及所有有关插入接口的时候,都需要检查增容。

void CheckSLCapacity(SeqList* ps)
{if (ps->_size == ps->_capacity){int next_cap = (ps->_capacity == 0) ? 4 : ps->_capacity * 2;SLDataType* ptmp = (SLDataType*)realloc(ps->_a, sizeof(SeqList) * next_cap);if (ptmp == NULL){//申请空间失败perror("realloc");exit(1);}ps->_a = ptmp;ps->_capacity = next_cap;}
}void SeqListPushBack(SeqList* ps, SLDataType x)
{//检查扩容CheckSLCapacity(ps);//尾部插入ps->_a[ps->_size] = x;//更新顺序表ps->_size++;
}

只要这样才是完整的尾插。

2.6* 顺序表的尾删

首先我们需要明白,衡量这个SeqList对象有效数据多少的是,是成员变量_size的大小,所以我们其实只需要让ps->_size--,就可以达到尾删的效果,并不需要我们对尾部的数据赋值成0,-1或者是什么值,在下一次的插入的时候,这个刚刚尾删的数据单元,会被覆盖的,这个数据是什么改不改并不重要。

ps->_size--确实可以达到尾部删除的效果,可是我们在所有时候的删除都要让ps->_size--吗?我们测试如下代码

void SeqListPopBack(SeqList* ps)
{//减少有效数据个数即可删除ps->_size--;
}

可是你是否考虑过,删除的时候,如果此时_size大小,即顺序表有效数据的数目,已经是0的话,这时候我们还能减吗?如果不加以约束,_size依旧无脑减一,那后面我们就会发现,进程崩溃!

void SeqListTest5()
{SeqList sq;SeqListInit(&sq);SeqListPushBack(&sq, 10);SeqListPushBack(&sq, 20);//如果不加以在删除函数内部约束,这样删除会使得_size:2->-3SeqListPopBack(&sq);SeqListPopBack(&sq);SeqListPopBack(&sq);SeqListPopBack(&sq);SeqListPopBack(&sq);//接下来插入会发生数组的越界访问SeqListPushBack(&sq, 30);//_a[-3]=30SeqListPushBack(&sq, 40);//a[-2]=40SeqListDestroy(&sq);//在free的时候,会检查出数组的越界错误}
int main()
{SeqListTest5();return 0;
}

原因也很简单,那就是我们看到,如果无脑的过度删除那_size就会变成负数。然后后续我们再插入的时候,执行到这一句代码的时候:ps->_a[ps->_size] = x;就有可能变成ps->_a[-1]=x;ps->_a[-2]=x;等等,就会发生越界访问的问题!这就是问题!不过我们还是需要知道一件事,这个数组越界问题的报错并不是在访问修改的时候就及时报错,而是会在后面的free的时候才会进行越界的检查与报错,所以我们说数组越界访问的问题的报错是在最后才会呈现出来的。

所以我们应该允许有有效数据,即_size>0的时候,才进行删除,否则就是无效删除,在调用删除的时候,就什么都不干。

void SeqListPopBack(SeqList* ps)
{//在有有效数据的情况下删,没有则不删if (ps->_size > 0){//减少有效数据个数即可删除ps->_size--;}
}

2.7 顺序表的头插

首先画图,其次考虑各种情况,然后再写代码,最后考虑代码的纰漏。

最后还需要更新ps->_size++。

void SeqListPushFront(SeqList* ps, SLDataType x)
{//检查扩容CheckSLCapacity(ps);//往后移动int end = ps->_size - 1;while (end >= 0){//a[end]代表去赋值者ps->_a[end + 1] = ps->_a[end];--end;}ps->_a[0] = x;//更新顺序表ps->_size++;
}

2.8 顺序表的头删

删除我们要知道,必须是_size>0的时候才能进行删除。

void SeqListPopFront(SeqList* ps)
{//在有有效数据时才删除if (ps->_size > 0){//处理数据:所有的前挪动//a[beg]是被赋值者int beg = 0;while (beg <= ps->_size - 2){ps->_a[beg] = ps->_a[beg + 1];++beg;}//更新顺序表有效数据ps->_size--;}
}

2.9 顺序表的查找

我们要在一个顺序表中,对所有有效数据进行遍历,找到目标要寻找元素的下标,如果寻找失败,则返回-1。

int  SeqListFind(SeqList* ps, SLDataType x)
{int pos = 0;while (pos < ps->_size){if (ps->_a[pos] == x){return pos;}++ pos;}return -1;
}

2.10 顺序表的插入(任意位置)

我们设计一个接口Insert,传入要插入的位置pos,然后在这个位置进行插入,同时pos位置及其之后的元素都要被挤到后面去,完成一次插入。当然我们插入的位置是有要求的,我们可以在_a[0]到_a[_size]进行插入在[0]位置的插入就是头插,在[_size]位置的插入就是尾插,在0<pos<_size插入就是在中间插入。其他的位置插入都是非法的,因为顺序表(线性表)的要求就是数据必须连续存储,不能有任何间隔的数组。如果允许跳跃存储的话,那就会违反顺序表的基本性质。

如果检查插入的位置是合法的,那就要火速进行检查扩容,防止存储的位置不够。

逻辑就是在_a[0]---_a[pos-1]的元素不变,然后_a[pos]---a[_size-1]整体往后挪动,最后我们在_a[pos]位置上赋值插入用户想插入的值。

当然要注意在最后要更新_size。

void SeqListInsert(SeqList* ps, int pos, SLDataType x)
{//在pos位置插入//检查插入位置是否合法assert(pos >= 0 && pos <= ps->_size);//检查扩容CheckSLCapacity(ps);//[0,pos-1]不动,[pos,ps->_size-1]集体往后挪int end = ps->_size - 1;while (end >= pos){ps->_a[end + 1] = ps->_a[end];--end;}//给pos位置赋值ps->_a[pos] = x;//更新size大小ps->_size++;
}

2.11顺序表的删除(任意位置)

首先我们要意识到,删除必须是在合法的位置删除才有意义,不合法的位置删除是没有意义的。比如说,我们可以删除从_a[0]---_a[_size-1]的任意一个元素。如果要删除的元素是[_size-1],那就是尾删,如果删除的元素下标位置是0<pos<_size-1,此时_a[0]---_a[pos-1]的元素不变,同时_a[pos+1]---a[_size-1]整体往前挪动覆盖,就可以达到删除的效果。而如果不是[0,_size-1]位置的元素要进行删除,那其实是不允许的。

最后不要忘记_size--,这才是真正宣告顺序表少了一个元素。

void SeqListErase(SeqList* ps, int pos)
{//删除位置有效才可以,非有效直接报错assert(pos >= 0 && pos < ps->_size);if (ps->_size > 0){//[0]--[pos-1]:保留不变,[pos]--[ps-_size-1]前挪删除int beg = pos;while (beg <= ps->_size - 2){ps->_a[beg] = ps->_a[beg + 1];++beg;}//更新有效数据大小(其实这才是删除)ps->_size-- ;}
}

2.12 复用大说明

其实很多的接口代码都是不用从头到尾一行行的实现,我们可以复用已经实现的函数接口,例如,如果我们已经实现了SeqListInsert,从这个角度上说,其实头插SeqListPushFront(ps,x)就是SeqListInsert(ps,0,x),尾插SeqListPushBack(ps,x)就是SeqListInsert(ps,ps->_size,x)。同理尾删和头删也可以用SeqListErase这个通用接口实现。

所以我们可以复用,不用再去实现尾插和头插了,代码如下所示。

//如果我们先实现出来 SeqListInsert和SeqListErase 这两个接口
void SeqListPushFront(SeqList* ps, SLDataType x)
{SeqListInsert(ps,0,x);
}
void SeqListPopFront(SeqList* ps)
{SeqListErase(ps,0);
}
void SeqListPushBack(SeqList* ps, SLDataType x)
{SeqListInsert(ps,ps->_size,x);
}
void SeqListPopBack(SeqList* ps)
{SeqListErase(ps,ps->_size-1);
}

2.13测试大说明(应用上可以有查找删除面)

我们应该对每一个接口进行测试,就是说写一个接口就要测试一个接口写的正确,因为写一点点,就测试一点点,我们很容易就发现问题出在哪里,而如果写了几百行代码再调试,会有许多的错误夹在在一起,这时候更加难办!所以其实写一点点,测试一点点,这其实在节约我们的时间!一定要写一点点,就测试一点点

测试的时候我们采用单元测试,具体的形式是,不直接写代码到main函数当中,而是封装几个Test函数,分别在main函数中跑,这样非常方便我们进行测试,代码也更加清晰规范:

void SeqListTest4()
{SeqList sq;SeqListInit(&sq);SeqListInsert(&sq, 0, 55);SeqListInsert(&sq, 0, 66);SeqListInsert(&sq, 0, 77);SeqListInsert(&sq, 0, 88);SeqListInsert(&sq, 0, 99);SeqListPrint(&sq);SeqListDestroy(&sq);
}
int main()
{/*SeqListTest1();*//*SeqListTest2();*//*SeqListTest3();*///SeqListTest4();*/SeqListTest5();return 0;
}

3.* 未来写数据结构注意

一定要按照这个逻辑步骤来,细致的思考与画图后,再进行书写,可以减少很多不必要的麻烦。

数据结构的起航,用C语言实现一个简约却不简单的顺序表!(零基础也能看懂)相关推荐

  1. c语言创建空顺序表的程序,用C语言编写一个完整的程序,实现顺序表的建立、插入、删除、输出等基本运算。...

    #include #include #define maxsize 30 typedef int datatype; typedef struct seqlist{ datatype data[max ...

  2. c语言 三子棋详细解析 (零基础也能看懂)附源码 c语言小游戏

    代码运行结果如下 代码实现 test.c(测试游戏的逻辑) game.h(关于游戏相关的函数声明符号声明)头文件包含的 game.c游戏相关函数的实现 test.c 游戏如何玩 希望游戏玩完一把还可以 ...

  3. 在顺序表中第五个位置插入一个元素9,实现顺序表插入的基本操作,输出顺序表中所有元素

    题目 在顺序表中第五个位置插入一个元素9,实现顺序表插入的基本操作,输出顺序表中所有元素 #include<iostream>using namespace std; #define OK ...

  4. proteus如何添加stm32_一个应用软件程序员的单片机STM32零基础入门

    为什么开始考虑搞嵌入式开发? 近5年来,从云计算.大数据到机器学习.AI,各种新潮的技术概念一波--接一波. 作为一名好奇心旺盛的软件程序员,每一个技术概念流行起来都会去凑凑热闹. 但,在我的技术栈里 ...

  5. 一个包含学生信息的顺序表

    #include <stdio.h> #include <stdlib.h> #include <string.h>#define MAXSIZE 100 /*单个 ...

  6. CQUPT数据结构作业2.1:设有一如下定义的SqList类型的顺序表,将其中的数据元素按递增顺序排列.试写一算法,将x插入到顺序表的适当位置,以保持该表的有序性

    加粗样式 2.1设有一如下定义的SqList类型的顺序表,将其中的数据元素按递增顺序排列.试写一算法,将x插入到顺序表的适当位置,以保持该表的有序性; typedef struct{int *elem ...

  7. 零基础的同学看过来,如何系统学习前端,只要你掌握了,学习web前端的思路就打开了,为以后成为高级前端工程师做一个铺垫

    软件开发工程师在行业外大众的眼里, 或许是一个出众的职业,收入不低, 技术含量还挺高,就连我自己刚入行时也是这么认为的,但事实上并不确切.任何行业中,只要是在金字塔顶端的那部分,都是令人羡慕的,然而, ...

  8. 散列表删除一个元素c语言,分享一个简单高效的哈希表C语言实现

    hashtable是一种非常实用的数据结构,尤其数据量相当大的时候,当然其中很关键的一点是hash算法.之前看redis数据库源码它里面实现hash的算法为murmurhash3,后来发现了这个xxh ...

  9. 数据结构题集(c语言版)第2章:线性表

    2.10 从顺序表中a中删除第i个元素起的k个元素 #include <stdio.h> #include <stdlib.h> #define TRUE 1 #define ...

最新文章

  1. 使用傅里叶变换进行图像边缘检测
  2. oracle 之 EXP、IMP 使用简介
  3. Redis Lua脚本中学教程(下)
  4. 多元价值呼唤教育性父母
  5. PropertyPathFacoryBean获取对象的值
  6. android获取自定义属性,android 自定义控件中获取属性的三种方式(转)
  7. 21天学通python第4章课后题答案_人工智能教程习题及答案第4章习题参考解答
  8. 【机器学习】决策树知识点小结
  9. 机器学习 处理不平衡数据_在机器学习中处理不平衡数据
  10. 在Sql Server 2005使用公用表表达式CTE简化复杂的查询语句
  11. Android UI之困 横跨四个屏幕的战争
  12. pulsar 容量_[Apache Pulsar] 企业级分布式消息系统-Pulsar入门基础
  13. ASP.NET Web API 2基于令牌的身份验证
  14. bat转换成exe文件:bat2exe
  15. [UE4]委托代理:单播委托,多播委托,动态单播委托,动态多播委托,事件
  16. 加速度传感器和角度传感器
  17. rabbit 的使用方法
  18. 大学里青年教师待遇真的很低吗?
  19. windows7下使用mingw和msys编译JEPG源代码
  20. 不使用循环,求二进制中1的个数

热门文章

  1. matlab选修结课作业,matlab在高等数学中的应用结课作业
  2. Non-terminating decimal expansion; no exact representable decimal result
  3. 和大家一起分享人性中的感动!- 转老贴
  4. 安卓帧率FPS计算原理
  5. 互联网老辛2022年2月社群分享精华
  6. 字符串复制函数strcpy()
  7. C++求一个数的阶乘factorial(附完整源码)
  8. uniapp调用地图,进行位置查询,标记定位
  9. 肖博高中数学一对一补习高考数学强化直线与圆 题型总结|附带详细解析
  10. 领导讨厌这三种老实人,不被重用有内情,知道后悄悄改别声张