目录
引言
本文详细介绍了顺序表的相关知识,并基于此实现了可无限扩容,可实现数据增,删,查,改,功能的通讯录。
顺序表知识回顾
线性表
顺序表是线性表的一种,而线性表是一种实际中被广泛使用的数据结构,常见的线性表有:顺序表,栈,链表,队列,字符串........。线性表在逻辑上是线性结构,但在物理上不一定是线性的,比如链表,线性表在物理存储上,通常是数组和链式结构的形式,而本文要讨论的顺序表在逻辑上和物理上都是线性的。因为顺序表的底层就是数组,数组在内存上是连续存放的。
顺序表的分类与缺陷
顺序表分为动态顺序表和静态顺序表两种,静态顺序表,即能储存的空间大小是固定的顺序表,动态顺序表,即可以根据实际需要,动态的在内存中申请空间,以满足需要的一种数据结构。动态顺序表,虽然较静态顺序表功能更强,但是仍有以下几点缺陷:
1,动态申请的空间即扩容时有可能会出现浪费。
2,在头插或者指定位置插入数据时,需要成段的移动数据时间复杂度为O(N)
3,增容时,需要申请新空间,拷贝旧数据,释放旧空间,这会有不少的消耗。
动态顺序表的代码实现
为了代码的规范性,我们需要分别建立SeqList.c与SeqList.h两个文件如图所示
Seq即Sequence
顺序表的定义
动态顺序表分为储存数据的数组,有效数据个数,总空间大小三个部分,这里为了之后方便更改储存的数据类型,使用typedef进行重定义int,并将结构体类型简写为SL。
顺序表的思考
前面说过,顺序表的底层就是数组,在顺序表的定义中就很好的体现了这一点。当我们定义一个普通的数组时,我们会确定其首元素的地址,这里就是SLDatatype,我们会确定其最大容量,这里就是capacity。如果定义的同时,我们初始化数组中一部分数据,就会出现有效元素个数,这里就是size。所以顺序表的就是将数组的各项构成元素拆开,并使数组的容量可根据需要自动扩容,同时提供增删查改等方法的一种数据结构。
顺序表的方法
涉及的方法由头文件可清晰的看到:
-
初始化与销毁
顺序表有三个参数一个指针,两个整型变量用于记录顺序表的有效值与目前申请的总内存空间。所以需要让指针指向NULL,避免野指针。而capacity与size的初始化对后续代码的影响巨大。
第一种方法:将两值全置为0,这种做法较简单,但会给顺序表的扩容带来而外的工作量。因为扩容使用的函数realloc的第二个参数是新空间的字节数,转换成代码就是 sizeof(SLDatatype) * 2 * ps->capacity,这里需要乘以capacity,如果是第一次扩容并且初始化使用的是第一种方法,capacity的值为零,程序是跑不起来的,所以需要而外处理,如下:
第二种方法:就是将size的值初始化为2,相应的capacity的值初始化使用malloc申请两个空间,这样在后续扩容是会方便一些,如下:
销毁方法如下:
-
增
数据的增加分为头插,尾插,指定位置之前插入。值得注意的一点是,因为我们想影响的是顺序表,所以需要传顺序表的指针,进行传址调用,如果函数参数未使用指针,程序只是将原顺序表拷贝一份,进行处理这不是我们想要的。
对于每次插入,都需要判断是否需要扩容,所以这里将扩容独立封装成一个函数,同时为提高代码的壮硕性,使用assert函数判断ps不为空,而头插和指定位置插入只需要额外添加memmove函数就能轻松搞定,当然也可以使用for循环遍历,如下:
-
删
删除分为头删,尾删,指定位置删除,结构与头插,尾插,指定位置插是耦合的。即插入时判断数组大小是否足够,改为删除时判断数组是否为空,同样的头删,指定位置删除需要使用memmove函数能够解决,如下
-
查
这一方法我们希望达到能够返回目标数据结构的下标,所以参数有顺序表(这里不需要指针),查找数据的函数指针(这里需要用户自己提供),目标数据结构。
这里需要注意,如果查找失败,需要返回-1这样的无效数据。
顺序表代码:
Seqlist.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
typedef int SLDatatype;
typedef struct SeqList
{
SLDatatype * arr;
int size;
int capacity;
}SL;
//顺序表的初始化
void SLInit(SL* ps);
//顺序表的打印
void SLPrint(SL p, void (*FucPrint)(SLDatatype p));
//顺序表的内存申请
void SLBuy(SL* ps);
//顺序表的尾插
void SLPushBack(SL* ps, SLDatatype x);
//顺序表的头插
void SLPushFront(SL* ps, SLDatatype x);
//顺序表指定位置之前的添加
void SLInsert(SL* ps, SLDatatype x, int pos);
//顺序表尾删
void SLPopBack(SL* ps);
//顺序表的头删
void SLPopFront(SL* ps);
//顺序表的指定位置的删除
void SLErase(SL* ps, int pos);
//顺序表的元素的查找
int SLFind(SL ps, int(*Fuc)(SLDatatype x, SLDatatype y), SLDatatype n);
//顺序表的销毁
void SLDestory(SL* ps);
Seqlist.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"
//顺序表的初始化
void SLInit(SL* ps)
{
ps->size = 0;
ps->capacity = 2;
ps->arr = malloc(sizeof(SLDatatype)*2);
}
//顺序表的打印
void SLPrint(SL ps, void (*FucPrint)(SLDatatype p))
{
assert(FucPrint);
for (int i = 0; i < ps.size; i++)
{
(*FucPrint)(*(ps.arr + i));
}
printf("\n");
}
//顺序表的内存申请
void SLBuy(SL* ps)
{
if (ps->size == ps->capacity)
{
SLDatatype* Tmp = (SLDatatype*)realloc(ps->arr, sizeof(SLDatatype) * 2 * ps->capacity);
if (Tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->capacity *= 2;
ps->arr = Tmp;
}
}
//顺序表的尾插
void SLPushBack(SL* ps, SLDatatype x)
{
assert(ps);
SLBuy(ps);
ps->arr[ps->size++] = x;
}
//顺序表的头插
void SLPushFront(SL* ps, SLDatatype x)
{
assert(ps);
SLBuy(ps);
memmove(ps->arr + 1, ps->arr, ps->size * sizeof(SLDatatype));
ps->arr[0] = x;
ps->size++;
}
//顺序表指定位置之前的添加
void SLInsert(SL* ps, SLDatatype x, int pos)
{
assert(ps&&0<=pos<=ps->size);
SLBuy(ps);
memmove(ps->arr + 1 + pos, ps->arr + pos, (ps->size - pos) * sizeof(SLDatatype));
ps->arr[pos] = x;
ps->size++;
}
//顺序表尾删
void SLPopBack(SL* ps)
{
assert(ps && ps->size);
ps->size--;
}
//顺序表的头删
void SLPopFront(SL* ps)
{
assert(ps && ps->size);
memmove(ps->arr, ps->arr + 1, sizeof(SLDatatype) * (ps->size - 1));
ps->size--;
}
//顺序表的指定位置的删除
void SLErase(SL* ps, int pos)
{
assert(ps && ps->size);
assert(0 <= pos < ps->size);
memmove(ps->arr + pos, ps->arr + pos + 1, sizeof(SLDatatype) * (ps->size - pos));
ps->size--;
}
//顺序表的元素的查找
int SLFind(SL ps,int(*Fuc)(SLDatatype x,SLDatatype y),SLDatatype n)
{
for(int i= 0;i<ps.size;i++ )
{
if ((*Fuc)(n, ps.arr[i])==0)
return i;
}
return -1;
}
//顺序表的销毁
void SLDestory(SL* ps)
{
ps->capacity = ps->size = 0;
if (ps->arr != NULL)
{
free(ps->arr);
ps->arr = NULL;
}
}
从顺序表到通讯录
本章主要介绍如何通过顺序表这一数据结构,实现通讯录。前面提到顺序表的本质就是数组,而这里就是使用数组储存一个个的用户信息,用户的信息我们使用结构体来表示。综上所述,通讯录项目就是一个结构体数组,同时使用顺序表中的方法,进行增,删,查,改。
前置申明
如图需要建立五个文件,额外建立Contact.h和Contact.c分别用来放通讯录相关的定义和函数实现
如图,这里与前面不同的是SLDatatype的类型变成了用于储存用户信息的结构体,所以Seqlist.h中需要包含Contact.h的头文件,这就决定了Contact.h中不能引用Seqlist.h头文件,但在通讯录函数声明时需要使用SL类型作为参数,所以这里先使用一个前置申明,告诉电脑这个参数的类型是一个结构体,而在Contact.c文件中同时引用两个头文件,经过编译,预处理,那么电脑就知道前置申明中SeqLlist这一结构体即Contact的具体类型。
通讯录代码:
Contact.h
#pragma once
#define NAME_MAX 10
#define GANDER_MAX 10
#define ADDR_MAX 20
#define PHONE_MAX 20
typedef struct PersonInfo
{
char name[NAME_MAX];
char gender[GANDER_MAX];
int age;
char phone[PHONE_MAX];
char addr[ADDR_MAX];
}PerInfo;
//前置申明
typedef struct SeqList Contact;
//通讯录初始化
void ContactInit(Contact* con);
//通讯录的添加数据
void ContactAdd(Contact* con);
//通讯录的删除数据
void ContactDel(Contact* con);
//通讯录的查找数据
void ContactFind(Contact con);
//通过名字查找
int ContactFindByName(Contact con, char* name);
//通过电话查找
int ContactFindByPhone(Contact con, char* phone);
//通讯录的特定数据展示
void ContactSpeDisplay(Contact con, int pos);
//通讯录的打印
void ContactDisplay(Contact con);
//通讯录的修改数据
void ContactEdit(Contact* con);
//通讯录的销毁
void ContactDestory(Contact* con);
Contact.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"
#include"Contact.h"
//通讯录初始化
void ContactInit(Contact* con)
{
SLInit(con);
}
//通讯录的添加数据
void ContactAdd(Contact* con)
{
assert(con);
PerInfo p1;
printf("\n请输入联系人姓名:>");
scanf("%s", p1.name);
printf("\n请输入联系人电话号码:>");
scanf("%s", p1.phone);
printf("\n请输入联系人性别:>");
scanf("%s", p1.gender);
printf("\n请输入联系人年龄:>");
scanf("%d", &p1.age);
printf("\n请输入联系人家庭住址:>");
scanf("%s", p1.addr);
printf("###############\n");
SLPushFront(con,p1);
}
//通讯录的删除数据
void ContactDel(Contact* con)
{
char name_tmp[20] = "0";
printf("请输入你想删除的联系人姓名:>");
scanf("%s", name_tmp);
int i = 0;
for (i = 0; i < con->size; i++)
{
if (strcmp(name_tmp, (con->arr+i)->name) == 0)
break;
}
if (i == con->size)
{
printf("未查找到该联系人数据\n");
ContactDisplay(*con);
}
else
{
SLErase(con, i);
printf("该联系人已删除\n");
ContactDisplay(*con);
}
}
//通过名字查找
int ContactFindByName(Contact con, char* name)
{
for (int i = 0; i < con.size; i++)
{
if (strcmp(name, (con.arr + i)->name) == 0)
return i;
}
return -1;
}
//通过电话查找
int ContactFindByPhone(Contact con, char* phone)
{
for (int i = 0; i < con.size; i++)
{
if (strcmp(phone, (con.arr + i)->phone) == 0)
return i;
}
return -1;
}
//通讯录的查找数据
void ContactFind(Contact con)
{
printf("请在下方选出查找方式;\n");
printf("***************\n");
printf("****0 姓名*****\n");
printf("****1 电话*****\n");
printf("***************\n");
int info;
scanf("%d", &info);
if (info == 0)
{
char name_tmp[20] = "0";
printf("请输入姓名:>");
scanf("%s", name_tmp);
int pos = ContactFindByName(con, name_tmp);
ContactSpeDisplay(con, pos);
}
else
{
char phone_tmp[20];
printf("请输入电话:>");
scanf("%s", phone_tmp);
int pos = ContactFindByName(con, phone_tmp);
ContactSpeDisplay(con, pos);
}
}
//通讯录的所有数据展示
void ContactDisplay(Contact con)
{
if (con.size != 0)
{
for (int i = 0; i < con.size; i++)
{
printf("姓名:%s\n", (con.arr + i)->name);
printf("电话:%s\n", (con.arr + i)->phone);
printf("性别:%s\n", (con.arr + i)->gender);
printf("年龄:%d\n", (con.arr + i)->age);
printf("家庭住址:%s\n", (con.arr + i)->addr);
printf("###############\n");
}
}
else
{
printf("还未添加联系人数据\n");
}
}
//通讯录的特定数据展示
void ContactSpeDisplay(Contact con,int pos)
{
if (pos<con.size)
{
printf("姓名:%s\n", (con.arr + pos)->name);
printf("电话:%s\n", (con.arr + pos)->phone);
printf("性别:%s\n", (con.arr + pos)->gender);
printf("年龄:%d\n", (con.arr + pos)->age);
printf("家庭住址:%s\n", (con.arr + pos)->addr);
printf("###############\n");
}
else
{
printf("还未添加联系人数据\n");
}
}
//通讯录的修改数据
void ContactEdit(Contact* con)
{
char name_tmp[20] = "0";
printf("请输入你想修改的联系人姓名:>");
scanf("%s", name_tmp);
int i = ContactFindByName(*con, name_tmp);
if (i==-1)
printf("未找到你要修改的数据\n");
else
{
PerInfo p1;
printf("\n请输入联系人姓名:>");
scanf("%s", p1.name);
printf("\n请输入联系人电话号码:>");
scanf("%s", p1.phone);
printf("\n请输入联系人性别:>");
scanf("%s", p1.gender);
printf("\n请输入联系人年龄:>");
scanf("%d", &p1.age);
printf("\n请输入联系人家庭住址:>");
scanf("%s", p1.addr);
*(con->arr + i) = p1;
printf("修改完成\n");
printf("###############\n");
}
}
//通讯录的销毁
void ContactDestory(Contact* con)
{
SLDestory(con);
}