前言
数据结构是计算机存储、组织数据的方式。是指相互之间存在一种或多种特定关系的数据元素合集。数据结构反映数据的内部构成,即数据由哪部分构成,什么方式构成,以及数据元素之间的呈现的结构。
使用数据结构是为了将杂乱无章的数据按特定逻辑关系高效地组织起来,从而在时间(运算速度)与空间(内存占用)上最大化程序的执行与存储效率。
一、线性表(linear list)
n个具有相同特性的数据元素的有限序列。在逻辑上是线性的,在物理结构上不一定是线性的

常见的线性表有:顺序表、链表、栈、队列、字符串…
二、顺序表(Sequential List)
顺序表(Sequential List)是线性表的一种,对数组进行了封装,增加了常用的增删查改等接口
三、静态顺序表和动态顺序表
静态顺序表(Static Sequential List)
1 2 3 4 5 6 7 8
| #define SZ 10 struct SSeqList { int arr[SZ]; int size; };
|
动态顺序表(Dynamic Sequential List)
1 2 3 4 5 6 7 8 9
| struct DSeqList { int* arr; int size; int capcity; };
|
四、实现动态顺序表
4.1 头文件
实现会用到以下头文件
1 2 3
| stdio.h string.h assert.h
|
4.2 动态顺序表的初始化
1 2 3 4 5 6
| struct DSeqList { int* arr; int size; int capcity; };
|
为了方便,这里使用 typedef 关键字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| typedef int SLdata; struct DSeqList { SLdata* arr; int size; int capcity; };
typedef struct DSeqList { SLdata* arr; int size; int capcity; }SL;
|
将动态顺序表的初始化封装为一个函数
1 2 3 4 5 6 7
| void SLInt(SL* ps) { ps->arr = NULL; ps->size = 0; ps->capcity = 0; }
|
测试
1 2 3 4 5 6 7
| int main() { SL s; SLInt(&s); return 0; }
|

4.3 动态顺序表的容量检查与扩容
往顺序表中插入数据,需要先行判断顺序表是否有足够的空间进行插入,如果够就可以直接进行值的插入,如果不够就需要扩容空间

判断空间是否足够的依据是有效数据个数 size 是否与空间容量 capcity 相等。
如果相等的话说明满了就需要进行扩容,扩容多少没有定数,但一般选择扩容 1.5 或 2 倍。扩容操作使用 realloc 函数。如果空间为0,直接给予空间一个初始值
将这个过程封装为一个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void Capcity_Chek(SL* ps) { assert(ps); if (ps->size == ps->capcity) { int NewCapcity = ps->capcity == 0 ? 4 : ps->capcity * 2; SLdata* Newps = (SLdata*)realloc(ps->arr, NewCapcity * sizeof(SLdata)); if (!Newps) { perror("realloc"); return ; } ps->arr = Newps; ps->capcity = NewCapcity; } }
|
4.4 动态顺序表值的插入
4.4.1 尾插
顺序表插入值的一种特殊情况,向当前顺序表的尾部插入一个数值,顺序表的有效数据个数 size 恰好是顺序表的尾部

将尾插的过程封装成一个函数
1 2 3 4 5 6 7 8 9 10 11
| void PushBack(SL* ps, SLdata data) { Capcity_Chek(ps); ps->arr[ps->size++] = data; }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12
| int main() { SL s; SLInt(&s); PushBack(&s, 1); PushBack(&s, 2); PushBack(&s, 3); PushBack(&s, 4); PushBack(&s, 5); return 0; }
|

