顺序表
声明:本文是我这个小菜鸟学习时记录下来的,如有意见,请提出,咱们友好探讨,共同进步,感谢。
顺序表其实是一种线性表
顺序表底层是数组,对数组进行了封装,实现了增删改查等接口
线性表结构特点:
物理结构:不一定是线性的(指的是元素不一定在内存中连续存放)
逻辑结构:一定连续
顺序表结构特点:
物理结构:线性
逻辑结构:线性
顺序表分类:静态和动态
静态有明显的缺点:空间给小了不够用;给多了又太过于浪费;分配不够灵活
所以咱一般用动态顺序表
方便:动态增容(成2到3倍增加)
实现顺序表的时候,最好是分两个文件,
一个.h文件,里面放着需要用到的头文件,顺序表的声明,顺序表需要的各种功能的实现函数的声明, 在这里我根据个人习惯,命名为seqlist.h
另一个是.c或者.cpp文件,该文件直接#include”seqlist.h” 引用自己写的头文件,简洁,且条理清晰 该.c或者.cpp文件内,写顺序表需要的各种功能的实现函数
如果要使用顺序表以及它的各种功能,那么就新建一个.cpp或者.c文件,引用自己写的头文件(因为头文件中有相关函数的声明),就可以直接进行测试辣
要想实现顺序表的各种功能,咱们首先需要干嘛呢?
typedef struct SeqList
{
SqlDataType* memberlist; //此为顺序表的数组部分 用指针的原因:方便增容与存储数据
int size;
int box;
}SqL;
用顺序表之前,需要干什么呢?---》初始化
void SqLInit(SqL* ps) //顺序表初始化
{
ps->memberlist = NULL; //顺序表数组部分的初始化
ps->size = 0; //有效数据个数 --》初始化为0代表啥也没存
ps->box = 10; //当前的空间容量
}
有初始化,那自然也有销毁对吧!---》销毁
//顺序表的销毁
void SqLDestroy(SqL* ps)
{
//咱们写的是动态顺序表,会用到malloc calloc realloc这些函数,所以要手动释放,且必须注意,不能释放空指针NULL
if (ps->memberlist != NULL)
{
free(ps->memberlist);
}
ps->memberlist = NULL;
ps->size = 0;
ps->box = 0;
}
----------------------元素插入部分--------------------
头插:即把顺序表所有元素后移一位,然后在头部空出来的那个位置插入元素
void SqLPushHead(SqL* ps, SqLDataType x) //(顺序表地址,要插入的数据)
{
//阻止空指针的传入
assert(ps);
//首先检测空间够不够
if (ps->size == ps->box)
{
SqLExpanse(ps);
}
for (int i = ps->size - 1; i >= 0; i--)
{
ps->memberlist[i + 1] = ps->memberlist[i];
}
ps->memberlist[0] = x;
ps->size++;
}
尾插:在顺序表最后一个元素后面插入元素
void SqLPushBack(SqL* ps, SqLDataType x) //(顺序表地址,要插入的数据) --测试一下
{
//阻止空指针的传入
assert(ps);
if (ps->size == ps->box) //先检查一下,顺序表还有没有剩余空间
{
//进来了就是没剩余空间辣,需要申请
SqLExpanse(ps);
}
ps->memberlist[ps->size] = x; //size为有效元素个数,第size个有效元素下标为(size-1),所以下标为size的地方,就是我们尾插元素的地方
ps->size++; //有效元素又多了一个
}
中插:选定一个元素(以下标的方式),在其前/后插入元素
//3.中插--》在某个数据前/后插入数据 我这里就写在某个数据前插入吧
void SqLPushInside(SqL* ps, int position, SqLDataType x) //(顺序表地址,要找到的数据的下标,在找到的数据前/后插入的数据)
{
//阻止空指针的传入
assert(ps);
//限制position的大小
assert(position >= 0 && position <= ps->size); //position=size时
//首先检测空间够不够
if (ps->size == ps->box)
{
SqLExpanse(ps);
}
for (int i = ps->size-1; i >=position; i--)
{
ps->memberlist[i+1] = ps->memberlist[i];
}
ps->memberlist[position] = x;
ps->size++;
}
有插入元素,那自然会有删除元素
---------------元素删除部分-------------------
//尾删
void SqLDelBack(SqL* ps) //(顺序表地址,要插入的数据)
{
//阻止空指针的传入
assert(ps);
if (ps->size == 0)
{
printf("删除失败!顺序表中并未存放数据\n");
return;
}
ps->memberlist[ps->size] = 0; //size为有效元素个数,第size个有效元素下标为(size-1),所以下标为size的地方,就是我们尾插元素的地方
ps->size--; //有效元素又少了一个
}
//头删
void SqLDelHead(SqL* ps) //(顺序表地址,要插入的数据)
{
//阻止空指针的传入
assert(ps);
if (ps->size == 0)
{
printf("删除失败!顺序表中并未存放数据\n");
return;
}
for (int i = 0; i < ps->size-1; i++)
{
ps->memberlist[i] = ps->memberlist[i+1];
}
ps->size--;
}
//中删
void SqLDelInside(SqL* ps, int position)
{
//阻止空指针传入
assert(ps);
for (int i = position; i < ps->size-1; i++)
{
ps->memberlist[i] = ps->memberlist[i + 1];
}
ps->size--;
}
查找数据
//顺序表的查找 --导向可以是数据本身,也可以是下标 --》在顺序表的应用里面,
//我这里写的是数据本身为导向
void SqLSearch(SqL* ps, SqLDataType x)
{
//阻止空指针传入
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->memberlist[i] == x)
{
printf("找到了!\n");
printf("该数据下标为%d\n",i);
return;
}
}
//走到这说明没找到
printf("该数据不存在!\n");
return;
}
到这里顺序表的基础增删改查都完事辣,是时候整些更高级的东西了
----------------------------顺序表的应用:通讯录-----------------------------
SqLDataType x ---x的数据类型,可以是int,可以是char等等,那当然也可以是结构体类型
想象一下,顺序表的每个位置上,存着一个结构体变量,该变量里存储着联系人的年龄等个人信息
如下图
在这里,
我们又要新建一个头文件叫contact.h,用于通讯录中:
- 储存个人信息的结构体变量初始化
- 各种功能函数的声明
还要新建一个源文件contact.cpp,用于:各种函数的实现
在顺序表的基础上,做出如下改变:
- 把存储的数据类型改成“个人信息”这个结构体
- 在顺序表功能的实现函数的基础上稍微加一点东西,让他适配通讯录
不多bb,直接上代码
Seqlist.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//typedef int SqLDataType;
typedef struct PersonalInformation SqLDataType;
typedef struct SeqList
{
SqLDataType* memberlist; //此为顺序表的数组部分 用指针的原因:方便增容与存储数据
int size; //有效内容个数
int box; //当前最大空间大小
}SqL;
//顺序表的初始化
void SqLInit(SqL* ps);
//顺序表的销毁
void SqLDestroy(SqL* ps);
//顺序表的空间申请-->扩容
void SqLExpanse(SqL* ps);
//顺序表的数据插入
//1.尾插
void SqLPushBack(SqL* ps, SqLDataType x);
//2.头插
void SqLPushHead(SqL* ps, SqLDataType x);
//3.中插--》在某个数据前/后插入数据
void SqLPushInside(SqL* ps, int position, SqLDataType x);
//顺序表的数据删除
//尾删
void SqLDelBack(SqL* ps);
//头删
void SqLDelHead(SqL* ps);
//中删
//void SqLDelInside(SqL* ps, int position);
通讯录特供版中删
void SqLDelInside(SqL* ps,char name);
//改
//通讯录特供版改数据
void SqLChangeInside(SqL* ps, char named);
//顺序表的查找 --导向可以是数据本身,也可以是下标 --》在顺序表的应用里面,
void SqLSearch(SqL* ps, char named);
//顺序表的打印
void SqLPrint(SqL* ps);
-----------------------------------------------------------------------------------------------------------------
Contact.h
#include"seqlist.h"
#define NAME_MAX 20
#define GENDER_MAX 20
#define PHONENUM_MAX 20
#define ADDRESS_MAX 20
typedef struct PersonalInformation //这个结构体,作为顺序表中储存的元素
{
char name[NAME_MAX]; //名字
char gender[GENDER_MAX]; //性别
int age; //年龄
char phonum[PHONENUM_MAX];
char address[ADDRESS_MAX];
}Personel;
//菜单
void menu();
//通讯录创建
void ContactInit(SqL *sl);
//通讯录销毁
void ContactDestroy(SqL *sl);
//增加数据 ---》使用尾插
void ContactInput(SqL *sl);
//删除数据 --》选择删除 --》输入名字,找到就删,找不到就报个查不到
void ContactDel(SqL *sl);
//改变数据 --》选择改变 --》输入名字,找到就可以改,找不到就报个查不到
void ContactChange(SqL *sl);
//查询数据 --》输入名字查找
int ContactSearchByname(SqL *sl);
//打印数据
void ContactPrint(SqL *sl);
//把写入的联系人信息保存到文件
void ContactSavetoFile(SqL *sl);
//从文件读取已保存的通讯录
void ContactLoadFile(SqL *sl);
以上都是头文件,里面都是函数的声明,需要用到的头文件的包含等
接下来看源文件
Contact.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
void menu()
{
int n = 0;
SqL sl;
ContactInit(&sl);
ContactLoadFile(&sl);
while (1)
{
printf("-------------------------------------------------------\n");
printf("-------------------------------------------------------\n");
printf("--------------------Contact List-----------------------\n");
printf("-------------------------------------------------------\n");
printf("-------------------------------------------------------\n");
printf("------------------1.Add Contacts-----------------------\n");
printf("-------------------------------------------------------\n");
printf("------------------2.Delete Contacts--------------------\n");
printf("-------------------------------------------------------\n");
printf("------------------3.Change Infomation------------------\n");
printf("-------------------------------------------------------\n");
printf("------------------4.Show Contacts--------------------\n");
printf("-------------------------------------------------------\n");
printf("------------------ElseChoice====>exit------------------\n");
printf("-------------------------------------------------------\n");
printf("-------------------------------------------------------\n");
printf("请选择:");
scanf("%d", &n);
if (n == 1)
{
ContactInput(&sl);
continue;
}
else if (n == 2)
{
ContactDel(&sl);
continue;
}
else if (n == 3)
{
ContactChange(&sl);
continue;
}
else if (n == 4)
{
ContactPrint(&sl);
continue;
}
else
{
ContactSavetoFile(&sl);
ContactDestroy(&sl);
return;
}
}
}
//通讯录创建
void ContactInit(SqL *sl)
{
SqLInit(sl);
}
//通讯录销毁
void ContactDestroy(SqL *sl)
{
SqLDestroy(sl);
}
//增加数据 ---》使用尾插
void ContactInput(SqL *sl)
{
SqLDataType x;
printf("请输入联系人姓名\n");
scanf("%s", &x.name);
printf("请输入联系人性别\n");
scanf("%s", &x.gender);
printf("请输入联系人年龄\n");
scanf("%d", &x.age);
printf("请输入联系人电话号码\n");
scanf("%s", &x.phonum);
printf("请输入联系人住址\n");
scanf("%s", &x.address);
SqLPushBack(sl, x);
}
//删除数据 --》选择删除 --》输入名字,找到就删,找不到就报个查不到
void ContactDel(SqL *sl)
{
int pos = ContactSearchByname(sl);
SqLDelInside(sl, pos);
printf("删除完成!\n");
printf("\n");
}
//改变数据 --》选择改变 --》输入名字,找到就可以改,找不到就报个查不到
void ContactChange(SqL *sl)
{
//错误示范-------------------------------------------------------------
// /*char named[NAME_MAX];
// printf("请输入联系人姓名\n");
// scanf("%s",named);*/
// SqLChangeInside(sl, named[0]);
//
// /*int i = 0;
// int j = 0;
//
// for (i = 0; i < sl->size; i++)
// {
// if (0 == strcmp(sl->memberlist[i].name, named))
// {
// printf("找到了!\n");
// j = i;
// }
// }
//
// if (j == sl->size - 1)
// {
// printf("您要修改的联系人数据不存在!\n");
// return;
// }
//
// printf("请输入更改后的联系人姓名\n");
// scanf("%s", sl->memberlist[j].name);
// printf("请输入更改后的联系人性别\n");
// scanf("%s", sl->memberlist[j].gender);
// printf("请输入更改后的联系人年龄\n");
// scanf("%d", &sl->memberlist[j].age);
// printf("请输入更改后的联系人电话号码\n");
// scanf("%s", sl->memberlist[j].phonum);
// printf("请输入更改后的联系人住址\n");
// scanf("%s", sl->memberlist[j].address);
//*/-------------------------------------------------------------------------
int ret = ContactSearchByname(sl);
if (ret < 0)
{
printf("该联系人不存在!\n");
return;
}
printf("请输入更改后的联系人姓名\n");
scanf("%s", sl->memberlist[ret].name);
printf("请输入更改后的联系人性别\n");
scanf("%s", sl->memberlist[ret].gender);
printf("请输入更改后的联系人年龄\n");
scanf("%d", &sl->memberlist[ret].age);
printf("请输入更改后的联系人电话号码\n");
scanf("%s", sl->memberlist[ret].phonum);
printf("请输入更改后的联系人住址\n");
scanf("%s", sl->memberlist[ret].address);
//测试用
printf("更改成功\n");
}
//查询数据 --》输入名字查找
int ContactSearchByname(SqL *sl)
{
char named[NAME_MAX];
printf("请输入要查找的联系人姓名: \n");
scanf("%s", named);
int i = 0;
for (i = 0; i < sl->size; i++)
{
if (0 == strcmp(sl->memberlist[i].name, named))
{
printf("找到了!\n");
return i;
}
}
//到这里了就是没找到
return -1;
}
//打印数据
void ContactPrint(SqL *sl)
{
SqLPrint(sl);
}
//把写入的联系人信息保存到文件
void ContactSavetoFile(SqL *sl)
{
FILE* pfdata = fopen("text.txt", "wb"); //开二进制文件
if (pfdata == NULL) //判断上一步是否成功
{
perror("pfdata");
return;
}
for (int i = 0; i < sl->size; i++)
{
fwrite(sl->memberlist + i, sizeof(Personel), 1, pfdata); //写入二进制文件
}
printf("数据已存入文件\n");
fclose(pfdata);
pfdata = NULL;
}
//从文件读取已保存的通讯录
void ContactLoadFile(SqL *sl)
{
FILE* pfdata = fopen("text.txt", "rb"); //开二进制文件
if (pfdata == NULL)
{
perror("pfdata");
return;
}
Personel tem;
Personel *temp1 = &tem; //中继器
while (fread(temp1, sizeof(Personel), 1, pfdata)) //这里用不了for 用while,为真就一直读,读到没有为止
{
SqLPushBack(sl, *temp1);
}
printf("文件中的数据已导入\n");
fclose(pfdata);
pfdata = NULL;
}
结尾处有两个文件操作函数,作用是把每次通讯录里面写的东西保存到test.txt这个文件,下次打开通讯录的时候还能读出来
通讯录中的函数基本上都是基于顺序表的,通讯录本质上就是顺序表,顺序表中储存的元素是包含了个人信息的结构体
本文到此结束,如有错误,敬请指正