2026/4/14 22:18:26
网站建设
项目流程
网站建设怎么记账,住房和建设部网站首页,建站哪个便宜,万网网站制作前言#xff1a;数据结构是我们学习编程的核心灵魂#xff0c;前面我们主要只是学习了编程语言的语法#xff0c;但我们在实际写代码时会发现不知道怎么写#xff0c;数据结构解决的正是这个问题。数据结构研究的正是数据的组织、管理与存储。下面我将从数据结构中的顺序表…前言数据结构是我们学习编程的核心灵魂前面我们主要只是学习了编程语言的语法但我们在实际写代码时会发现不知道怎么写数据结构解决的正是这个问题。数据结构研究的正是数据的组织、管理与存储。下面我将从数据结构中的顺序表开始为大家讲解数据结构及顺序表的简单实践所以可以会有点长1.顺序表的简单概念介绍1.1线性表线性表的定义为n个有相同特性的数据元素组成的有限序列。而我们今天要学习的顺序表就是属于线性表中一种。线性表在逻辑上连续的一天直线但在物理结构上不一定是连续的而我们的顺序表在物理结构上也是连续的因为顺序表的底层是基于数据来实现的而数组在内存中的元素是连续存放的所以我们可以知道顺序表在物理结构上是连续的。举个例子在超市买完东西准备结账时可以看到有很多人排队但有些队很整齐有些队不整齐但他们都需要在前一个人结完账之后才能轮到下一个人所以在逻辑结构上就是相同的只是在物理层面上不是整齐的连续罢了这两个队都同样是队列线性表1.2顺序表的分类前面我们也说过了顺序表的底层为数组而顺序表只是对这个底层的数据进行了一个封装提供了一些接口来实现对数组存放的数据进行增删改查的一些接口。而顺序表又分为两类第一类为静态顺序表第二类为动态顺序表。我们知道我们在定义一个数组的时候会定义数组的大小而顺序表的底层为数组而静态顺序表正是一开始就把顺序表的大小给它固定了。而动态顺序表顾名思义是在实际的使用过程中我们根据自身需要为静态顺序表增加了自动扩容的功能静态顺序表的定义typedef struct Seqlist { SLDDataType arr[100];//定长数组 int size;//有效数据大小 }SL;动态顺序表定义typedef struct Seqlist { SLDDataType* arr; int size;//有效数据大小 int capacity;//空间大小 }SL;光靠直觉就可以知道还是动态顺序表更好实现也更加复杂静态顺序表不太好把握需要的空间的大小大了会浪费空间小了又无法满足要求甚至造成数据丢失等严重的问题所有我下面将主要讲解动态顺序表及具体的实现运用。2.顺序表实现前期准备在写代码之前要把代码根据功能分类为三个子文件1头文件子文件用于存放头文件及声明函数。2实现函数的子文件用于函数的定义和实现。3测试子文件用于测试功能是否正常。测试是非常重要的看因为我之前写过一个小扫雷就没有测试结果导致出了错误排查大半天所以最好是写一个功能测试一个功能。这样分类可以防止项目结构混乱可以提高代码可读性也方便调整修改代码下面我将逐步带大家来实现顺序表顺序表的定义及初始化首先我们将要用到的头文件包含在这个“Swqlist.h”这个文件下后面我们要调用的函数库的头文件就都包含在这个文件下就好了其他文件想要引用的话只需要包含一个头文件#include Seqlist.h就足够了。下面是我用到的头文件可以参考我的需要什么的话再自己添加就完了#include stdio.h #include stdlib.h #include assert.h #include ContactBook.h//用于后面实现通讯录 #include string.h下面是定义顺序表typedef struct Seqlist { SLDDataType* arr;//指向存放的数据类型 int size;//有效空间大小 int capacity;//空间大小 }SL;这里我把这个结构体类型重定义为了SL后面就不用这么繁琐了因为还不知道要存储的数据类型是什么就先把原来的int也重命名的方便后面的修改。下面是我们要实现的功能可以现在Seqlist.h文件中都声明了后面再去Seqlist.c文件中把各个函数逐一实现//初始化 void SIlT(SL* ps); //销毁 void SLDestroy(SL* ps); //判断空间大小是否满足并申请空间 void SLIsCapaciyaEnough(SL* ps); //头插 void SLpushFront(SL* ps, SLDDataType x); //尾插 void SLpushBack(SL* ps, SLDDataType x); //头删 void SLPopFront(SL* ps); //尾散 void SLPopBack(SL* ps); //打印 void SLPrint(SL ps); //指定位置插入数据 void SLQInsert(SL* ps, int pos, SLDDataType x); //指定位置删除数据 void SLQErase(SL* ps, int x); //查找数据 int SLFind(SL* ps, SLDDataType x);别看好像挺多的但实际实现下来都挺简单初始化及销毁void SIlT(SL* ps) { ps-arr NULL; ps-capacity 0; ps-size 0; } //销毁函数定义 void SLDestroy(SL* ps) { if (ps-arr) { free(ps-arr); } ps-arr NULL; ps-capacity ps-size 0; }判断空间是否足够并扩容void SLIsCapaciyaEnough(SL* ps) { assert(ps); if (ps-capacity ps-size) { int newCapecity ps-capacity 0 ? 4 : 2 * ps-capacity; SLDDataType* tmp (SLDDataType*)realloc(ps-arr, newCapecity * sizeof(SLDDataType)); if (tmp NULL) { perror(realloc fail!); exit(1); } ps-arr tmp; ps-capacity newCapecity; } }后面还有很多个函数要用到这个函数所以我就先把它封装起来了这个函数的核心逻辑就是当有效空间大小等于顺序表空间大小时我们还想执行如添加的操作时空间就不够用了所以我们需要对这个顺序表进行扩容扩容的大小一般是二倍或者是四倍这里我就选择了二倍为扩容后的大小然后在通过realloc函数进行扩容就好了。当然不能传个空指针过来所以我用了assert进行断言。头插void SLpushFront(SL* ps, SLDDataType x) { assert(ps); SLIsCapaciyaEnough(ps); for (int i ps-size; i 0; i--) { ps-arr[i] ps-arr[i - 1]; } ps-arr[0] x; ps-size; }在顺序表头部插入数据之前要先判断顺序表的空间还够不够所以我们要先调用SLIsCapaciyaEnough函数进行扩容在头部插入数据之前要将顺序表的数据整体向后移动一位来保证原来头部的数据不被覆盖掉所以我这里通过一个for循环来实现了这个目的最后再将数据输入头部位置就可以了尾插void SLpushBack(SL* ps, SLDDataType x) { assert(ps); SLIsCapaciyaEnough(ps); ps-arr[ps-size] x; ps-size; }知道了头插的话尾插更简单在尾部直接插入数据即可当然在插入数据之前也同样要判断空间是否满足并扩容头删void SLPopFront(SL* ps) { assert(ps); assert(ps-size); for (int i 0; i ps-size - 1; i) { ps-arr[i] ps-arr[i 1]; } --ps-size; }要删除头部函数先判断空间大小是否为零在通过for循环将数据整体前移一位这样我们想删除的数据就会被覆盖掉达到删除头部数据的目的当然有效数据也就少了一个最后再对size--就好了。尾删void SLPopBack(SL* ps) { assert(ps); assert(ps-size); --ps-size; }尾删直接把有效数据size--就好了有人喜欢先赋值为某个数字如-1在把size--但没有必要直接size--就可以了指定位置插入数据void SLQInsert(SL* ps, int pos, SLDDataType x) { assert(ps-arr); assert(pos 0 ps-size pos); SLIsCapaciyaEnough(ps); for (int i ps-size; i pos; i--) { ps-arr[i] ps-arr[i - 1]; } ps-arr[pos] x; ps-size; }pos是表示的是数据插入的位置所以我们不能输入个负数有效空间的大小也要大于等于pos的大小所以我添加了断言来增加代码的健壮性。再将pos后面的数据整体向后移动一位给要插入的数据留个位置这里我同样通过for循环来完成这个目的。最后在pos处插入数据并把size即可指定位置删除数据void SLQErase(SL* ps, int pos) { assert(ps); assert(pos 0 ps-size pos); for (int i pos; i ps-size - 1; i) { ps-arr[i] ps-arr[i 1]; } --ps-size; }这里也是同理直接把pos后的数据整体先前移动一位把原来pos处的数据覆盖掉就可以达成我们删除指定位置数据的目的了查找数据int SLFind(SL* ps, SLDDataType x) { assert(ps); for (int i 0; i ps-size; i) { if (ps-arr[i] x) { return i; } } return -1; }这里我们直接遍历整个顺序表找到该数据再把这个数据的下标位置返回就可以了3.基于顺序表的通讯录实现我这里要实现的通讯录是基于顺序表实现的前面我们写的顺序表就可以作为通讯录的底层只需要少量代码就可以实现我们的通讯录了3.1通讯录结构体的定义及其声明通讯录要存放并修改联系人的信息所以这里要先定义一个结构体来存放联系人的信息并将这个结构体类型重命名为了ContactInfotypedef struct ContactInfo { char name[NAME_MAX];//名字 char gender[GENDER_MAX];//性别 int age;//年龄 char phone[PHONE_MAX];//电话 char address[ADDRESS_MAX];//地址 }ContactInfo;为了方便修改所以我这里我用#difine来定义常量#define NAME_MAX 20 #define GENDER_MAX 10 #define PHONE_MAX 20 #define ADDRESS_MAX 100前面在Seqlist.h中我不清楚顺序表要存放的数据类型所以我用了SLDDataType来表示数据类型当确定要存放的数据类型时我们就可以通过typedef在Seqlist.h中定义SLDDataType为ContactInfotypedef ContactInfo SLDDataType;并在ContactBook.h中把struct Seqlist重命名为了Contacttypedef struct Seqlist Contact;下面是我们要实现的通讯录功能及声明同样我们还是创建两个文件分别为1ContactBook.h用于声明函数及其存放头文件2ContactBook.c用于实现通讯录这是通讯录要用到的函数可以先在ContactBook.h中声明函数//通讯录初始化 void ContactInit(Contact* con); //通讯录销毁 void ContactDestroy(Contact* con); //通讯录添加数据 void ContactAdd(Contact* con); //通讯录删除数据 void ContactRemove(Contact* con); //通讯录的修改 void ContactModify(Contact* con); //通讯录的查找 void ContactFind(Contact* con); //展示通讯录所有数据 void ContactShowAll(Contact* con);3.2通讯录的函数实现通讯录的初始化及销毁//初始化 void ContactInit(Contact* con) { SIlT(con); } //销毁 void ContactDestroy(Contact* con) { SLDestroy(con); }直接调用我们在顺序表那里写的初始化及销毁函数就可以了通讯录添加联系人void ContactAdd(Contact* con) { ContactInfo info; //用户添加数据 printf(请输入要添加的联系人姓名\n); int tmp1 scanf(%s, info.name); printf(请输入要添加的联系人性别\n); int tmp2 scanf(%s, info.gender); printf(请输入要添加的联系人年龄\n); int tmp3 scanf(%d, info.age); printf(请输入要添加的联系人电话\n); int tmp4 scanf(%s, info.phone); printf(请输入要添加的联系人地址\n); int tmp5 scanf(%s, info.address); //在通讯录中输入数据 SLpushBack(con, info); }我们创建了一个联系人结构体数据类型的info用于存放我们要添加的联系人信息我这里之所以创建很多临时变量来接受scanf的返回值是因为在vs中不接受返回值就会报警告所以我就加了实际上也可以不接受返回值最后再通过之前我们在顺序表里写的尾插函数来存放联系人数据就可以了当热也可以选择用头插但我这里就使用尾插作为例子了根据姓名查找函数int FindByName(Contact* con, char name[]) { for (int i 0; i con-size; i) { if (0 strcmp(con-arr[i].name, name)) { return i; } } //没找到 return -1; }我们想要查找某个联系人时可以自由选择根据什么数据年龄、性别来查找满足要求的联系人我这里就以姓名为查找依据通过strcmp函数来查找联系人方便后面我们查找或者删除修改联系人找到了就返回该联系人在顺序表中的位置找不到就返回一个-1通讯表删除联系人void ContactRemove(Contact* con) { char name[NAME_MAX] { 0 }; printf(请输入要删除的联系人姓名\n); int rm scanf(%s, name); int find FindByName(con, name); if (find 0) { printf(要删除的联系人不存在\n); return; } //存在 SLQErase(con, find); printf(删除成功!\n); }当联系人存在时就可以通过之前我们在顺序表中写的指定位置删除函数来删除这个联系人数据通讯录的修改void ContactModify(Contact* con) { char name[NAME_MAX] { 0 }; printf(请输入要修改的联系人姓名\n); int rm scanf(%s, name); int find FindByName(con, name); if (find 0) { printf(要修改的联系人不存在\n); return; } printf(请输入新的联系人姓名\n); int sz1 scanf(%s, con-arr[find].name); printf(请输入新的联系人性别\n); int sz2 scanf(%s, con-arr[find].gender); printf(请输入新的联系人年龄\n); int sz3 scanf(%d, con-arr[find].age); printf(请输入新的联系人电话\n); int sz4 scanf(%s, con-arr[find].phone); printf(请输入新的联系人地址\n); int sz5 scanf(%s, con-arr[find].address); printf(修改成功!\n); }我们通过通讯录查找函数找到对应的联系人对这个联系人的数据进行修改当然前提是要先判断该联系人是否存在通讯录的查找void ContactFind(Contact* con) { char name[NAME_MAX] { 0 }; printf(请输入要查找的联系人姓名\n); int rm scanf(%s, name); int find FindByName(con, name); if (find 0) { printf(要查找的联系人不存在\n); return; } //存在 printf(%10s %15s %15s %15s %15s\n, 名字, 性别, 年龄, 电话, 地址); printf(%10s %15s %15d %15s %15s\n, con-arr[find].name, con-arr[find].gender, con-arr[find].age, con-arr[find].phone, con-arr[find].address ); }这里我们同样通过通讯录查找函数找到对应联系人并把联系人数据通过printf函数打印出来就可以了展示通讯录的所有联系人void ContactShowAll(Contact* con) { //打印表头 printf(%10s %15s %15s %15s %15s\n, 名字, 性别, 年龄, 电话, 地址); for (int i 0; i con-size; i) { printf(%10s %15s %15d %15s %15s\n, con-arr[i].name, con-arr[i].gender, con-arr[i].age, con-arr[i].phone, con-arr[i].address ); } }这里通过for来遍历整个顺序表将里面存放的联系人数据分别打印出来就可以了3.3通讯录展示写完前面的代码代表着通讯录几乎以及完成了下面我们在test.c文件中写个简单的接口来简单测试一下void ContactBookShowMenu() { printf(通讯录\n); printf(**** 1.增加联系人 2.删除联系人 ****\n); printf(**** 3.修改联系人 4.查找联系人 ****\n); printf(**** 5.展示联系人 0. 退出 ****\n); printf(\n); } int main() { int op -1; Contact con; ContactInit(con); do { ContactBookShowMenu(); printf(请选择你的操作-); int tmp scanf(%d, op); printf(\n); switch (op) { case 1: ContactAdd(con); break; case 2: ContactRemove(con); break; case 3: ContactModify(con); break; case 4: ContactFind(con); break; case 5: ContactShowAll(con); break; case 0: printf(退出通讯录\n); break; default: printf(输出错误请重新输入\n); break; } } while (op ! 0); ContactDestroy(con); return 0; }添加联系人这里我们看看是否存放进去了可以看到数据成功的存放进去想测试其他功能可以自己去试试如果想在程序关闭时不丢失数据可以运用一下我们之前的文件操作将数据保存在文件之中我就先到这里了完