顺序表相关知识总结并基于此实现通讯录

目录

引言

顺序表知识回顾

线性表

顺序表的分类与缺陷

动态顺序表的代码实现

顺序表的定义

顺序表的思考

顺序表的方法

初始化与销毁

顺序表代码:

Seqlist.h

Seqlist.c

从顺序表到通讯录

前置申明

通讯录代码:

Contact.h

Contact.c


引言

本文详细介绍了顺序表的相关知识,并基于此实现了可无限扩容,可实现数据增,删,查,改,功能的通讯录。

顺序表知识回顾

线性表

顺序表是线性表的一种,而线性表是一种实际中被广泛使用的数据结构,常见的线性表有:顺序表,栈,链表,队列,字符串........。线性表在逻辑上是线性结构,但在物理上不一定是线性的,比如链表,线性表在物理存储上,通常是数组和链式结构的形式,而本文要讨论的顺序表在逻辑上和物理上都是线性的。因为顺序表的底层就是数组,数组在内存上是连续存放的。

顺序表的分类与缺陷

顺序表分为动态顺序表和静态顺序表两种,静态顺序表,即能储存的空间大小是固定的顺序表,动态顺序表,即可以根据实际需要,动态的在内存中申请空间,以满足需要的一种数据结构。动态顺序表,虽然较静态顺序表功能更强,但是仍有以下几点缺陷:

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);
}

  • 40
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值