大家好,我是苏貝,本篇博客带大家了解顺序表,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
目录
- 一.概念及结构
- 二.接口实现
- 2.1 创建顺序表结构体
- 2.2 初始化顺序表
- 2.3 销毁顺序表
- 2.4 打印顺序表
- 2.5 尾插
- 2.6 头插
- 2.7 尾删
- 2.8 头删
- 2.9 任意位置插入
- 2.10 任意位置删除
- 2.11 查找并返回下标
- 三.模块化代码实现
- 3.1 SeqList.h
- 3.2 SeqList.c
- 3.3 test.c
- 3.4 结果演示
一.概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
1.静态顺序表:使用定长数组存储元素。
2.动态顺序表:使用动态开辟的数组存储。
二.接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
首先在编译器中建立3个文件:SeqList.h文件,即一个头文件,用来声明;SeqList.c文件,用来实现顺序表的增删查改基本功能;test.c文件,用来测试代码
2.1 创建顺序表结构体
因为顺序表信息包括一个动态数组、数据个数和数组容量,后两者都是int型,数组的类型不确定,而且它们所占空间大小不同,所以我们想到创建一个顺序表结构体,为了书写方便,我们将struct SeqList重命名为SL
因为我们不知道多态数组的类型,所以将它的类型定为SLDataType,如果我们后来知道了动态数组的类型,比如是int型,只要将int重命名为SLDataType即可
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;//指向动态开辟的数组
int size;//有效数据个数
int capacity;//数组的容量
}SL;
2.2 初始化顺序表
因为初始化顺序表会改变顺序表,所以不能采用传值调用,只能传址,即将顺序表的地址传过来。因为顺序表结构体不能为NULL,所以对它断言,接口都需要使用顺序表结构体,所以每个接口都需要对它断言。
注意:size是最后一个有效数据的下一个索引,因为初始化的时候还没有有效数据,所以size置为0
void SLInit(SL* s)
{
assert(s);
s->a = NULL;
s->size = 0;
s->capacity = 0;
}
2.3 销毁顺序表
因为我们开辟的是动态数组,所以需要在程序结束前将数组所占空间释放,再让指向该数组的指针a置为NULL
void SLDestroy(SL* s)
{
assert(s);
if (s->a != NULL)
{
free(s->a);
s->size = 0;
s->capacity = 0;
s->a = NULL;
}
}
2.4 打印顺序表
打印顺序表不需要改变顺序表,为什么我们还是采用传址调用呢?因为相较于传值调用,两者都可以实现该接口,但是因为函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降 。简单来讲就是因为形参是实参的一份临时拷贝,若实参(即结构体)过大,那么形参也会较大,这样就会导致效率降低。但传结构体变量的地址的话,形参的大小只是4个或8个字节,效率较传结构体更高。所以我们还是采用传址调用
void SLPrint(SL* s)
{
assert(s);
for (int i = 0; i < s->size; i++)
{
printf("%d ", s->a[i]);
}
printf("\n");
}
2.5 尾插
尾插即在动态数组的最后一个有效数据后面插入一个数据。在插入数据之前,我们要判断是否需要增容,增容的条件是有效数据个数size>=数组容量capacity。增容时我们一般将新的容量置为原来容量的2倍,因为我们初始化的时候将初始容量置为0,所以先要对容量判断,如果=0就将新的容量置为4,如果不为0则将新的容量置为原来容量的2倍。因为要增加容量,所以数组的大小会改变,所以用realloc来动态开辟数组,返回值是调整后起始位置的地址。但realloc也可能开辟空间失败,此时它的返回值是NULL,所以我们先要对它的返回值判断,如果为NULL,返回错误信息,不为NULL,将返回值赋值给指向数组的指针a
因为我们准备写的插入接口包含尾插、头插和在任意位置插入,插入时都要判断是否需要增容,所以不妨将增容的代码封装为一个函数
void SLCheckCapacity(SL* s)
{
if (s->size >= s->capacity)
{
int newcapacity = (s->capacity == 0 ? 4 : s->capacity * 2);
SLDataType* tmp = (SLDataType*)realloc(s->a, sizeof(SLDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
s->a = tmp;
s->capacity = newcapacity;
}
}
插入成功后,将有效数据个数size++
void SLPushBack(SL* s, SLDataType x)
{
assert(s);
//判断是否需要增容
SLCheckCapacity(s);
s->a[s->size] = x;
s->size++;
}
2.6 头插
头插时我们要将所有数据都向后挪一位,再将要插入的数据放在数组索引为0的位置上,不要忘记将有效数据个数size++
void SLPushFront(SL* s, SLDataType x)
{
assert(s);
//判断是否需要增容
SLCheckCapacity(s);
for (int i = s->size-1; i >=0; i--)
{
s->a[i + 1] = s->a[i];
}
s->a[0] = x;
s->size++;
}
2.7 尾删
尾删即将最后一个有效数据删除,在删除前要保证有效数据的个数>=1,所以最后一个有效数据的索引>=0,所以要size>0,对它断言。尾删只需要将size- -
void SLPopBack(SL* s)
{
assert(s);
assert(s->size > 0);
s->size--;
}
2.8 头删
头删即将第一个有效数据删除,然后将所有的有效数据都向前挪一位。头删也需要对size断言,删除成功后记得将size- -
void SLPopFront(SL* s)
{
assert(s);
assert(s->size > 0);
for (int i = 1; i < s->size; i++)
{
s->a[i - 1] = s->a[i];
}
s->size--;
}
2.9 任意位置插入
这个接口需要的形参有指向顺序表结构体的指针,插入位置的下标以及插入的元素。pos要>=0并且要<=size,pos=0时即头插,pos=size时即尾插,对pos断言。最后size++
void SLInsert(SL* s, int pos, SLDataType x)
{
assert(s);
assert(pos >= 0 && pos <= s->size);
SLCheckCapacity(s);
for (int i = s->size-1; i >=pos; i--)
{
s->a[i + 1] = s->a[i];
}
s->a[pos] = x;
s->size++;
}
2.10 任意位置删除
要使用3个断言,将要删除位置下标之后的元素都向前挪一位,最后size- -
void SLErase(SL* s,int pos)
{
assert(s);
assert(s->size > 0);
assert(pos >= 0 && pos <= s->size);
for (int i = pos; i < s->size; i++)
{
s->a[i] = s->a[i + 1];
}
s->size--;
}
2.11 查找并返回下标
对动态数组遍历,如果有要查找的元素,返回其下标,否则返回-1
int SLFind(SL* s, SLDataType x)
{
assert(s);
for (int i = 0; i < s->size; i++)
{
if (s->a[i] == x)
return i;
}
return -1;
}
三.模块化代码实现
3.1 SeqList.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;
int size;
int capacity;
}SL;
//初始化顺序表
void SLInit(SL* s);
//打印
void SLPrint(SL* s);
//销毁
void SLDestroy(SL* s);
//头插
void SLPushFront(SL* s, SLDataType x);
//尾插
void SLPushBack(SL* s, SLDataType x);
//头删
void SLPopFront(SL* s);
//尾删
void SLPopBack(SL* s);
//任意位置插入
void SLInsert(SL* s, int pos, SLDataType x);
//任意位置删除
void SLErase(SL* s, int pos);
//查找并返回下标
int SLFind(SL* s, SLDataType x);
3.2 SeqList.c
#include"SeqList.h"
//初始化顺序表
void SLInit(SL* s)
{
assert(s);
s->a = NULL;
s->size = 0;
s->capacity = 0;
}
//打印
void SLPrint(SL* s)
{
assert(s);
for (int i = 0; i < s->size; i++)
{
printf("%d ", s->a[i]);
}
printf("\n");
}
//销毁
void SLDestroy(SL* s)
{
assert(s);
if (s->a != NULL)
{
free(s->a);
s->size = 0;
s->capacity = 0;
s->a = NULL;
}
}
//增容
void SLCheckCapacity(SL* s)
{
if (s->size >= s->capacity)
{
int newcapacity = (s->capacity == 0 ? 4 : s->capacity * 2);
SLDataType* tmp = (SLDataType*)realloc(s->a, sizeof(SLDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
s->a = tmp;
s->capacity = newcapacity;
}
}
//头插
void SLPushFront(SL* s, SLDataType x)
{
assert(s);
//判断是否需要增容
SLCheckCapacity(s);
for (int i = s->size-1; i >=0; i--)
{
s->a[i + 1] = s->a[i];
}
s->a[0] = x;
s->size++;
}
//尾插
void SLPushBack(SL* s, SLDataType x)
{
assert(s);
SLCheckCapacity(s);
s->a[s->size] = x;
s->size++;
}
//头删
void SLPopFront(SL* s)
{
assert(s);
assert(s->size > 0);
for (int i = 1; i < s->size; i++)
{
s->a[i - 1] = s->a[i];
}
s->size--;
}
//尾删
void SLPopBack(SL* s)
{
assert(s);
assert(s->size > 0);
s->size--;
}
//任意位置插入
void SLInsert(SL* s, int pos, SLDataType x)
{
assert(s);
assert(pos >= 0 && pos <= s->size);
SLCheckCapacity(s);
for (int i = s->size-1; i >=pos; i--)
{
s->a[i + 1] = s->a[i];
}
s->a[pos] = x;
s->size++;
}
//任意位置删除
void SLErase(SL* s,int pos)
{
assert(s);
assert(s->size > 0);
assert(pos >= 0 && pos <= s->size);
for (int i = pos; i < s->size; i++)
{
s->a[i] = s->a[i + 1];
}
s->size--;
}
//查找并返回下标
int SLFind(SL* s, SLDataType x)
{
assert(s);
for (int i = 0; i < s->size; i++)
{
if (s->a[i] == x)
return i;
}
return -1;
}
3.3 test.c
#include"SeqList.h"
void test1()
{
SL s1;
SLInit(&s1);
SLPushFront(&s1, 1);
SLPushFront(&s1, 2);
SLPushFront(&s1, 3);
SLPushFront(&s1, 4);
SLPrint(&s1);
SLPushBack(&s1, 2);
SLPushBack(&s1, 3);
SLPushBack(&s1, 4);
SLPrint(&s1);
SLPopFront(&s1);
SLPopFront(&s1);
SLPrint(&s1);
SLPopBack(&s1);
SLPopBack(&s1);
SLPrint(&s1);
SLDestroy(&s1);
}
void test2()
{
SL s1;
SLInit(&s1);
SLPushFront(&s1, 1);
SLPushFront(&s1, 2);
SLPushFront(&s1, 3);
SLPushFront(&s1, 4);
SLPrint(&s1);
SLInsert(&s1, 1, 10);
SLInsert(&s1, 0, 10);
SLPrint(&s1);
SLErase(&s1, 3);
SLErase(&s1, 2);
SLPrint(&s1);
SLDestroy(&s1);
}
void test3()
{
SL s1;
SLInit(&s1);
SLPushFront(&s1, 1);
SLPushFront(&s1, 2);
SLPushFront(&s1, 3);
SLPushFront(&s1, 4);
SLPrint(&s1);
int ret=SLFind(&s1, 3);
printf("%d\n", ret);
SLDestroy(&s1);
}
int main()
{
test3();
return 0;
}
3.4 结果演示
1.test1
2.test2
3.test3
好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️