顺序表的应用
目录
一、基于顺序表的通讯录项目
1.1.顺序表头文件编写(SeqList.h)
1.1.1.头文件包含
cpp
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "Contact.h"
1.1.2.数组元素类型重定义
cpp
typedef peoInfo SLDataType;
注:
数组元素为联系人结构变量,而顺序表的头文件中是没有定义这个变量的,需要引入通讯录的头文件才能将这个元素类型重命名
1.1.3.顺序表结构定义
cpp
typedef struct SeqList
{
SLDataType* arr;
int size;//有效数据个数
int capacity;//空间大小
}SL;
1.1.4.顺序表功能函数声明
cpp
void SLInit(SL* ps);
void SLDestroy(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);
1.2.顺序表源文件编写(SeqList.c)
1.2.1.头文件包含
cpp
#include "SeqList.h"
1.2.2.顺序表的初始化
cpp
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
1.2.3.空间检测函数
cpp
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));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
1.2.4.顺序表的尾插
cpp
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;
}
1.2.5.顺序表的头插
cpp
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++;
}
1.2.6.顺序表的尾删
cpp
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->arr);
--ps->size;
}
1.2.7.顺序表的头删
cpp
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->arr);
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
1.2.8.指定位置前插入数据
cpp
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++;
}
1.2.9.删除指定位置的数据
cpp
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
1.2.10.顺序表的销毁
cpp
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
1.3.通讯录头文件编写(Contact.h)
1.3.1.头文件包含
cpp
#pragma once
1.3.2.联系人结构定义
cpp
宏定义常量,便于参数修改
#define NAME_MAX 20
#define GENDER_MAX 10
#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;
1.3.3.顺序表结构前置声明
cpp
struct SeqList;
**解析:**要想在通讯录的头文件中使用顺序表结构体,需要引入顺序表的头文件,但为了避免顺序表的头文件与通讯录的头文件相互包含,可以通过结构体的【前置声明】来解决这个问题
1.3.4.顺序表结构重命名
cpp
typedef struct SeqList Contact;
**解析:**可以将顺序表结构变量struct SeqList改名为通讯录结构变量Contact,方便识别
**注:**结构体变量改名必须要用结构体变量创建时的名字struct SeqList,而非SL
1.3.5.通讯录的初始化
cpp
void ContactInit(Contact* con);
1.3.6.通讯录的销毁
cpp
void ContactDestroy(Contact* con);
1.3.7.通讯录添加数据
cpp
void ContactAdd(Contact* con);
1.3.8.通讯录删除数据
cpp
void ContactDel(Contact* con);
1.3.9.通讯录的修改
cpp
void ContactModify(Contact* con);
1.3.10.通讯录的查找
cpp
void ContactFind(Contact* con);
1.3.11.通讯录的展示
cpp
void ContactShow(Contact* con);
1.3.12.通讯录的保存
cpp
void SaveContact(Contact* con);
1.3.13.通讯录的读取
cpp
void LoadContact(Contact* con);
1.4.通讯录源文件编写(Contact.c)
1.4.1.头文件包含
cpp
#include "Contact.h"
#include "SeqList.h"
1.4.2.通讯录的初始化
cpp
void ContactInit(Contact* con)
{
SLInit(con);
LoadContact(con);
}
**注:**在通讯录初始化之后要读取文件中通讯录的数据
1.4.3.通讯录的销毁
cpp
void ContactDestroy(Contact* con)
{
SaveContact(con);
SLDestroy(con);
}
**注:**在通讯录销毁之前要将当前通讯录数据进行文件操作保存
1.4.4.通讯录添加数据
cpp
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);
}
解析:
- 声明一个联系人结构体变量
- 初始化联系人结构体中的成员变量
- 尾插到通讯录中
1.4.5.通讯录删除数据
cpp
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;
}
void ContactDel(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");
}
解析:
- 创建name变量输入要查找的联系人姓名
- 创建find变量接收姓名查找函数的返回值
- 如果find小于0则联系人不存在,如果查找成功,则删除该下标值的联系人数据
**注:**由于通讯录的删除,修改,查找都需要姓名查找,可以将这段代码封装成一个函数
代码如下:
1.4.5.1姓名查找函数
cpp
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;
}
解析:
- for循环遍历通讯录,通过strcmp函数比较输入的联系人姓名与通讯录中的联系人姓名
- 如果相同则返回该联系人的下标值,如果不同就返回-1
1.4.6.通讯录的修改
cpp
void ContactModify(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");
}
解析:
- 创建name变量输入要查找的联系人姓名
- 创建find变量接收姓名查找函数的返回值
- 如果find小于0则联系人不存在,如果查找成功,则修改该下标值的联系人数据
1.4.7.通讯录的查找
cpp
void ContactFind(Contact* con)
{
char name[NAME_MAX];
printf("请输入要查找的联系人姓名\n");
scanf("%s", name);
int find = FindByName(con, name);
if (find < 0)
{
printf("要查找的联系人数据不存在!\n");
return;
}
printf("%-10s %-10s %-10s %-10s %-10s\n", "姓名", "性别", "年龄", "电话", "地址");
printf("%-10s %-10s %-10d %-10s %-10s\n",
con->arr[find].name,
con->arr[find].gender,
con->arr[find].age,
con->arr[find].tel,
con->arr[find].addr
);
}
解析:
- 创建name变量输入要查找的联系人姓名
- 创建find变量接收姓名查找函数的返回值
- 如果find小于0则联系人不存在,如果查找成功,则打印表头和该下标值的联系人数据
1.4.8.通讯录的展示
cpp
void ContactShow(Contact* con)
{
printf("%-10s %-10s %-10s %-10s %-10s\n", "姓名", "性别", "年龄", "电话", "地址");
for (int i = 0; i < con->size; i++)
{
printf("%-10s %-10s %-10d %-10s %-10s\n",
con->arr[i].name,
con->arr[i].gender,
con->arr[i].age,
con->arr[i].tel,
con->arr[i].addr
);
}
}
解析:
- 打印表头:姓名、性别、年龄、电话、地址
- for循环遍历通讯录,按照格式打印每一条联系人的数据,打印格式可以自行调整
1.4.9通讯录的保存
cpp
void SaveContact(Contact* con)
{
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL)
{
perror("fopen error!\n");
return;
}
for (int i = 0; i < con->size; i++)
{
fwrite(con->arr + i, sizeof(peoInfo), 1, pf);
}
fclose(pf);
pf = NULL;
printf("通讯录数据保存成功!\n");
}
解析:
- 用二进制只写的方式打开contact.txt文件
- for循环遍历通讯录,将所有数据写入文件
- 关闭文件流,避免内存泄露
补充:
回顾fwrite函数:
cpp
size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
| 参数 | 含义 |
|---|---|
| ptr | 指向要写入的数组或结构体的地址 |
| size | 每个数据的字节数 |
| count | 要写入数据的个数 |
| stream | 目标流(只能是文件流) |
| 返回值 | 成功写入的数据个数 |
**功能:**将ptr指向的数组或结构体数据以二进制形式写入文件流
1.4.10.通讯录的读取
cpp
void LoadContact(Contact* con)
{
FILE * pf = fopen("contact.txt", "rb");
if (pf == NULL)
{
perror("fopen error!\n");
return;
}
peoInfo info;
while (fread(&info, sizeof(peoInfo), 1, pf))
{
SLPushBack(con, info);
}
fclose(pf);
pf = NULL;
printf("历史数据导⼊通讯录成功!\n");
}
解析:
- 用二进制只读的方式打开contact.txt文件
- 创建一个联系人结构的变量
- while循环遍历通讯录,将从文件流中读取的数据存入联系人结构体
- 将联系人结构变量尾插进通讯录中
- 读取结束后跳出循环
- 关闭文件流,避免内存泄露
补充:
回顾fread函数:
cpp
size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
| 参数 | 含义 |
| ptr | 指向要读取的数组或结构体的地址 |
| size | 每个数据的字节数 |
| count | 要读取数据的个数 |
| stream | 目标流(只能是文件流) |
| 返回值 | 成功读取的数据个数 |
|---|
**功能:**从文件流中读取二进制数据到ptr指向数组或结构体空间
1.5.通讯录测试文件编写(test.c)
1.5.1.头文件包含
cpp
#include "SeqList.h"
#include "Contact.h"
1.5.2.测试方法01
cpp
void ContactTest01()
{
声明通讯录结构类型的变量
Contact con;
初始化通讯录结构类型变量
ContactInit(&con);
通讯录添加数据
ContactAdd(&con);
通讯录删除数据
ContactDel(&con);
通讯录修改数据
ContactModify(&con);
通讯录展示数据
ContactShow(&con);
通讯录的销毁
ContactDestroy(&con);
}
int main()
{
ContactTest01();
return 0;
}
1.5.2.实现方法
cpp
void menu()
{
printf("------------------------------------------------------------------------------------------------------------------------------------------------\n");
printf("*********************通讯录*********************\n");
printf("*********1.增加联系人 2.删除联系人**********\n");
printf("*********3.修改联系人 4.查找联系人**********\n");
printf("*********5.展示联系人 0.退出通讯录**********\n");
printf("***********************************************\n");
printf("------------------------------------------------------------------------------------------------------------------------------------------------\n");
}
int main()
{
int op = -1;
Contact con;
ContactInit(&con);
do
{
menu();
printf("请选择您的操作:\n");
scanf("%d", &op);
switch (op)
{
case 1:
ContactAdd(&con);
break;
case 2:
ContactDel(&con);
break;
case 3:
ContactModify(&con);
break;
case 4:
ContactFind(&con);
break;
case 5:
ContactShow(&con);
break;
case 0:
printf("退出通讯录......\n");
break;
default:
printf("输入错误,请重新选择您的操作!\n");
break;
}
} while (op != 0);
ContactDestroy(&con);
return 0;
}
二、顺序表的经典算法
试题1:移除元素
题目内容:
给你一个数组nums和一个值val,你需要原地移除所有数值等于val的元素
元素的顺序可能发生改变。然后返回nums中与val不同的元素的数量
示例1:
输入:nums = [3,2,2,4],val = 3
输出:2,nums = [2,2]
思路1:双指针法
思路解析:
创建两个变量src(源数据)和dst(目标数据)
让src遍历数组
如果src指向的数据为val,则src往后走
如果src指向的数据不为val,则将src指向的数据赋给dst,src与dst一起往后走
src跳出数组后,dst中的数据刚好为新的数组中元素个数
代码部分:
cpp
int removeElement(int* nums, int numsSize, int val)
{
int src,dst;
src = dst = 0;
while(src < numsSize)
{
if(nums[src] == val)
{
src++;
}
else
{
nums[dst] = nums[src];
dst++;
src++;
}
}
return dst;
}
试题2:合并两个有序数组
题目内容:
给你两个按非递减顺序排列的整数数组nums1和nums2
另有两个整数m和n,分别表示nums1和nums2中的元素数目
请你合并nums2到nums1中,使合并后的数组同样按非递减顺序排列
示例1:
输入:nums1 = [1,2,3,0,0,0],m = 3,nums2 = [2,5,6],n = 3
输出:[1,2,2,3,5,6]
注意:
nums1的初始长度为m+n
思路1:排序法
思路解析:
将nums2中的数据依次放入到nums1数组的后面,用排序算法对nums1进行排序
但借助效率低下的排序算法会影响到程序整体的运行效率
思路2:三指针法
思路解析:
创建两个指针变量l1和l2
从前往后比谁小,谁小谁就往前放:
l1指向nums1首元素,l2指向num2首元素
如果l1小于l2,l1往后走,如果l1等于l2,l1往后走
如果l1大于l2,则将l2的值赋给l1,但l1中的值就会被l2的值覆盖
这种方式行不通
从后往前比谁大,谁大谁就往后放:
l1指向nums1最后一个有效数据,l2指向nums2最后一个有效数据
我们还需要再创建一个指针变量l3指向nums1最后一个位置来存放数据
如果l1小于l2,则将l2的值赋给l3,l2和l3往前走
如果l1大于l2,则将l1的值赋给l3,l1和l3往前走
如果l1等于l2,则将l1或l2的值赋给l3,赋值的那个指针变量与l3往前走
注:
如果是l1先出循环,nums2中还有数据未放到nums1中
最后需要循环将num2的剩余数据放入nums1数组的前面
代码部分:
cpp
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
int l1,l2,l3;
l1 = m - 1;
l2 = n - 1;
l3 = m + n - 1;
while(l1 >= 0 && l2 >= 0)
{
if(nums1[l1] > nums2[l2])
{
nums1[l3--] = nums1[l1--];
}
else
{
nums1[l3--] = nums2[l2--];
}
}
while(l2 >= 0)
{
nums1[l3--] = nums2[l2--];
}
}
三、顺序表的问题
- 中间/头部插入效率低下
- 增容降低运行效率
- 增容造成空间浪费
四、总结
本篇博客是对于数据结构中顺序表应用的整理归纳,后续还会更新链表等内容,如果对你有帮助,欢迎点赞+收藏+关注,让我们一起共同进步🌟~