数据结构(初阶)笔记归纳3:顺序表的应用

顺序表的应用

目录

顺序表的应用

一、基于顺序表的通讯录项目

1.1.顺序表头文件编写(SeqList.h)

1.1.1.头文件包含

1.1.2.数组元素类型重定义

1.1.3.顺序表结构定义

1.1.4.顺序表功能函数声明

1.2.顺序表源文件编写(SeqList.c)

1.2.1.头文件包含

1.2.2.顺序表的初始化

1.2.3.空间检测函数

1.2.4.顺序表的尾插

1.2.5.顺序表的头插

1.2.6.顺序表的尾删

1.2.7.顺序表的头删

1.2.8.指定位置前插入数据

1.2.9.删除指定位置的数据

1.2.10.顺序表的销毁

1.3.通讯录头文件编写(Contact.h)

1.3.1.头文件包含

1.3.2.联系人结构定义

1.3.3.顺序表结构前置声明

1.3.4.顺序表结构重命名

1.3.5.通讯录的初始化

1.3.6.通讯录的销毁

1.3.7.通讯录添加数据

1.3.8.通讯录删除数据

1.3.9.通讯录的修改

1.3.10.通讯录的查找

1.3.11.通讯录的展示

1.3.12.通讯录的保存

1.3.13.通讯录的读取

1.4.通讯录源文件编写(Contact.c)

1.4.1.头文件包含

1.4.2.通讯录的初始化

1.4.3.通讯录的销毁

1.4.4.通讯录添加数据

1.4.5.通讯录删除数据

1.4.5.1姓名查找函数

1.4.6.通讯录的修改

1.4.7.通讯录的查找

1.4.8.通讯录的展示

1.4.9通讯录的保存

1.4.10.通讯录的读取

1.5.通讯录测试文件编写(test.c)

1.5.1.头文件包含

1.5.2.测试方法01

1.5.2.实现方法

二、顺序表的经典算法

试题1:移除元素

试题2:合并两个有序数组

三、顺序表的问题

四、总结


一、基于顺序表的通讯录项目

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

三、顺序表的问题

  1. 中间/头部插入效率低下
  2. 增容降低运行效率
  3. 增容造成空间浪费

四、总结

本篇博客是对于数据结构中顺序表应用的整理归纳,后续还会更新链表等内容,如果对你有帮助,欢迎点赞+收藏+关注,让我们一起共同进步🌟~

相关推荐
光算科技2 小时前
AI重写工具导致‘文本湍流’特征|如何人工消除算法识别标记
大数据·人工智能·算法
星竹晨L2 小时前
【C++内存安全管理】智能指针的使用和原理
开发语言·c++
智者知已应修善业2 小时前
【C语言 dfs算法 十四届蓝桥杯 D飞机降落问题】2024-4-12
c语言·c++·经验分享·笔记·算法·蓝桥杯·深度优先
罗湖老棍子2 小时前
最优乘车(travel)(信息学奥赛一本通- P1377)
算法·图论·bfs·最短路·字符串流·单向边
九成宫2 小时前
计算机网络期末复习——第4章:网络层 Part Three
网络·笔记·计算机网络·软件工程
旺仔小拳头..2 小时前
Java ---变量、常量、类型转换、默认值、重载、标识符、输入输出、访问修饰符、泛型、迭代器
java·开发语言·python
副露のmagic2 小时前
更弱智的算法学习 day36
学习·算法
lsx2024062 小时前
Vue3 自定义指令
开发语言
core5122 小时前
SVD 算法详解:给数据做个“CT扫描”
算法·svd·图片压缩·目标函数