4.4.2 头插
顺序表头插是顺序表插值的一种特殊情况,插入一个值到顺序表的首元素位置,插入后整个顺序表的元素要整体向后移动一个 SLdata 大小。
在移动时是从当前顺序表的末尾元素(即下标为 size - 1)的位置开始向后移动,移动后在进行插入

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void PushFront(SL* ps, SLdata data) { Capcity_Chek(ps); for (int i = ps->size - 1;i >= 0 ;i--) { ps->arr[i + 1] = ps->arr[i]; } ps->arr[0] = data; ps->size++; }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12
| int main() { SL s; SLInt(&s); PushFront(&s, 1); PushFront(&s, 2); PushFront(&s, 3); PushFront(&s, 4); PushFront(&s, 5); return 0; }
|
4.4.3 指定位置插入
对于顺序表的指定位置 pos 插入,插入到这个位置之前。首先要给指定位置限制一个范围(pos >= 0 且 pos < size)需要先将指定位置及之后的数据整体向后移动一个 SLdata 大小的空间,再将值插入到指定位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void Push_Pos(SL* ps, int pos, SLdata data) { if (!(pos >= 0 && pos < ps->size)) { printf("位置不合法!\n"); return; } Capcity_Chek(ps);
for (int i = ps->size - 1;i >= pos;i--) { ps->arr[i + 1] = ps->arr[i]; } ps->arr[pos] = data; ps->size++; }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int main() { SL s; SLInt(&s); PushFront(&s, 1); PushFront(&s, 2); PushFront(&s, 3); PushFront(&s, 4); Push_Pos(&s, 0, 5); Push_Pos(&s, s.size, 6); Push_Pos(&s, 1, 7); Push_Pos(&s, 3, 8); return 0; }
|

4.5 动态顺序表值的删除
4.5.1 顺序表尾删
顺序表删除的一种的特殊情况,删除的是顺序表尾部的数据,即 size - 1 位置的数据

1 2 3 4 5 6 7
| void PopBack(SL* ps) { assert(ps && ps->size--); }
|
这里直接让 size– 就可以了,因为 size 表示的是有效数据个数
测试
1 2 3 4 5 6 7 8 9 10 11 12 13
| int main() { SL s; SLInt(&s); PushBack(&s, 1); PushBack(&s, 2); PushBack(&s, 3); PushBack(&s, 4); PushBack(&s, 5); PopBack(&s); return 0; }
|
这样顺序表就使用不了被删除的数据

4.5.2 顺序表头删
顺序表删除的一种的特殊情况,删除的是顺序表头部的数据,直接让头元素之后的数据整体向前移动一个 SLdata 大小的空间就行了,同时 size 自减
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void PopFront(SL* ps) { assert(ps); for (int i = 0;i < ps->size - 1;i++) { ps->arr[i] = ps->arr[i + 1]; }
ps->size--; }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int main() { SL s; SLInt(&s); PushBack(&s, 1); PushBack(&s, 2); PushBack(&s, 3); PushBack(&s, 4); PopFront(&s); PopFront(&s); PopFront(&s); PopFront(&s); return 0; }
|

