C语言:顺序表的实现和通迅录项目实现

顺序表

1、顺序表的概念及结构

1.1 线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的,
线性表在物理上存储时,通常以数组和链式结构的形式存储。

2、顺序表分类

• 顺序表和数组的区别
◦ 顺序表的底层结构是数组,对数组的封装实现了常⽤的增删改查等接⼝
• 顺序表分类

2.1◦ 静态顺序表

概念:使⽤定⻓数组存储元素
e0eb1881ab98446fb785c4215be81546.png
静态顺序表缺陷:空间给少了不够⽤,给多了造成空间浪费

2.2 动态顺序表

不使用定长数组,定义数组的地址。

67450bf375f94a249f5a5c328914fbe2.png
增容一般是以原来空间的两倍到三倍来增容。

3、动态顺序表的实现

3.1初始化和销毁

初始化:

只需要顺序表的数组地址置为空,然后让有效数据个数和空间容量初始化为零即可。

顺序标的销毁:

则是将顺序表内数组的空间释放掉(销毁之前要判空),同时,要指向该数组的指针指为空防止其成为野指针。后让有效数据个数和空间容量指为零即可。

void SLInit(SL* ps)
{
    ps->arr = NULL;
    ps->size = ps->capacity = 0;
}

void SLDestroy(SL* ps)
{
    assert(ps->arr != NULL);
    free(ps->arr);
    ps->arr = NULL;
    ps->size = ps->capacity = 0;
}

3.2动态顺序表 -- 按需申请(扩容)

因为初始化时没有对顺序表申请空间,所以,要先判断空间是否等于零,如果是零的话,就给它赋一个初值,如果不为零的话,那就让它变为原来的两倍。

注意:申请来的空间,不能直接传给顺序表,因为空间有可能申请失败,如果这时直接给的话,就会导致数据丢失。

void SLCheckCapacity(SL* ps)
{
    if (ps->capacity == ps->size)
    {
        int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
        SLdataType* tmp = (SLdataType*)realloc(ps->arr, newCapacity * sizeof(SLdataType));
        assert(tmp != NULL);
        ps->arr = tmp;
        ps->capacity = newCapacity;
    }
}

3.3尾部插⼊删除

尾插:

插入之前要先判断传过要顺序表地址是否为空,和空间够不够,空间不够就要申请(这里调用可上面写的SLCeckCapacity(SL* ps)即可)然后直接插入到最后一个元素的位置即可。

尾删:

删要先判断顺序地址是否为空,顺序表里面元素个数不为零。尾删只需要把有效数据个数减一就行。

void SLPushBack(SL* ps, SLdataType x)
{
    assert(ps!= NULL);
        SLCheckCapacity(ps);
    ps->arr[ps->size] = x;
    ps->size++;
}

void SLPopBack(SL* ps)
{
    assert(ps!= NULL);
    assert(ps->size > 0);
    ps->size--;
}

3.4头部插⼊删除

头插:

头删同样需要判空跟判空间够不够。

因为顺序表里面的元素在物理上是相邻,所以我们在插入第一个元素的时候,要把后面的元素全部往后移,然后再把元素插入空出来的第一个位置里面。

头删:

是把后面的元素全部往前挪一个位置,把第一个元素覆盖掉,再把有效数据减1就行了

void SLPushFront(SL* ps, SLdataType x)
{
	assert(ps != NULL);
		SLCheckCapacity(ps);
	for (int i = ps->size;i>0;i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[0] = x;
	ps->size++;
}

void SLPopFront(SL* ps)
{
	assert(ps != NULL);
	assert(ps->size > 0);
	for (int i = 0;i<ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

3.5指定位置之前插⼊/删除数据

插入:

插入之前先看空间够不够和顺序表是否为空

然后只需要将指定位置及其之后的全部元素往后挪一位,再把元素插入到这个指定位置即可。

删:

删之前先判有无元素跟顺序表表是否为空。然后将指定位置之后的数据往前挪动1个元素位置,把要删除的元素覆盖,再将有效元素个数减一即可。

void SLInsert(SL* ps, int pos, SLdataType x)
{
	assert(ps != NULL);
	assert(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 != NULL);
	assert(pos >= 0 && pos < ps->size);
	for (int i =pos;i<ps->size-1;i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

4.顺序表的应⽤-- 基于动态顺序表实现通讯录

C语⾔基础要求:结构体、动态内存管理、顺序表、⽂件操作
1、功能要求
1)⾄少能够存储100个⼈的通讯信息
2)能够保存⽤⼾信息:名字、性别、年龄、电话、地址等
3)增加联系⼈信息
4)删除指定联系⼈
5)查找制定联系⼈
6)修改指定联系⼈
7)显⽰联系⼈信息

4.1.文件的布置和通讯录结构

上面我们实现的顺序表代码定义放在SeqList.c
声明放在SeqList.h。同时又新建两个文件,一个是Contact.h通讯录实现函数的声明,另一个Contact.c放通讯录函数的实现,再创建一个主函数文件test.c
7ca4dfc6646949dab6b3b4d5dcee7bd0.png
Contact.h:

