目录
一,线性表
什么是线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列; 线性表是一种在实际中广泛使 用的数据结构,常见的线性表有:顺序表、链表、栈、队列、字符串…
线性表的结构
线性表在逻辑上是线性结构,通俗点讲就是一条连续的直线;但是在物理结构上不一定是连续的,线性表在物理存储上,通常是以数组以及链式结构的形式存储的。
二,顺序表
1,什么是顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
通俗来说顺序表就是数组,只不过数组元素必须连续存储。
2,顺序表的分类
顺序表一般分为两类:静态顺序表和动态顺序表
静态顺序表:一般使用定长数组来存储元素
#define MAX 100 //数组的最大长度
typedef int SLDtataType; //重命名数据类型
typedef struct SeqList
{
SLDtataType data[MAX]; //使用定长数组来存储数据
size_t size; //有效数据的个数
}SL;
动态顺序表:使用动态开辟的数据来存储元素
typedef int SLDataType; //将数据类型重命名为SLDataType,数据可能不同
typedef struct SeqList
{
SLDataType* data; //对应数据类型的指针,用来指向动态开辟的空间
size_t size; //记录当前有效数据的个数
size_t capacity; //记录当前容量,不够就扩容
}SL;
两种顺序表的对比:相对于动态顺序表来说,静态顺序表最大的缺点就是空间不足的问题,空间的初始值的设定,小了不够,大了浪费,所以静态顺序表使用的场景是确定需要存储多少数据的场景,带入到我们的生活场景中,基本都是使用动态顺序表,根据需要的空间大小来分配内存,接下来小编带大家写一个动态顺序表吧。
三,实现动态顺序表
1,结构的定义
#define DEF_SIZE 5 //初始容量
#define CRE_SIZE 2 //一次扩容的倍数
typedef int SLDataType; //将数据类型重命名为SLDataType
typedef struct SeqList
{
SLDataType* data; //对应数据类型的指针,用来指向动态开辟的空间
size_t size; //记录当前有效数据的个数
size_t capacity; //记录当前容量
}SL;
如上图代码:我们把int定义成了SLDataType,此做法是为了方便我们以后要用此顺序表去管理其他不同类型的数据,这样只用修改一个地方即可。
相对于静态顺序表,我们的动态顺序表多了一个capacity参数,此参数是用来记录当前容量的,当它等于size时,我们就需要扩容,因为数据的个数以及当前容量都不可能小于0,因此我们把他们设置成size_t类型。
再说一下宏定义参数,我们把初始值设定成了5,将扩容倍数设定成了2,因此扩容每次就2倍扩容。
2,顺序表的初始化
在初始化中我们把size和capacity置成相应大小(注意:是相应大小哟)在为data数组开辟一块空间用来存储数据。
//初始化顺序表
void SeqListInit(SL* psl)
{
assert(psl); //断言:防止psl为空指针
psl->data = (SLDataType*)calloc(DEF_SIZE, sizeof(SLDataType));
if (psl == NULL)
{
perror("calloc fail"); //打印错误信息
return;
}
psl->size = 0;
psl->capacity = DEF_SIZE;
}
3,检查扩容
当我们结构体中的size和capacity相等我们就需要扩容,千万不要是大于才扩容;并且我们要注意不能直接用data指针来接收realloc扩容的函数返回值,扩容也不一定是100%成功的,如果失败可能会造成data指针找不到之前的管理空间,从而造成内存泄露。
//检查容量(增容)
void CheckCapacity(SL* psl)
{
assert(psl); //断言:防止psl为空
if (psl->size == psl->capacity) //当数据个数和容量相等时扩容
{
//将realloc的返回值交由一个临时变量保存,防止扩容失败丢失原来空间的地址
SLDataType* ptr = (SLDataType*)realloc(psl->data, psl->capacity * CRE_SIZE * sizeof(SLDataType));
if (ptr == NULL) //判空
{
perror("realloc fail");
return;
}
psl->data = ptr;
psl->capacity *= CRE_SIZE; //增加容量
}
}
4,在头部和尾部插入数据
在头部插入数据我们需要把原数据整体向后挪动一位,空出头部,再插入数据,并且要把size++;而尾部相对就简单很多了,直接插入就行了。
//在头部插入数据
void SeqListPushFront(SL* psl, SLDataType x)
{
assert(psl);
CheckCapacity(psl); //检查容量
int i = 0;
for (i = psl->size - 1; i >= 0; i--)
{
psl->data[i + 1] = psl->data[i]; //将数据整体向后移
}
psl->data[0] = x; //插入数据
psl->size++;
}
//在尾部插入数据
void SeqListPushBack(SL* psl, SLDataType x)
{
assert(psl);
CheckCapacity(psl);
psl->data[psl->size] = x;
psl->size++;
}
5,在指定位置插入数据
在此函数调用时,我们需要把pos以及之后的数据整体向后移动一位,再再pos处插入数据。
//在任意位置插入数据
void SeqListInsert(SL* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(pos <= psl->size); //尾插,pos可能会等于size
CheckCapacity(psl);
size_t end = psl->size;
while (end > pos) //把pos及以后的数据向后挪动一位
{
psl->data[end] = psl->data[end - 1];
--end;
}
psl->data[pos] = x;
++psl->size;
}
由于头插和尾插函数是可以通过上图函数实现的,本质是一样的,所以我们就可以对两个函数进行改造。
头插:
//在头部插入数据
void SeqListPushFront(SL* psl, SLDataType x)
{
assert(psl);
SeqListInsert(psl, 0, x); //相当于在0位置处插入数据
}
尾插:
//在尾部删除数据
void SeqListPopBack(SL* psl)
{
assert(psl);
SeqListErase(psl, psl->size - 1); //相当于在size-1处插入数据(数组下标从0开始)
}
6,在头部和尾部删除数据
在头部删除数据,我们只需要头部以后的数据整体往前移,size--就可以了,而尾部就很简单了,只需要把size--就可以了,下一次插入数据就会把它覆盖掉。
//在尾部删除数据
void SeqListPopBack(SL* psl)
{
assert(psl);
assert(psl->size);
psl->size--; //如果尾部有数据,直接让size--
}
//在头部删除数据
void SeqListPopFront(SL* psl)
{
assert(psl);
assert(psl->size);
size_t i = 0;
for (i = 0; i < psl->size - 1; i++)
{
psl->data[i] = psl->data[i + 1]; //让表中的数据依次往前移
}
psl->size--;
}
7,删除指定位置的数据
我们只需要把指定位置以后的数据往前移动一位,在让size--就可以了。
//删除指定位置的数据
void SeqListErase(SL* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);
size_t i = 0;
for (i = pos; i < psl->size - 1; i++)
{
psl->data[i] = psl->data[i + 1];
}
psl->size--;
}
这个地方和上面一样的我们可以调用以上函数来实现头删和尾删函数的简单化。
头删:
//在头部删除数据
void SeqListPopFront(SL* psl)
{
assert(psl);
SeqListErase(psl, 0); //相当于删除0下标处的数据
}
尾删:
//在尾部删除数据
void SeqListPopBack(SL* psl)
{
assert(psl);
SeqListErase(psl, psl->size - 1); //相当于删除size-1下标处的数据
}
8,查找数据
如果我们找到返回返回元素下标,如果找不到返回-1。
//查找数据
int SeqListFind(const SL* psl, SLDataType x)
{
assert(psl);
int i = 0;
for (i = 0; i < (int)psl->size; i++)
{
if (psl->data[i] == x)
return i; //返回下标
}
return -1;
9,修改指定位置的数据
//修改指定位置的数据
void SeqListModify(SL* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(pos < psl->size);
psl->data[pos] = x; //修改数据
}
10,打印顺序表中的数据
//打印顺序表中的数据
void SeqListPrint(const SL* psl)
{
assert(psl);
size_t i = 0;
for (i = 0; i < psl->size; i++)
{
printf("%d ", psl->data[i]);
}
printf("\n");
}
11,顺序表的销毁
销毁顺序表的时候,一定要记得将前面开辟的动态内存空间释放掉,防止内存泄漏,并且要把它置为空,防止野指针。
//销毁顺序表
void SeqListDestory(SL* psl)
{
assert(psl);
free(psl->data); //释放
psl->data = NULL; //置空
psl->size = 0;
psl->capacity = 0;
}
四,完整代码
1,SeqList.h
#pragma once//防止头文件重复
//头文件
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//结构体定义
#define DEF_SIZE 5 //初始容量
#define CRE_SIZE 2 //扩容的倍数
typedef int SLDataType; //数据类型重命名
typedef struct SeqList
{
SLDataType* data; //对应数据的指针,指向动态开辟的空间
size_t size; //记录当前数据的个数
size_t capacity; //记录当前容量,不够就扩容
}SL;//定义变量名
//函数的声明
//初始化顺序表
void SeqListInit(SL* psl);
//销毁顺序表
void SeqListDestory(SL* psl);
//检查增容
void CheckCapacity(SL* psl);
//尾部插入数据
void SeqListPushBack(SL* psl, SLDataType x);
//头部插入数据
void SeqListPushFront(SL* psl, SLDataType x);
//尾部删除数据
void SeqListPopBack(SL* psl);
//头部删除数据
void SeqListPopFront(SL* psl);
//在指定下标处插入数据
void SeqListInsert(SL* psl,size_t pos,SLDataType x);
//在指定下标处删除数据
void SeqListErase(SL* psl, size_t pos);
//查找数据
int SeqListFind(const SL* psl, SLDataType x);
//修改指定位置的数据
void SeqListModify(SL* psl, size_t pos, SLDataType x);
//打印顺序表中的数据
void SeqListPrint(const SL* psl);
2,SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
//初始化顺序表
void SeqListInit(SL* psl)
{
assert(psl);//断言:防止psl为空指针
//开辟默认的大小的空间并初始化
psl->data = (SLDataType*)calloc(DEF_SIZE, sizeof(SLDataType));
//防止扩容失败
if (psl == NULL)
{
perror("calloc fail");//打印错误信息
}
psl->size = 0;
psl->capacity = DEF_SIZE;
}
//销毁数据表
void SeqListDestory(SL* psl)
{
assert(psl);
free(psl->data);//释放:避免内存泄漏
psl->data = NULL;//置空:避免野指针
psl->size = 0;
psl->capacity = 0;
}
//检查增容
void CheckCapacity(SL* psl)
{
assert(psl);
if (psl->size == psl->capacity)
{
SLDataType* ret = (SLDataType*)realloc(psl->data, psl->capacity * CRE_SIZE * sizeof(SLDataType));
if (ret == NULL)
{
perror("realloc fail");
return;
}
psl->data = ret;
psl->capacity *= CRE_SIZE;//增容:按照2倍扩容
}
}
//在尾部插入数据
void SeqListPushBack(SL* psl, SLDataType x)
{
assert(psl);
SeqListInsert(psl, psl->size, x);//直接调用
}
//在头部插入数据
void SeqListPushFront(SL* psl, SLDataType x)
{
assert(psl);
SeqListInsert(psl, 0, x);
}
//打印顺序表中的内容
void SeqListPrint(const SL* psl)
{
assert(psl);
size_t i = 0;
for (i = 0; i < psl->size; i++)
{
printf("%d ", psl->data[i]);
}
printf("\n");
}
//在尾部删除数据
void SeqListPopBack(SL* psl)
{
assert(psl);
SeqListErase(psl, psl->size - 1);
}
//在头部删除数据
void SeqListPopFront(SL* psl)
{
assert(psl);
SeqListErase(psl, 0);
}
//查找数据
int SeqListFind(const SL* psl, SLDataType x)
{
assert(psl);
int i = 0;
for (i = 0; i < (int)psl->size; i++)
{
if (psl->data[i] == x)
{
return i;
}
}
return -1;
}
//修改指定位置的数据
void SeqListModify(SL* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(psl < psl->size);
psl->data[pos] = x;//修改数据
}
//在指定下标处插入数据
void SeqListInsert(SL* psl, size_t pos, SLDataType x)
{
assert(psl);//断言
assert(pos <= psl->size);//因为可能要在尾部插入,pos可以等于size
CheckCapacity(psl);//检查容量
size_t end = psl->size;
while (end > pos) //把pos以后的数据向后挪一位
{
psl->data[end] = psl->data[end - 1];
end--;
}
psl->data[pos] = x;//插入数据
++psl->size;
}
//在指定下标处删除数据
void SeqListErase(SL* psl, size_t pos)
{
assert(psl);
assert(psl < psl->size);
size_t i = 0;
for (i = pos; i < psl->size - 1; i++)
{
psl->data[i] = psl->data[i + 1];
}
psl->size--;
}
3,test_0222.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
void test1()
{
//初始化
SL sl;
SeqListInit(&sl);
//尾插
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 2);
SeqListPushBack(&sl, 3);
SeqListPushBack(&sl, 4);
SeqListPrint(&sl);
//头插
SeqListPushFront(&sl, 8);
SeqListPushFront(&sl, 9);
SeqListPushFront(&sl, 10);
SeqListPrint(&sl);
//在指定位置插入
SeqListInsert(&sl, 3, 6);
SeqListInsert(&sl, 0, 6);
SeqListInsert(&sl, 9, 6);
SeqListPrint(&sl);
//销毁
SeqListDestory(&sl);
}
void test2()
{
//初始化
SL sl;
SeqListInit(&sl);
//尾插
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 2);
SeqListPushBack(&sl, 3);
SeqListPushBack(&sl, 4);
SeqListPrint(&sl);
//尾删
//SeqListPopBack(&sl);
//SeqListPopBack(&sl);
//SeqListPopBack(&sl);
//SeqListPrint(&sl);
//头删
//SeqListPopFront(&sl);
//SeqListPopFront(&sl);
//SeqListPopFront(&sl);
//SeqListPrint(&sl);
//删除指定位置元素
SeqListErase(&sl, 3);
SeqListPrint(&sl);
SeqListErase(&sl, 1);
SeqListPrint(&sl);
SeqListErase(&sl, 0);
SeqListPrint(&sl);
SeqListDestory(&sl);
}
void test3()
{
//初始化
SL sl;
SeqListInit(&sl);
//尾插
SeqListPushBack(&sl, 1);
SeqListPushBack(&sl, 2);
SeqListPushBack(&sl, 3);
SeqListPushBack(&sl, 4);
SeqListPrint(&sl);
//查找并修改
int find = 0;
int modify = 0;
do
{
scanf("%d", &find);
scanf("%d", &modify);
int pos = SeqListFind(&sl, find);
if (pos != -1)
{
SeqListModify(&sl, pos, modify);
}
SeqListPrint(&sl);
} while (find != EOF);
SeqListDestory(&sl);
}
//测试函数
int main()
{
//test1(); //插入
//test2(); //删除
test3(); //查改
return 0;
}
文章有很多不足,如果大家想要更为完整的代码可以去小编的gitee上获取:zsn_C1: 学习之路 - Gitee.com
博主朽木,欢迎大家指正,如果对你有一点的帮助,还是希望给小编一个三连。谢谢大家!