目录
1. 基于动态顺序表实现通讯录
1.功能要求
1.至少能够存储100个⼈的通讯信息
2.能够保存⽤⼾信息:名字、性别、年龄、电话、地址等
3.增加联系⼈信息
4.删除指定联系⼈
5.查找制定联系⼈
6.修改指定联系⼈
7.显示联系⼈信息
2.实现方法
通讯录的实现是基于顺序表的,所有通讯录的底层还是顺序表,只不过顺序表的存储类型是int整型类型,而现在我要存储结构体类型。
Condact.h
#pragma once
#define NAME_MAX 20
#define GENDER_MAX 4
#define TEL_MAX 20
#define ADDR_MAX 100
//定义联系人数据的结构
typedef struct PersonInfo
{
char name[NAME_MAX];//姓名
char gender[GENDER_MAX];//性别
int age;//年龄
char tel[TEL_MAX];//电话
char addr[ADDR_MAX];//住址
}peoInfo;
//typedef SL contact;//不能对SL重命名,找不到
typedef struct SeqList contact;//前置声明
//初始化通讯录
void InitContact(contact* con);
//添加通讯录数据
void AddContact(contact* con);
//删除通讯录数据
void DelContact(contact* con);
//展⽰通讯录数据
void ShowContact(contact* con);
//查找通讯录数据
void FindContact(contact* con);
//修改通讯录数据
void ModifyContact(contact* con);
//销毁通讯录数据
void DestroyContact(contact* con);
SeqList.h
#pragma once
//定义顺序表的结构
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "Condact.h"
//方便以后修改类型,如果今后要修改成char,只修改这一行代码就可以
//typedef int SLDataType;
typedef peoInfo SLDataType;//将整型替换为通讯录
/*
#define N 100
//静态顺序表
struct SeqList
{
int arr[N];
int size;
};
*/
//动态顺序表
struct SeqList
{
SLDataType* arr;
int size;//顺序表当前有效的数据个数
int capacity;//空间大小
};
typedef struct SeqList SL;//对结构体类型重命名
//顺序表的初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestroy(SL* ps);
//扩容
void SLCheckCapacity(SL* ps);
//打印顺序表
void SLPrint(SL* ps);
//尾部插入数据
void SLPushBack(SL* ps, SLDataType x);
//头部插入数据
void SLPushFront(SL* ps, SLDataType x);
//尾部删除数据
void SLPopBack(SL* ps);
//头部删除数据
void SLPopFront(SL* ps);
//指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//指定位置删除数据
void SLErase(SL* ps, int pos);
//查找顺序表中的数据
int SLFind(SL* ps, SLDataType x);
Condact.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
//初始化通讯录
void InitContact(contact* con)
{
//通讯录的初始化实际上就是顺序表的初始胡
//而顺序表的初始化已经实现好了,直接调用即可
SLInit(con);
}
//销毁通讯录数据
void DestroyContact(contact* con)
{
//通讯录的销毁实际上就是顺序表的销毁
//而顺序表的销毁已经实现好了,直接调用
SLDestroy(con);
}
//添加通讯录数据
void AddContact(contact* con)
{
peoInfo info;
//姓名 性别 年龄 电话 住址
printf("请输入要添加的联系人的姓名\n");
scanf("%s", info.name);
printf("请输入要添加的联系人的性别\n");
scanf("%s", info.gender);
printf("请输入要添加的联系人的年龄\n");
scanf("%d", &info.age);
printf("请输入要添加的联系人的电话\n");
scanf("%s", info.tel);
printf("请输入要添加的联系人的住址\n");
scanf("%s", info.addr);
SLPushBack(con, info);
}
//字符串判断是否相等函数
int My_StrCmp(const char* str1, const char* str2)
{
while (*str1++ == *str2++)
if (*str1 == '\0')
return 0;
if (*str1 > *str2)
return 1;
else
return -1;
}
//查找,下标返回函数
int findByName(contact* con, char* name)
{
for (int i = 0; i < con->size; i++)
{
if (My_StrCmp(con->arr[i].name, name) == 0)
{
//找到了,返回下标
return i;
}
}
//没找到,返回无效下标,-1
return -1;
}
//删除通讯录数据
void DelContact(contact* con)
{
char name[NAME_MAX];
//首先判断联系人存不存在
//查找
printf("请输入要删除的联系人的姓名\n");
scanf("%s", name);
int find = findByName(con, name);
if (find < 0)
{
printf("要删除的联系人不存在!\n");
return;
}
//要删除的联系人存在
SLErase(con, find);
printf("删除成功!\n");
}
//展⽰通讯录数据
void ShowContact(contact* con)
{
//姓名 性别 年龄 电话 住址
printf("姓名 性别 年龄 电话 住址\n");
for (int i = 0; i < con->size; i++)
printf("%s %s %d %s %s\n",
con->arr[i].name,
con->arr[i].gender,
con->arr[i].age,
con->arr[i].tel,
con->arr[i].addr
);
}
//修改通讯录数据
void ModifyContact(contact* con)
{
char name[NAME_MAX];
//要修改的联系人数据是否存在
printf("请输入要修改的联系人的姓名\n");
scanf("%s", name);
int find = findByName(con, name);
if (find < 0)
{
printf("要修改的联系人不存在!\n");
return;
}
//要修改的联系人存在
//直接修改
printf("请输入新的联系人的姓名\n");
scanf("%s", con->arr[find].name);
printf("请输入新的联系人的性别\n");
scanf("%s", con->arr[find].gender);
printf("请输入新的联系人的年龄\n");
scanf("%d", &con->arr[find].age);
printf("请输入新的联系人的电话\n");
scanf("%s", con->arr[find].tel);
printf("请输入新的联系人的住址\n");
scanf("%s", con->arr[find].addr);
printf("修改成功\n");
}
//查找通讯录数据
void FindContact(contact* con)
{
char name[NAME_MAX];
//要查找的联系人数据是否存在
printf("请输入要查找的联系人的姓名\n");
scanf("%s", name);
int find = findByName(con, name);
if (find < 0)
{
printf("要查找的联系人不存在!\n");
return;
}
//要查找的联系人存在
//显示
printf("姓名 性别 年龄 电话 住址\n");
printf("%s %s %d %s %s\n",
con->arr[find].name,
con->arr[find].gender,
con->arr[find].age,
con->arr[find].tel,
con->arr[find].addr
);
}
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
//顺序表的初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
/*
也可以初始化的时候给capacity给空间大小,但是ps->arr同时也要malloc空间
*/
}
//顺序表的销毁
void SLDestroy(SL* ps)
{
//动态申请的空间需要手动的释放掉
//不是销毁整个顺序表
if (ps->arr)//判断数组成员是否为空
{
free(ps->arr);//释放动态申请的空间
}
ps->arr = NULL;//置为空指针,否则会变成野指针
ps->size = ps->capacity = 0;//置为0
}
//扩容
void SLCheckCapacity(SL* ps)
{
/*if (ps == NULL)
return;*/
assert(ps);//assert(ps != NULL);
//插入数据之前先看数据够不够
//判断当前空间与有效个数是不是相同,相同则需要申请空间
if (ps->size == ps->capacity)
{
//申请空间
/*
要申请多大的空间,一次增容多大?
增容通常来说是成倍的增加,一般是2或者3倍,实际上是数学推理出来的
用2倍更合理
可以百度:为什么增容要以2倍增加
增容我们要用realloc,malloc和calloc都是用来申请空间的
*/
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tem = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
if (tem == NULL)
{
perror("realloc err!");//打印申请失败的原因
exit(1);//给一个非0的值
}
ps->arr = tem;
ps->capacity = newCapacity;
}
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;//插入完数据直接加加,也可以分开写
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
SLCheckCapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i-1];//ps->arr[1] = ps->arr[0]
}
ps->arr[0] = x;
ps->size++;
}
//打印 - 可以不用传指针,因为这里不需要修改,只是打印
//void SLPrint(SL* ps)
//{
// for (int i = 0; i < ps->size; i++)
// {
// printf("%d ", ps->arr[i]);
// }
// printf("\n");
//}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);//判断有效个数,有效个数为0则不能执行删除
ps->size--;//直接--不影响后续的增删改查
}
//头删
void SLPopFront(SL* ps)
{
assert(ps);
for (int i = 0; i <ps-> size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
//指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps && pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
++ps->size;
}
//指定位置删除数据
void SLErase(SL* ps, int pos)
{
assert(ps && pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
--ps->size;
}
//查找顺序表中的数据
//int SLFind(SL* ps, SLDataType x)
//{
// assert(ps);
// for (int i = 0; i < ps->size; i++)
// {
// if (x == ps->arr[i])
// {
// return i;
// }
// }
// return -1;
//}
main.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
//通讯录的测试函数
//void ContactTest()
//{
// //创建通讯录对象,实际上就是顺序表对象,等价于SL sl。
// contact con;
// //对通讯录的初始化
// InitContact(&con);
//
// /*对通讯录的增删改查*/
// //添加通讯录数据
// AddContact(&con);
// AddContact(&con);
// ModifyContact(&con);
// DelContact(&con);
// ShowContact(&con);
// //对通讯录的销毁
// DestroyContact(&con);
//}
void menu()
{
printf("*************************************************\n");
printf("************1.添加联系人 2.删除联系人************\n");
printf("************3.展示联系人 4.查找联系人************\n");
printf("************5.修改联系人 0.退出通讯录************\n");
printf("*************************************************\n");
}
//通讯录主函数
void ContactMainFunc()
{
int input = 0;
//创建通讯录变量
contact con;
//初始化通讯录
InitContact(&con);
do
{
menu();
printf("请输入要操作的序列号\n");
scanf("%d", &input);
switch (input)
{
case 1:
AddContact(&con);
break;
case 2:
DelContact(&con);
break;
case 3:
ShowContact(&con);
break;
case 4:
FindContact(&con);
break;
case 5:
ModifyContact(&con);
break;
case 0:
printf("退出通讯录!!!\n");
break;
default:
printf("输入错误,请重新输入!!!\n");
}
} while (input);
//销毁通讯录数据,包括申请的动态内存回收
DestroyContact(&con);
}
int main()
{
//ContactTest();
ContactMainFunc();
return 0;
}
思考:
如何保证程序结束后,历史通讯录信息不会丢失。
2. 顺序表经典算法
经典算法OJ题1:27. 移除元素 - 力扣(LeetCode)
写法1:
//numsSize表示数组的长度
int removeElement(int* nums, int numsSize, int val) {
int* src = nums;
int* dest = nums;
int i = 0;
while(i < numsSize)
{
if(*src == val)
{
src++;
}
else if(*src != val)
{
*dest = *src;
dest++;
src++;
}
i++;
}
return dest - nums;
}
写法2:
//numsSize表示数组的长度
int removeElement(int* nums, int numsSize, int val) {
int src = 0;
int dest = 0;
while(src < numsSize)
{
if(nums[src] == val)
{
src++;
}
else if(nums[src] != val)
{
nums[dest] = nums[src];
dest++;
src++;
}
}
return dest;
}
解析:
经典算法OJ题2:88. 合并两个有序数组 - 力扣(LeetCode)
//nums1Size:表示nums1的数组长度
//nums2Size:表示nums2的数组长度
//上面的两个参数都用不到
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {
int s1 = m-1;
int s2 = n-1;
int s3 = m+n-1;
while(s1 >= 0 && s2 >= 0)//只要有一个条件为假就跳出循环
{
if(nums1[s1] > nums2[s2])
{
nums1[s3--] = nums1[s1--];
}
else
{
nums1[s3--] = nums2[s2--];
}
}
//退出循环有两种情况,s1>=0或者s2>=0。
//只需要处理一种情况,s2>=0(说明nums2中的数据还没完全放到nums1中)
while(s2>=0)
{
nums1[s3--] = nums2[s2--];
}
}
解析:
3. 顺序表的问题及思考
1. 中间/头部的插入删除,时间复杂度为O(N)。
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容⼀般是呈2倍的增长,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。
这三个问题就是我们顺序表出现的问题。
针对顺序表:中间/头部插入效率dixia,增容降低代码运行效率,增容造成空间浪费,我们可以通过另一种数据结构:链表来解决。