顺序表的应用

目录

1. 基于动态顺序表实现通讯录

1.功能要求

2.实现方法

2. 顺序表经典算法

3. 顺序表的问题及思考


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,增容降低代码运行效率,增容造成空间浪费,我们可以通过另一种数据结构:链表来解决。

  • 17
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值