4.5.3 顺序表指定位置删除
判断指定位置 pos 的合法性(pos >= 0 且 pos < size),将 pos 后的数据数据整体向前移动一个 SLdata 大小的空间就行了

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void Pop_Pos(SL* ps, int pos) { assert(ps && ps->size); if (!(pos >= 0 && pos < ps->size)) { printf("位置不合法!\n"); return; } for (int i = pos ;i < ps->size - 1 ;i++) { ps->arr[i] = ps->arr[i + 1]; } ps->size--; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int main() { SL s; SLInt(&s); PushBack(&s, 1); PushBack(&s, 2); PushBack(&s, 3); PushBack(&s, 4); PushBack(&s, 5); PushBack(&s, 6); Pop_Pos(&s, 0); Pop_Pos(&s, s.size - 1); Pop_Pos(&s, 2); return 0; }
|
测试

4.6 动态顺序表值的修改
对指定位置的值进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void Edit_Pos(SL* ps, int pos,SLdata data) { assert(ps && ps->size); if (!(pos >= 0 && pos < ps->size)) { printf("位置不合法!\n"); return; } ps->arr[pos] = data; }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int main() { SL s; SLInt(&s); PushBack(&s, 1); PushBack(&s, 2); PushBack(&s, 3); PushBack(&s, 4); Edit_Pos(&s, 0, 5); Edit_Pos(&s, 1, 5); Edit_Pos(&s, 2, 5); Edit_Pos(&s, 3, 5); return 0; }
|

4.7 动态顺序表值的查找
查找指定值是否在表中,查找到了就告知下标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void Find(const SL* ps, SLdata data) { assert(ps && ps->size); for (int i = 0;i < ps->size;i++) { if (data == ps->arr[i]) { printf("找到了,下标是%d !\n",i); return; } } printf("当前表中不存在该数据\n"); }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13
| int main() { SL s; SLInt(&s); PushBack(&s, 1); PushBack(&s, 2); PushBack(&s, 3); PushBack(&s, 4); Find(&s, 4); Find(&s, 5); return 0; }
|

4.8 动态顺序表的销毁
动态顺序表是通过动态内存管理实现的,在使用完成后需要销毁
1 2 3 4 5 6 7 8 9 10 11 12
| void Destory(SL* ps) { assert(ps); ps->size = 0; ps->capcity = 0; free(ps->arr); ps->arr = NULL; }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12
| int main() { SL s; SLInt(&s); PushBack(&s, 1); PushBack(&s, 2); PushBack(&s, 3); PushBack(&s, 4); Destory(&s); return 0; }
|

五、动态顺序表实现简易通讯录
5.1 通讯录结构
使用一个结构体,包含姓名、性别、电话号码
1 2 3 4 5 6 7 8 9 10 11 12 13
| #define NAME_MAX 30 #define GENDER_MAX 6 #define TELEPHONE_MAX 20
typedef struct Contact { char name[NAME_MAX]; char gender[GENDER_MAX]; char telephone[TELEPHONE_MAX]; }Contact;
typedef struct Contact Con_data;
|
对应的,顺序表也需要进行修改
1 2 3 4 5 6
| typedef struct SeqList { Con_data* arr; int size; int capcity; }SL;
|
5.2 通讯录的初始化
1 2 3 4 5 6 7 8 9
| void ContactInt(SL* ps) { assert(ps); ps->arr = NULL; ps->size = 0; ps->capcity = 0; }
|
测试
1 2 3 4 5 6
| int main() { SL s; ContactInt(&s); return 0; }
|

5.3 通讯录空间检查与扩容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void CheckCapcity(SL* ps) { assert(ps); if (ps->size == ps->capcity) { int NewCapcity = ps->capcity == 0 ? 4 : ps->capcity * 2; Con_data* Newp = (Con_data*)realloc(ps->arr, sizeof(Con_data) * NewCapcity); if (!Newp) { perror("realloc"); return; } ps->arr = Newp; ps->capcity = NewCapcity; } }
|
5.4 通讯录数据的插入
采用尾插的方式进行插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void ContactAdd(SL* ps) { assert(ps); Contact con = { 0 }; printf("请输入要添加的姓名:\n"); printf("> "); scanf("%s", con.name);
printf("请输入要添加的性别:\n"); printf("> "); scanf("%s", con.gender);
printf("请输入要添加的电话号码:\n"); printf("> "); scanf("%s", con.telephone);
CheckCapcity(ps); ps->arr[ps->size++] = con; }
|
测试
1 2 3 4 5 6 7 8
| int main() { SL s; ContactInt(&s); ContactAdd(&s); ContactAdd(&s); return 0; }
|

5.5 通讯录数据的查找
采用的是根据姓名来查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int ContactFind_ByName(const SL* ps, char* name) { assert(ps); for (int i = 0;i < ps->size;i++) { if (!(strcmp(name, ps->arr[i].name))) { return i; } } printf("当前数据表中不存在该联系人\n"); return 0; }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| int ContactFind_ByName(const SL* ps, char* name) { assert(ps); for (int i = 0;i < ps->size;i++) { if (!(strcmp(name, ps->arr[i].name))) { return i; } } printf("当前数据表中不存在该联系人\n"); return -1; }
|

5.6 通讯录数据删除
先要通过查找数据才能进行删除,用的是指定位置删除的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void ContactDel_ByName(SL* ps) { char name[NAME_MAX] = { 0 }; printf("请输入要删除的联系人姓名:\n"); printf("> "); scanf("%s", name); int pos = ContactFind_ByName(ps, name); if (pos == -1) { return; } for (int i = pos;i < ps->size - 1;i++) { ps->arr[i] = ps->arr[i + 1]; } printf("删除成功!\n"); ps->size--; }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12
| int main() { SL s; ContactInt(&s); ContactAdd(&s); ContactAdd(&s); ContactAdd(&s); ContactAdd(&s); ContactDel_ByName(&s); return 0; }
|

5.7 通讯录数据修改
也是通过姓名匹配到之后进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| void Contact_Edit_ByName(SL* ps) { char name[NAME_MAX] = { 0 }; printf("请输入要修改的联系人姓名:\n"); printf("> "); scanf("%s", name); int pos = ContactFind_ByName(ps, name); if (pos == -1) { return; } Contact con = { 0 }; printf("请输入要修改的姓名:\n"); printf("> "); scanf("%s", con.name);
printf("请输入要修改的性别:\n"); printf("> "); scanf("%s", con.gender);
printf("请输入要修改的电话号码:\n"); printf("> "); scanf("%s", con.telephone);
ps->arr[pos] = con; printf("修改成功!\n"); }
|
测评
1 2 3 4 5 6 7 8 9 10 11 12
| int main() { SL s; ContactInt(&s); ContactAdd(&s); ContactAdd(&s); ContactAdd(&s); Contact_Edit_ByName(&s); Contact_Edit_ByName(&s); return 0; }
|

5.8 通讯录的销毁
1 2 3 4 5 6 7 8 9 10 11
| void Con_Destory(SL* ps) { assert(ps); ps->size = 0; ps->capcity = 0; free(ps->arr); ps->arr = NULL; }
|
六、 通讯录的实现
6.1 简易菜单
1 2 3 4 5 6 7 8 9 10
| void menu() { printf("1.新增联系人"); printf("2.删除联系人\n"); printf("3.查找联系人"); printf("4.修改联系人\n"); printf("5.所有联系人"); printf("0.退出\n"); printf("选择你的操作:> "); }
|
6.2 打印单个联系人的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void Print_Person(const SL* ps, int pos) { assert(ps && ps->size); if (pos >= 0 && pos < ps->size) { printf("姓名 "); printf("性别 "); printf("电话 \n"); printf("%3s ", ps->arr[pos].name); printf("%5s ", ps->arr[pos].gender); printf("%5s \n", ps->arr[pos].telephone); } }
|
6.3 打印整个通讯录的联系人数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void Print_Person(const SL* ps, int pos) { assert(ps && ps->size); if (pos >= 0 && pos < ps->size) { printf("姓名 "); printf("性别 "); printf("电话 \n"); printf("%3s ", ps->arr[pos].name); printf("%5s ", ps->arr[pos].gender); printf("%5s \n", ps->arr[pos].telephone); } }
|
6.4 简易通讯录的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| int main() { int option = 0; SL s; char name[NAME_MAX] = { 0 };
ContactInt(&s); do { menu(); scanf("%d", &option); switch (option) { case 1: ContactAdd(&s); break; case 2: ContactDel_ByName(&s); break; case 3: printf("请输入要查找的联系人姓名:\n"); scanf("%s", name); int pos = ContactFind_ByName(&s, name); if (!pos) { printf("找到了!\n"); Print_Person(&s, pos); } break; case 4: Contact_Edit_ByName(&s); break; case 5: Print_Con(&s); break; case 0: printf("exit!\n"); break; default: printf("输入不合法,请重新输入!\n"); } } while (option); Con_Destory(&s); return 0; }
|
