呀哈喽,我是结衣。
提到数据结构,最最基础的当然是数据表了,今天我来教大家如何实现C语言中的数据表。
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使
⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的,
线性表在物理上存储时,通常以数组和链式结构的形式存储。
案例:蔬菜分为绿叶类、⽠类、菌菇类。线性表指的是具有部分相同特性的⼀类数据结构的集合
今天我们要实现的就是动态的顺序表。
在实现顺序表前,我们要先创建3个文件,头文件Sealist.h用来放函数的声明,源文件Seqlist.c用来实现函数,源文件test.c用来放主函数,也用来调用函数。
要实现顺序表我们首先要创建一个经结构体,在这个结构中,我们要存放,数据,有效数据的个数,以及有效空间的大小
那么我们就可以这样写
struct SeqList
{
int* a;
int size;
int capacity;
};
如果用这个来存放数据,是不是只能存放整形的数据,如果我们要存放其他类型的数据不就要重写了吗?为了避免这个情况,我们就要用到typedef
typedef int SLDataType;
// 动态顺序表
typedef struct SeqList
{
SLDataType* a;
int size;
int capacity;
}SL;
这样的话,我们如果再下次要存放其他类型的数据只需要,把typedef int SLDataType;中的int改为相应的数据就可以了。
我们应该怎么实现顺序表,实现顺序表需要哪几个函数呢?
让我们来想想,顺序表就是要插入数据,删除数据,那我们是不是就要写实现插入和删除的数据的函数呢,还是我们是不是应该给顺序表来初始化呢,然后再顺序表结束后是不是应该销毁顺序表呢?想到这些后我们就可以来写函数的声明了。
首先就是初始化然后把这些都敲上去,名字的话建议和我写一样的,因为以后的c++也是这么命名的。
//初始化
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 SLInit(SL* ps)
{
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
我这里把它初始化为空,你们也可以不初始化为空。
写完初始化我们来写销毁
//销毁
void SLDestroy(SL* ps)
{
if (ps->a != NULL)
{
free(ps->a);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
}
在里要注意的就是如果不为NULL才销毁,还有就是ps->a free后要记得赋值为NULL。
接下来我们就来首先尾插,顾名思义就是从后面插入数值。
我们先假设空间足够的情况下,我们要把x插入到最后面,我们应该怎么做呢?
首先我们肯定要想到size,它是有效数据的个数,图中这个情况就size等于4,4的化不就是数组元素4后面的下标位置吗?为此我们就要利用这个特性,直接把ps->a[size] = x;这样就顺利的将x插入到顺序表最后的位置了。
考虑的空间足够的情况,我们还要来考虑空间不足的情况下,当size == capecity的时候就说明空间不足了,那么我们就要来扩容了。
扩容就要用到realloc这个函数了。
了解完了我们就来敲代码了。
//尾插
void SLpushBack(SL* ps,SLDataType x)
{
assert(ps);
//空间不足,扩容
if (ps->size == ps->capacity)
{
ps->a = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);
}
ps->a[ps->size++] = x;
ps->capacity = ps->capacity * 2;
}
如果我们这样写是不是还会有一些错误呢?答案肯定是有的,比如因为我们的在初始化的时候给capacity赋值为0.那么在第一次尾插的时候就出现了空间不足,在扩容的时候,capacity*2还是为0,问题出现了,我们就要来修复问题。
为了修复这个问题我们就要再加一个判断条件,我们可以使用一个三目操作符“?:”,来解决问题
下面看代码
//尾插
void SLpushBack(SL* ps,SLDataType x)
{
assert(ps);
//空间不足,扩容
if (ps->size == ps->capacity)
{
int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, NewCapacity * sizeof(SLDataType));
//防止扩容失败
if (tmp == NULL)
{
perror("realloc fail!\n");
return 1;
}
ps->a= tmp;
ps->capacity = NewCapacity;
}
ps->a[ps->size++] = x;
}
写到这尾插就结束了,我们来试试效果吧。
为此我们要写一个打印函数。
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
没问题,那么下面就是头插。
我们要把x插到最前面,那么我们就要哪后面的数据都往后移上一位。怎么移就是我们该考虑的问题了。
要把数据往后移,是从前往后移呢还是从后往前移呢,如果我们从前往后移,那么后面的数据就会被覆盖,所以我们一个从前往后移。
把4往后移一位,再把3移到4原来的位置。最后再把ps->a[0] = x;想到了这些,我们就可以用代码来实现了。
首先还是经典的判断空间是否足够,和尾插时的判断一模一样。
void SLpushFront(SL* ps, SLDataType x)
{
assert(ps);
if (ps->size == ps->capacity)
{
int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, NewCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail!\n");
return 1;
}
ps->a = tmp;
ps->capacity = NewCapacity;
}
for (int i = ps->size; i > 0; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
既然判断的条件一模一样,那样的话我们就可以用一个函数来实现判断的条件,这样代码就精简化了。
void SLCheckCapcity(SL* ps)
{
if (ps->size == ps->capacity)
{
int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, NewCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail!\n");
return 1;
}
ps->a = tmp;
ps->capacity = NewCapacity;
}
}
用这个函数来实现空间是否足够
那么我们的头尾插就变得很简洁了。
//尾插
void SLpushBack(SL* ps,SLDataType x)
{
assert(ps);
//空间不足,扩容
SLCheckCapcity(ps);
ps->a[ps->size++] = x;
}
//头插
void SLpushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapcity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
下面就是尾删了,尾删就比较简单了。
我们只需要吧ps->size–;就可以了,要解释原因的话,我们把ps->size–后有效的数据就减少了,那么后续的打印也打印不到减少了的元素,如果我们后续还要插入元素的话,直接覆盖就可以了,如果有同学想到把它赋值为0的话也是没有必要的,如果我们要插入的元素就是0呢?还要同学会想到free,足够更是没有必要。
//尾删
void SLpopFront(SL* ps)
{
ps->size--;
}
也是没问题的。
最后我们来实现头删吧
如果我们要实现头部的删除我们就要,我第一个值给覆盖了,画一个图。
我们要把1给去掉,那么就要把后面的值覆盖前面的数,还是那个从前往后,和从后往前的问题。
在这里是哪个呢,答案是从前往后。为什么呢?如果我们从后往前的话,前面的值就被覆盖了,所以这里我们要用从前往后的思路来写代码。
//头删
void SLpopBack(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size-1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
我们来运行一下。
看起来是不是没有什么问题可是如果我删多了呢?
那样的话ps->size岂不是变成负数了吗,
为了避免这种情况我们还得再判断一下size是否为0,为0就不删了。
//头删
void SLpopFront(SL* ps)
{
assert(ps);
assert(ps->size);
for (int i = 0; i < ps->size-1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
这样的话就可以避免ps->size减成负数了。
前面的头删我就忘记加了。大家写代码的时候要记得补上哦。
除了这些函数,还有指定位置的插入和删除,这里我就不写了,其实也大差不差,大家可以自己来补充这些函数。
最后我把全部的代码都贴过来
Seqlist.h
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
typedef int SLDataType;
// 动态顺序表
typedef struct SeqList
{
SLDataType* a;
int size;
int capacity;
}SL;
//初始化
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 SLPrint(SL* ps);
//判断空间是否足够
void SLCheckCapcity(SL* ps);
Seqlist.c
#define _CRT_SECURE_NO_WARNINGS
#include "Seqlist.h"
//初始化
void SLInit(SL* ps)
{
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
//销毁
void SLDestroy(SL* ps)
{
if (ps->a != NULL)
{
free(ps->a);
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
}
void SLCheckCapcity(SL* ps)
{
if (ps->size == ps->capacity)
{
int NewCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, NewCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail!\n");
return 1;
}
ps->a = tmp;
ps->capacity = NewCapacity;
}
}
//尾插
void SLpushBack(SL* ps,SLDataType x)
{
assert(ps);
//空间不足,扩容
SLCheckCapcity(ps);
ps->a[ps->size++] = x;
}
//头插
void SLpushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapcity(ps);
for (int i = ps->size; i > 0; i--)
{
ps->a[i] = ps->a[i - 1];
}
ps->a[0] = x;
ps->size++;
}
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//尾删
void SLpopBack(SL* ps)
{
assert(ps);
ps->size--;
}
//头删
void SLpopFront(SL* ps)
{
assert(ps);
assert(ps->size);
for (int i = 0; i < ps->size-1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
tset.c
#define _CRT_SECURE_NO_WARNINGS
#include "Seqlist.h"
void test1()
{
SL sl;
SLInit(&sl);
SLpushBack(&sl, 1);
SLpushBack(&sl, 2);
SLpushBack(&sl, 3);
SLpushBack(&sl, 4);
SLPrint(&sl);
SLpushFront(&sl, 4);
SLpushFront(&sl, 5);
SLPrint(&sl);
/*SLpopBack(&sl);
SLpopBack(&sl);
SLpopBack(&sl);*/
SLpopFront(&sl);
SLpopFront(&sl);
SLpopFront(&sl);
SLpopFront(&sl);
SLpopFront(&sl);
SLpopFront(&sl);
SLpopFront(&sl);
SLpopFront(&sl);
SLPrint(&sl);
SLDestroy(&sl);
}
int main()
{
test1();
return 0;
}
完