(图片由AI生成)?
在程序设计的世界里,数据结构是非常重要的基础概念。本文将专注于C语言中的一种基本数据结构——顺序表。我们将从数据结构的基本概念讲起,逐步深入到顺序表的内部结构、分类,最后通过一个实战项目来具体展示顺序表的应用。
数据结构是计算机科学中的一个重要概念,它是指计算机中存储、组织数据的方式。数据结构关注的不仅仅是数据的存储,还包括数据之间的关系以及如何高效地访问和修改这些数据。数据结构的选择和设计对于软件开发的效率、性能和可维护性有着决定性的影响。
数据结构通常分为两类:逻辑结构和物理结构。
逻辑结构:它关注数据对象中数据元素之间的关系。逻辑结构分为线性结构、树结构、图结构等。
物理结构(存储结构):它关注数据的存储方式。物理结构主要包括顺序存储结构和链式存储结构。
数据结构对于计算机程序的设计至关重要。它不仅决定了程序的内存使用效率,还直接影响到程序中各种操作的执行效率。比如,在一个排序好的二叉搜索树中查找一个元素通常比在链表中快得多。因此,选择合适的数据结构可以极大地提高程序的效率和性能。
线性表是数据结构中最简单和最常用的一种结构,它是一组具有相同数据类型的数据元素的有限序列。
线性表支持多种操作,包括但不限于:
线性表可以用两种方式存储:
线性表广泛应用于程序设计中,例如:
顺序表是线性表的一种存储方式,它通过一段连续的存储单元依次存放线性表的数据元素。在顺序表中,每个数据元素的位置关系是由它们的存储顺序决定的。这种数据结构在C语言等编程语言中通常使用数组来实现。
在C语言中,顺序表通常有以下两种实现方式:
静态顺序表:使用固定大小的数组来存储元素。例如:
#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int length;
} StaticList;
在这种结构中,MAX_SIZE
定义了顺序表的最大容量,data
数组用于存储表中的元素,length
表示顺序表的当前长度。?
动态顺序表:使用动态分配的数组来存储元素。这种方式更加灵活,可以在运行时根据需要调整表的大小。例如:
typedef struct {
int *data;
int length;
int capacity;
} DynamicList;
在这种结构中,data
指向一个动态分配的数组,length
表示表的当前长度,capacity
表示分配的数组的容量。
文件组成
SeqList.h:这个头文件定义了动态顺序表的结构体SeqList
,其中包括指向动态数组的指针、数组的当前大小和容量。它还声明了各种操作动态顺序表所需的函数。
SeqList.c:这个源文件包含了头文件中声明的所有函数的具体实现。
test.c:这个源文件包含main函数,为测试文件,用来测试函数接口的准确性。
//SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
typedef int SLDataType;
//动态顺序表
typedef struct SeqList
{
SLDataType* arr;//动态开辟的数组
size_t size;//有效元素个数
size_t capacity;//容量
}SL;
//初始化
void SLInit(SL* ps);
//销毁
void SLDestory(SL* ps);
//判断容量
void SLCheckCapacity(SL* ps);
//打印
void SLPrint(SL* ps);
//尾插
void SLPushBack(SL* ps, SLDataType x);
//尾删
void SLPopBack(SL* ps);
//头插
void SLPushFront(SL* ps, SLDataType x);
//头删
void SLPopFront(SL* ps);
//查找
int SLFind(SL* ps, SLDataType x);
//在pos位置插入x
void SLInsert(SL* ps, size_t pos, SLDataType x);
//删除pos位置的元素
void SLErase(SL* ps, size_t pos);
//SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//初始化
void SLInit(SL* ps)
{
assert(ps);
ps->arr = (SLDataType*)malloc(sizeof(SLDataType)* 3);
if (ps->arr == NULL)
{
assert(0);
return;
}
ps->size = 0;
ps->capacity = 3;
}
//销毁
void SLDestory(SL* ps)
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//检查容量
void SLCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(ps->arr, sizeof(SLDataType)*ps->capacity * 2);
if (tmp == NULL)
{
perror("realloc fail\n");
return;
}
ps->arr = tmp;
ps->capacity *= 2;
}
}
//打印
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size] = x;
ps->size++;
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
if (ps->size == 0)
{
return;
}
ps->size--;
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size - 1; i >= 0; i--)
{
ps->arr[i + 1] = ps->arr[i];
}
ps->arr[0] = x;
ps->size++;
}
//头删
void SLPopFront(SL* ps)
{
assert(ps);
if (ps->size == 0)
{
return;
}
for (int i = 0; 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;//没找到,返回-1
}
//插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size - 1; i >= pos; i--)
{
ps->arr[i + 1] = ps->arr[i];
}
ps->arr[pos] = x;
ps->size++;
}
//删除
void SLErase(SL* ps, size_t pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
if (ps->size == 0)
{
return;
}
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
//test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
void SLTest01()
{
SL sl;
SLInit(&sl);
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushFront(&sl, 4);
SLPushFront(&sl, 5);
SLPrint(&sl);
SLPopFront(&sl);
SLPrint(&sl);
}
int main()
{
SLTest01();
return 0;
}
SLDataType
)typedef int SLDataType;
这行代码定义了顺序表中元素的数据类型。在这里,SLDataType
被定义为int
类型。这意味着在这个顺序表实现中,每个元素都是一个整数。使用typedef
为数据类型创建一个别名(在这里是SLDataType
)使得代码更具可读性和灵活性。如果将来需要改变顺序表中存储的数据类型,只需修改这个typedef
声明即可。
SeqList
)typedef struct SeqList {
SLDataType* arr; // 动态开辟的数组
size_t size; // 有效元素个数
size_t capacity; // 容量
} SL;
这部分定义了动态顺序表的结构体,用于存储顺序表的数据和相关信息。
SLDataType* arr;
:这是一个指向SLDataType
类型的指针,它指向顺序表的实际数据存储区域。在这个顺序表实现中,这个区域是动态分配的。这意味着顺序表的大小可以在运行时改变,而不是在编译时固定。
size_t size;
:这个成员变量存储顺序表中当前存储的元素数量。它不仅用于追踪顺序表中已经使用的元素数量,还用于确定在哪里插入新元素或从哪里删除元素。
size_t capacity;
:这个成员变量表示顺序表分配的总容量,即arr
可以存储的最大元素数量。这个值通常大于或等于size
。当size
达到capacity
时,需要扩展arr
来容纳更多元素。
下面,我们对SeqList.c中的各个函数接口逐一剖析,以加强对顺序表实现的理解。?
SLInit
)void SLInit(SL* ps) {
assert(ps);
ps->arr = (SLDataType*)malloc(sizeof(SLDataType) * 3);
if (ps->arr == NULL) {
assert(0);
return;
}
ps->size = 0;
ps->capacity = 3;
}
ps
非空。然后,使用malloc
为数组分配初始容量(这里是3个元素的空间)。如果内存分配失败,则触发断言。最后,设置顺序表的大小为0,容量为3。SLDestory
)void SLDestory(SL* ps) {
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
ps
非空,然后释放动态数组arr
所占用的内存,并将其指针设置为NULL
。最后,将顺序表的大小和容量都重置为0。SLCheckCapacity
)void SLCheckCapacity(SL* ps) {
assert(ps);
if (ps->size == ps->capacity) {
SLDataType* tmp = (SLDataType*)realloc(ps->arr, sizeof(SLDataType) * ps->capacity * 2);
if (tmp == NULL) {
perror("realloc fail\n");
return;
}
ps->arr = tmp;
ps->capacity *= 2;
}
}
ps
非空。如果顺序表已满(size
等于capacity
),则使用realloc
将数组空间加倍。如果重新分配失败,则打印错误信息并返回。如果成功,则更新数组指针和容量值。SLPrint
)void SLPrint(SL* ps) {
assert(ps);
for (int i = 0; i < ps->size; i++) {
printf("%d ", ps->arr[i]);
}
printf("\n");
}
ps
非空,然后遍历顺序表,打印每个元素。SLPushBack
)void SLPushBack(SL* ps, SLDataType x) {
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size] = x;
ps->size++;
}
ps
非空,调用SLCheckCapacity
以确保有足够的容量。然后在数组的size
位置插入新元素x
,并将size
增加1。SLPopBack
)void SLPopBack(SL* ps) {
assert(ps);
if (ps->size == 0) {
return;
}
ps->size--;
}
ps
非空,然后检查顺序表是否为空。如果不为空,则将size
减1来移除最后一个元素。SLPushFront
)void SLPushFront(SL* ps, SLDataType x) {
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size - 1; i >= 0; i--) {
ps->arr[i + 1] = ps->arr[i];
}
ps->arr[0] = x;
ps->size++;
}
ps
非空,检查容量。然后将所有元素向后移动一个位置,为新元素腾出空间。最后在数组的第一个位置插入新元素,并将size
增加1。SLPopFront
)void SLPopFront(SL* ps) {
assert(ps);
if (ps->size == 0) {
return;
}
for (int i = 0; i < ps->size - 1; i++) {
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
ps
非空,检查顺序表是否为空。如果不为空,则将所有元素向前移动一个位置,并将size
减1。SLFind
)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; // 没找到,返回-1
}
ps
非空,然后遍历顺序表。如果找到与x
相等的元素,则返回其位置;如果遍历完仍未找到,则返回-1。SLInsert
)void SLInsert(SL* ps, size_t pos, SLDataType x) {
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size - 1; i >= pos; i--) {
ps->arr[i + 1] = ps->arr[i];
}
ps->arr[pos] = x;
ps->size++;
}
ps
非空,并且插入位置pos
有效(即在0和size
之间)。检查并调整容量。然后从尾部开始,将每个元素向后移动一个位置,为新元素腾出空间。最后在指定位置插入新元素,并将size
增加1。SLErase
)void SLErase(SL* ps, size_t pos) {
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (size_t i = pos; i < ps->size - 1; i++) {
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
ps
非空,并且删除位置pos
有效。然后从pos
位置开始,将每个元素向前移动一个位置,覆盖掉要删除的元素。最后将size
减1。Contact.h
: 定义了通讯录的数据结构和操作函数原型。SeqList.h
: 定义了动态顺序表的数据结构和操作函数原型,扩展自Contact.h
。Contact.c
: 实现了Contact.h
中声明的通讯录操作函数。SeqList.c
: 实现了SeqList.h
中声明的动态顺序表操作函数。test.c
: 主函数,用于运行通讯录应用。SeqList
): 用于存储通讯录中的联系人信息。动态顺序表提供了灵活的数据存储和访问方式。PersonInfo
): 定义了通讯录中每个联系人的详细信息,包括姓名、年龄、性别、电话和地址。ContactAdd
): 允许用户输入新的联系人信息,并将其添加到通讯录中。ContactDel
): 根据用户输入的姓名,查找并删除相应的联系人。ContactFind
): 根据用户输入的姓名,查找并显示联系人的详细信息。ContactModify
): 允许用户根据姓名查找联系人并修改其详细信息。ContactShow
): 显示通讯录中所有联系人的详细信息。menu
): 提供一个简单的文本菜单,让用户选择不同的操作。main
) 运行程序。//Contact.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#define NAME_MAX 100
#define GENDER_MAX 10
#define TEL_MAX 12
#define ADDR_MAX 100
typedef struct PersonInfo
{
char name[NAME_MAX];
int age;
char gender[GENDER_MAX];
char tel[TEL_MAX];
char addr[ADDR_MAX];
}Info;
//前置声明
typedef struct SeqList Contact;
//菜单栏
void menu();
//初始化
void ContactInit(Contact* pcon);
//销毁
void ContactDestroy(Contact* pcon);
//增加
void ContactAdd(Contact* pcon);
//删除
void ContactDel(Contact* pcon);
//查找
void ContactFind(Contact* pcon);
int FindByName(Contact* pcon, char name[NAME_MAX]);
//修改
void ContactModify(Contact* pcon);
//展示
void ContactShow(Contact* pcon);
//SeqList.h
#pragma once
#include"Contact.h"
typedef struct PersonInfo SLDataType;
//动态顺序表
typedef struct SeqList
{
SLDataType* arr;//动态开辟的数组
size_t size;//有效元素个数
size_t capacity;//容量
}SL,Contact;
//初始化
void SLInit(SL* ps);
//销毁
void SLDestory(SL* ps);
//判断容量
void SLCheckCapacity(SL* ps);
//打印
void SLPrint(SL* ps);
//尾插
void SLPushBack(SL* ps, SLDataType x);
//尾删
void SLPopBack(SL* ps);
//头插
void SLPushFront(SL* ps, SLDataType x);
//头删
void SLPopFront(SL* ps);
//查找
int SLFind(SL* ps, SLDataType x);
//在pos位置插入x
void SLInsert(SL* ps, size_t pos, SLDataType x);
//删除pos位置的元素
void SLErase(SL* ps, size_t pos);
//Contact.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Contact.h"
#include"SeqList.h"
void menu()
{
printf("*****************通讯录***************\n");
printf("***** 1.添加联系人 2.删除联系人*****\n");
printf("***** 3.查找联系人 4.修改联系人*****\n");
printf("***** 5.查看联系人 0.退出 *****\n");
printf("**************************************\n");
}
//初始化
void ContactInit(Contact* pcon)
{
SLInit(pcon);
}
//销毁
void ContactDestroy(Contact* pcon)
{
SLDestory(pcon);
}
//增加
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.tel);
printf("请输入地址:\n");
scanf("%s", info.addr);
//将结构体变量尾插到顺序表中
SLPushBack(pcon, info);
}
//删除
void ContactDel(Contact* pcon)
{
printf("请输入要删除的姓名:\n");
char name[NAME_MAX] = { 0 };
scanf("%s", name);
//1.查找要删除的元素下标
int pos = FindByName(pcon, name);
if (pos == -1)
{
printf("要删除的联系人不存在\n");
return;
}
//2.删除元素
SLErase(pcon, pos);
printf("删除成功\n");
}
//查找
int FindByName(Contact*pcon, char name[NAME_MAX])
{
//遍历顺序表,查找姓名为name的联系人
int i = 0;
for (i = 0; i < pcon->size; i++)
{
if (strcmp(pcon->arr[i].name, name) == 0)
{
return i;
}
}
return -1;
}
void ContactFind(Contact* pcon)
{
//按照姓名查找
//1.输入要查找的姓名
char name[NAME_MAX] = { 0 };
printf("请输入要查找的姓名:\n");
scanf("%s", name);
//2.查找姓名为name的联系人
int pos = FindByName(pcon, name);
if (pos == -1)
{
printf("要查找的联系人不存在\n");
return;
}
//3.打印联系人信息
printf("%-10s\t%-4s\t%-5s\t%-12s\t%-20s\n", "姓名", "年龄", "性别", "电话", "地址");
printf(" %-10s\t%-4d\t%-5s\t%-12s\t%-20s\n",
pcon->arr[pos].name,
pcon->arr[pos].age,
pcon->arr[pos].gender,
pcon->arr[pos].tel,
pcon->arr[pos].addr);
}
//修改
void ContactModify(Contact* pcon)
{
char name[NAME_MAX] = { 0 };
printf("请输入要修改的姓名:\n");
scanf("%s", name);
//1.查找要修改的元素下标
int pos = FindByName(pcon, name);
if (pos == -1)
{
printf("要修改的联系人不存在\n");
return;
}
//2.修改元素
Info* pInfo = &pcon->arr[pos];
printf("请输入姓名:\n");
scanf("%s", pInfo->name);
printf("请输入年龄:\n");
scanf("%d", &pInfo->age);
printf("请输入性别:\n");
scanf("%s", pInfo->gender);
printf("请输入电话:\n");
scanf("%s", pInfo->tel);
printf("请输入地址:\n");
scanf("%s", pInfo->addr);
printf("修改成功\n");
}
//展示
void ContactShow(Contact* pcon)
{
//打印表头
printf("%-10s\t%-4s\t%-5s\t%-12s\t%-20s\n", "姓名", "年龄", "性别", "电话", "地址");
//遍历顺序表中的每个元素,打印每个元素的值
for (int i = 0; i < pcon->size; i++)
{
printf(" %-10s\t%-4d\t%-5s\t%-12s\t%-20s\n",
pcon->arr[i].name,
pcon->arr[i].age,
pcon->arr[i].gender,
pcon->arr[i].tel,
pcon->arr[i].addr);
}
}
//SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//初始化
void SLInit(SL* ps)
{
assert(ps);
ps->arr = (SLDataType*)malloc(sizeof(SLDataType)* 3);
if (ps->arr == NULL)
{
assert(0);
return;
}
ps->size = 0;
ps->capacity = 3;
}
//销毁
void SLDestory(SL* ps)
{
assert(ps);
free(ps->arr);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//检查容量
void SLCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity)
{
SLDataType* tmp = (SLDataType*)realloc(ps->arr, sizeof(SLDataType)*ps->capacity * 2);
if (tmp == NULL)
{
perror("realloc fail\n");
return;
}
ps->arr = tmp;
ps->capacity *= 2;
}
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
ps->arr[ps->size] = x;
ps->size++;
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
if (ps->size == 0)
{
return;
}
ps->size--;
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size - 1; i >= 0; i--)
{
ps->arr[i + 1] = ps->arr[i];
}
ps->arr[0] = x;
ps->size++;
}
//头删
void SLPopFront(SL* ps)
{
assert(ps);
if (ps->size == 0)
{
return;
}
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
//插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size - 1; i >= pos; i--)
{
ps->arr[i + 1] = ps->arr[i];
}
ps->arr[pos] = x;
ps->size++;
}
//删除
void SLErase(SL* ps, size_t pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
if (ps->size == 0)
{
return;
}
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
//test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//#include"Contact.h"
int main()
{
Contact con;
ContactInit(&con);
int op = -1;
do {
menu();
printf("请选择:>");
scanf("%d", &op);
switch (op)
{
case 1:
printf("添加联系人\n");
ContactAdd(&con);
break;
case 2:
printf("删除联系人\n");
ContactDel(&con);
break;
case 3:
printf("查找联系人\n");
ContactFind(&con);
break;
case 4:
printf("修改联系人\n");
ContactModify(&con);
break;
case 5:
printf("查看通讯录\n");
ContactShow(&con);
break;
case 0:
printf("退出\n");
break;
default:
printf("选择错误\n");
}
} while (op);
ContactDestroy(&con);
return 0;
}
在本文中,我们全面探讨了C语言中顺序表的理论与应用,尤其是通过动态顺序表实现的通讯录项目,深入理解了其实际应用价值。这不仅加强了我们对数据结构基本概念的理解,还展示了如何将理论知识应用于实际问题解决中。顺序表的学习是编程之路上的重要一步,为我们未来探索更复杂的数据结构和算法奠定了坚实的基础。最后,希望本文对您有所帮助,无论您是一名初学者还是希望巩固基础知识的程序员。在编程的世界里,永远有新知识等待我们去探索和学习。