🌈个人主页:小新_-
🎈个人座右铭:“成功者不是从不失败的人,而是从不放弃的人!”🎈
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
🏆所属专栏: 话说那些与C++的爱恨情仇 欢迎订阅,持续更新中~~~
✨让小新带着你快乐的学习吧~✨
目录
【知识框架】
1、线性表的定义
线性表(List):零个或多个数据元素的有限序列。
线性表的数据集合为{a1,a2,…,an},假设每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。
线性表特点:
- 元素个数有限
- 元素具有先后顺序(逻辑上的先后顺序)
- 表中每个元素都是数据元素,每个元素都是单个元素
- 表中元素的数据类型相同(意味着每个元素占用存储空间大小一样)
- 表中元素具有抽象性,仅讨论元素间的逻辑关系,而不考虑元素究竟表示什么内容
注意:线性表是一种逻辑结构,表示元素间一对一的相邻关系。顺序表、链表是指存储结构
2、顺序表分类
动态顺序表:
3.动态顺序表的实现
准备工作
1、文件的准备
首先我们创建三个文件,一个是sqlist.c用于实现各种函数接口,一个为sqlist.h用于放置函数声明,另外一个test.c用于测试函数的正确性。他们三个文件最后链接实现我们的动态顺序表。
要实现的函数接口,就放在下面啦!
sqlist.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
//动态顺序表
typedef int SLDataType;
typedef struct Seqlist {
SLDataType* arr;//数据
int capacity;//顺序表空间大小
int size;//有效数据个数
}SL;
void SLInit(SL* ps);
void SLDestroy(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);
2、顺序表的定义
首先在顺序表.h的文件里,把顺序表这个结构体先定义下来,定义完后,可以用typedef改名,然后,我们要想这个数组里必须是int型的吗,不一定,那我就用typedef提前把int重类型一下,这样我们就可以不仅仅停留在int,若想改成其他类型,直接在typedef后将int改成你想在顺序表放的类型就可以了。
3、扩容
在插入之前我们要想咱们现在初始化容量数为0,得扩容呀,那我们就扩容定义\n\n如何来扩容那,我们一般规定扩容到原空间的两倍,扩容用到的函数是realloc函数,但刚开始是0,我们可以用三目操作符来判断如果空间为0,我们要给它4个空间数,若不是0,直接capacity乘2,接着再使用新的容量数乘每个类型的字节数放到realloc里就是开辟了新的空间。至于为什么是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, 2 * newcapacity*sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
//扩容成功
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
4、打印当前顺序表
逻辑比较简单,不再赘述
代码如下:
void SLPrint(SL* ps) //打印
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
1、初始化和销毁
分析:
初始化十分简单,将数组置为空,因为此时没有数据。顺序表的空间大小和有效数据个数置为0。我们的初始化就完成了。
void SLInit(SL* ps)//初始化
{
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
分析:销毁基本和初始化差不多,俗话说一切回到原点,只不过为了健壮性要断言ps指针,然后将其空间归还给操作系统。
void SLDestroy(SL* ps)
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
2、插入
2.1尾插
分析:
尾插比较简单,空间足够,直接在顺序表尾部插入即可。不够就得先扩容,再插入
代码如下:
void SLPushBack(SL* ps, SLDataType x)//尾插
{
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size] = x;
ps->size++;
}
2.2头插
分析:
头插,我们要先将原来的数据整体往后挪动一位,这要通过一个for循环来实现,然后让头结点的值等于我们要赋予的值。如果空间不够,就得先扩容
代码如下:
void SLPushFront(SL* ps, SLDataType x)//头插
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i-1];
}
ps->arr[0] = x;
ps->size++;
}
2.3任意位置之前插入
分析:
首先,要删除的顺序表不能为空。再通过for循环让pos位置以及pos之后的元素向后挪动一位,再讲pos位置赋值为要给定的值即可
代码如下:
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
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++;
}
3、删除
3.1头删
分析:
首先,删除的顺序表肯定不为空,在通过for循环将元素整体往前挪动一位,有效数据个数size-1即可
代码如下:
void SLPopFront(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->arr[ps->size-1]; i++)
{
ps->arr[i] = ps->arr[i +1];
}
ps->size--;
}
3.2尾删
分析:
尾删其实很简单,只需将尾部元素置为0,size--即可。当然,顺序表为空,不能执行删除操作,所以要判断一下。
代码如下:
void SLPopBack(SL* ps)//尾部删除
{
assert(ps);
assert(ps->size);
ps->arr[ps->size] = 0;
ps->size--;
}
3.3任意位置删除
分析:
逻辑比较简单,首先判断是否为空。然后让pos之后的数据往前挪动一位,这样数据会被覆盖,也就完成了删除的效果。
代码如下:
void SLErase(SL* ps, int pos)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = pos; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
4、查找表中元素
分析:遍历顺序表,找到了就返回元素下标,没找到返回-1.
代码如下:
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
完整代码
sqlist.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"sqlist.h"
#include<assert.h>
void SLInit(SL* ps)//初始化
{
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
void SLCheckCapacity(SL* ps)//判断是否扩容
{
if (ps->capacity == ps->size)
{//扩容
int newcapacity = ps->capacity = 0 ? 4 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, 2 * newcapacity*sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
//扩容成功
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
void SLPushBack(SL* ps, SLDataType x)//尾插
{
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size] = x;
ps->size++;
}
void SLPushFront(SL* ps, SLDataType x)//头插
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i-1];
}
ps->arr[0] = x;
ps->size++;
}
void SLPopBack(SL* ps)//尾部删除
{
assert(ps);
assert(ps->size);
ps->arr[ps->size] = 0;
ps->size--;
}
void SLPopFront(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->arr[ps->size-1]; i++)
{
ps->arr[i] = ps->arr[i +1];
}
ps->size--;
}
void SLPrint(SL* ps) //打印
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
//指定位置之前插入数据
//删除指定位置数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
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);
SLCheckCapacity(ps);
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 (ps->arr[i] == x)
{
return i;
}
}
return -1;
}
void SLDestroy(SL* ps)
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
4、基于顺序表实现通讯录项目
通讯录就是给顺序表套一层壳,实际上还是顺序表。只不过里面的数据类型变成了一个结构体
手机通讯录其实就是一个顺序表,顺序表的每一个成员都是一个结构体,结构体中包含联系人的
名字、性别、年龄、电话、地址等信息,只不过我们之前建立的顺序表里存储的是整形数据,而通讯录里存储的是结构体类型而已。
所以我们还要创建一个结构体来存储联系人信息。
#define MAX_NAME 20
#define MAX_GENDER 10
#define MAX_PHONE 20
#define MAX_ADDR 30
typedef struct contacts
{
char name[MAX_NAME];
char gender[MAX_GENDER];
int age;
char phone[MAX_PHONE];
char addr[MAX_ADDR];
}contacts;
既然通讯录就是顺序表,那我们可以给顺序表改类型一个名字
struct SList;
typedef struct SList CON;
4.1功能要求
1)⾄少能够存储100个⼈的通讯信息
2)能够保存⽤⼾信息:名字、性别、年龄、电话、地址等
3)增加联系⼈信息
4)删除指定联系⼈
5)查找制定联系⼈
6)修改指定联系⼈
7)显示联系⼈信息
4.2通讯录初始化
void ContactInit(CON* con)
{
SlInit(con);
}
通讯录的初始化也就是顺序表的初始化,我们直接调用之前的顺序表初始化函数即可。
4.3添加联系人
void AddContact(CON* con)
{
contacts f;
printf("请输入要添加的联系人姓名:");
scanf("%s", f.name);
printf("请输入要添加的联系人性别:");
scanf("%s", f.gender);
printf("请输入要添加的联系人年龄:");
scanf("%d", &f.age);
printf("请输入要添加的联系人电话:");
scanf("%s", f.phone);
printf("请输入要添加的联系人住址:");
scanf("%s", f.addr);
AddTail(con, f);
}
我们先定义一个联系人结构体,再添加联系人信息,最后调用尾插函数,把联系人尾插到通讯录中。
4.4查看某联系人是否存在
int FundByContact(CON* con, char name[])
{
assert(con);
for (int i = 0; i < con->size; i++)
{
if (0 == strcmp(con->arr[i].name, name))
{
return i;
}
}
return -1;
}
遍历数组,如果遍历到了要查找的联系人的名字,就返回数组成员下标,否则返回一个小于0的数。
4.5删除联系人
void DelContact(CON* con)
{
assert(con);
char name[MAX_NAME];
printf("请输入要删除的联系人姓名:\n");
scanf("%s", name);
int fund = FundByContact(con, name);
if (fund < 0)
{
printf("该联系人不存在");
}
SpeLocDel(con, fund);
}
调用FundByContact函数,如果要删除的联系人存在,那么返回数组下标,在调用SpeLocDel函数,删除改下表对应的联系人。
4.6打印通讯录
void PrintContact(CON* 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].phone,
con->arr[i].addr);
}
}
遍历数组,打印结构体成员信息。
4.7修改联系人
void ModifyContact(CON* con)
{
char name[MAX_NAME];
printf("请输入要修改的联系人姓名");
scanf("%s", name);
int fund = FundByContact(con, name);
if (fund < 0)
{
printf("该联系人不存在");
}
printf("请输入新的联系人姓名:\n");
scanf("%s", con->arr[fund].name);
printf("请输入新的联系人性别:\n");
scanf("%s", con->arr[fund].gender);
printf("请输入新的联系人年龄:\n");
scanf("%d", &con->arr[fund].age);
printf("请输入新的联系人电话:\n");
scanf("%s", con->arr[fund].phone);
printf("请输入新的联系人住址:\n");
scanf("%s", con->arr[fund].addr);
}
如果要修改的联系人存在,用fund接收返回的下标,再通过下标修改联系人信息。
4.7查找联系人
void FundContact(CON* con)
{
assert(con);
char name[MAX_NAME];
printf("请输入要查找的联系人姓名:\n");
scanf("%s", name);
int fund = FundByContact(con, name);
if (fund < 0)
{
printf("该联系人不存在");
}
printf("%s %s %d %s %s\n",
con->arr[fund].name,
con->arr[fund].gender,
con->arr[fund].age,
con->arr[fund].phone,
con->arr[fund].addr);
}
如果要查找的联系人存在,打印该联系人的信息。
完整代码
contest.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
#include"sqlist.h"
#include<string.h>
void ContactInit(Contact* pcon)//实际初始化的还是顺序表
{
SLInit(pcon);
}
void ContactDesTroy(Contact* pcon)
{
SLDestroy(pcon);
}
//增加、删除、修改、查找、查看通讯录
int FindByName(Contact* pcon,char name[])
{
for (int i = 0; i < pcon->size; i++)
{
if (strcmp(pcon->arr->name, name) == 0)
{
return i;
}
}
return -1;
}
void ContactAdd(Contact* pcon)
{
Info info;
printf("请输入联系人姓名:\n");
scanf("%s", info.name);
printf("请输入联系人年龄:\n");
scanf("%d", &info.age);
printf("请输入联系人性别:\n");
scanf("%s", info.gender);
printf("请输入联系人住址:\n");
scanf("%s", info.addr);
printf("请输入联系人电话:\n");
scanf("%s", info.tel);
SLPushBack(pcon, info);
}
void ContactDel(Contact* pcon)
{
//删除之前一定要先查找
//找到了,可以删除
//找不到,不能执行删除
printf("请输入要删除的联系人姓名:\n");
char name[NAME_MAX];
scanf("%s", name);
int findIndex = FindByName(pcon, name);
if (findIndex < 0) {
printf("要删除的联系人不存在!\n");
return;
}
//执行删除操作
SLErase(pcon, findIndex);
printf("联系人删除成功!\n");
}
void ContactModify(Contact* pcon)
{
char name[NAME_MAX];
printf("请输入要修改的联系人姓名:\n");
scanf("%s", name);
int findIndex = FindByName(pcon, name);
if (findIndex < 0)
{
printf("要修改的联系人不存在!\n");
return;
}
//找到了,执行修改操作
//int arr[10] int arr[findIndex] = 100
printf("请输入姓名:\n");
scanf("%s", pcon->arr[findIndex].name);
printf("请输入年龄:\n");
scanf("%d", &pcon->arr[findIndex].age);
printf("请输入性别:\n");
scanf("%s", pcon->arr[findIndex].gender);
printf("请输入电话:\n");
scanf("%s", pcon->arr[findIndex].tel);
printf("请输入地址:\n");
scanf("%s", pcon->arr[findIndex].addr);
printf("联系人修改成功!\n");
}
void ContactFind(Contact* pcon)
{
char name[NAME_MAX];
printf("请输入要查找的用户姓名:\n");
scanf("%s", name);
int findIndex = FindByName(pcon, name);
if (findIndex < 0) {
printf("该联系人不存在!\n");
return;
}
//找到了,打印一下查找的联系人信息
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "住址");
printf("%s %s %d %s %s\n",
pcon->arr[findIndex].name,
pcon->arr[findIndex].gender,
pcon->arr[findIndex].age,
pcon->arr[findIndex].tel,
pcon->arr[findIndex].addr
);
}
void ContactShow(Contact* pcon)
{
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "住址");
for (int i = 0; i < pcon->size; i++)
{
printf("%s %s %d %s %s\n",
pcon->arr[i].name,
pcon->arr[i].gender,
pcon->arr[i].age,
pcon->arr[i].tel,
pcon->arr[i].addr
);
}
}
contest.h
#pragma once
#define NAME_MAX 100
#define GENDER_MAX 10
#define TEL_MAX 12
#define ADDR_MAX 100
#include<stdio.h>//暂时加上
//通讯录数据类型
typedef struct PersonInfo
{
char name[NAME_MAX];
int age;
char gender[GENDER_MAX];
char tel[TEL_MAX];
char addr[ADDR_MAX];
}Info;
struct Seqlist;
typedef struct Seqlist Contact;
//使用顺序表的前置声明
//struct SeqList;
//typedef struct SeqList Contact;
//通讯里提供的操作
//通讯录的初始化和销毁
void ContactInit(Contact* pcon);//实际初始化的还是顺序表
void ContactDesTroy(Contact* pcon);
//增加、删除、修改、查找、查看通讯录
void ContactAdd(Contact* pcon);
void ContactDel(Contact* pcon);
void ContactModify(Contact* pcon);
void ContactFind(Contact* pcon);
void ContactShow(Contact* pcon);
5、经典顺序表OJ
1.经典算法OJ题1: 移除元素(点击进入题目)
int removeElement(int* nums, int numsSize, int val) {
int i=0,j=0;
while(j<numsSize)
{
if(nums[j]==val)
{
j++;
}
else{
nums[i]=nums[j];
i++;
j++;
}
}
return i;
}
2.经典算法OJ题2: 合并俩个有序数组(点击进入题目)
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
int l1=m-1;
int l2=n-1;
int l3=m+n-1;
while(l1>=0&&l2>=0)
{
if(nums1[l1]>nums2[l2])
{
nums1[l3]=nums1[l1];
l3--;
l1--;
}
else
{
nums1[l3]=nums2[l2];
l3--;
l2--;
}
}
while(l2>=0)
{
nums1[l3]=nums2[l2];
l3--;
l2--;
}
}
6. 顺序表的问题及思考
最后,感谢大家的观看~~