4.2通讯录的初始化和销毁

就是对顺序表的初始化和销毁,是所以直接调用初始化和销毁函数即可。
  
//通讯录的初始化
void ContactInit(Contact* con)
{
	SLInit(con);
}

//通讯录的销毁
void ContactDesTroy(Contact* con)
{
	SLDestroy(con);
}

4.3通讯录添加数据

这里可以直接声明,联系人结构体第一局部变量,然后把数据写进去,在调用顺序表中,尾插函数,把这个结构体直接插入即可。
//通讯录添加数据
void ContactAdd(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);
}

4.4查找删除显⽰联系⼈信息

剩下的功能实现都要先查找到数据元素,这里写一个基于名字的查找函数函数,如果找到就把下标返回,否则返回-1。

int FindByName(Contact* con, char name[])
{
	for (int i = 0; i < con->size; i++)
	{
		if (0 == strcmp(con->arr[i].name, name))
		{
			//找到了
			return i;
		}
	}
	//没有找到
	return -1;
}

删除数据之前要先找到该元素,所以调用一下,上面写,查找函数FindByName。如果该下表有意义,那直接将该下表传进之前写的顺序表指定位置删除函数,就能把该数据删除了。

而顺序表的修改差不多,先找到该元素,利用下表,直接访问修改该数据就行了

//通讯录删除数据
void ContactDel(Contact* con)
{
	char name[NAME_MAX];
	printf("请输入要删除数据联系人的姓名:\n");
	scanf("%s", name);
	int ret=FindByName(con, name);
	if (ret != -1)
	{
		SLErase(con, ret);
		printf("删除成功\n");
		return;
	}
	else
		printf("该联系人不存在\n");
}

//通讯录的修改
void ContactModify(Contact* con)
{
	char name[NAME_MAX];
	printf("请输入要修改数据联系人的姓名:\n");
	scanf("%s", name);
	int ret = FindByName(con, name);
	if (ret != -1)
	{
		printf("请输入新的联系人姓名:\n");
		scanf("%s", con->arr[ret].name);

		printf("请输入新的联系人性别:\n");
		scanf("%s", con->arr[ret].gender);

		printf("请输入新的联系人年龄:\n");
		scanf("%d", &con->arr[ret].age);

		printf("请输入新的联系人电话:\n");
		scanf("%s", &con->arr[ret].tel);

		printf("请输入新的联系人住址:\n");
		scanf("%s", con->arr[ret].addr);
	}
	else
		printf("该联系人不存在\n");
}

这里查找也是先调用上面的那个查找函数FindByName。然后将要查找的数据源打印出来。
展示全部数据函数,又for循环将它们的个一个打印出来就行
//查找
int ContactFind(Contact* con)
{
	char name[NAME_MAX];
	printf("请输入要查找数据联系人的姓名:\n");
	scanf("%s", name);
	int ret = FindByName(con, name);
	if (ret != -1)
	{
		printf("%3s %3s %3d %3s %3s \n",
			con->arr[ret].name, 
			con->arr[ret].gender,
			con->arr[ret].age,
			con->arr[ret].tel,
			con->arr[ret].addr);
		return ret;
	}
	else
		printf("联系人不存在\n ");
	return -1;
}


void ContactShow(Contact* con)
{
	if (con->size == 0)
	{
		printf("联系人不存在\n ");
		return;
	}
	printf("%3s %3s %3s %3s %3s \n", "姓名", "性别", "年龄", "电话", "地址");
	for (int i = 0; i < con->size; i++)
	{
		printf(" %3s  %3s  %3d  %3s  %3s \n",
			con->arr[i].name,
			con->arr[i].gender,
			con->arr[i].age,
			con->arr[i].tel,
			con->arr[i].addr);
	}

}

信息持久化,这里的调用二进制文件函数实现。

写的时候,我们是将一整块联系人数据用for循环写入。

读文件:

一边读一边调用顺序表尾插函数。就可以写入上一次的完整信息了。

//信息持久化
void SvaeContact(Contact* con)
{
	FILE* pf = fopen("Contact.txt", "wb");
	if (pf == NULL)
	{
		perror("Contact.txt\n");
		return;
	}

	peoInfo info;
	for (int i = 0; i < con->size; i++)
	{
		fwrite(con->arr + i, sizeof(peoInfo), 1, pf);
	}

	fclose(pf);
	pf = NULL;
}




void LoadContact(Contact* con)
{
	FILE* pf = fopen("Contact.txt", "rb");
	if (pf == NULL)
	{
		perror("Contact.txt\n");
		return;
	}
	peoInfo info;
	while (fread(&info, sizeof(peoInfo), 1, pf) != 0)
	{
		SLPushBack(con,info);
	}
	printf("历史数据读取成功\n");

	fclose(pf);
	pf = NULL;
}

完整文件在开头,需要的自取

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值