目录
一、顺序表
二、静态顺序表
三、动态顺序表
3.1、动态顺序表的实现
3.2、动态顺序表的实现
3.3.1、结构体创建
3.3.2、初始化
3.3.3、销毁数据
3.3.4、增容空间
3.3.5、尾插数据
3.3.6、头插数据
3.3.7、删除尾数据
3.3.8、打印数据
3.3.9、删除头数据
3.3.10、在指定位置之前插入数据
3.3.11、删除指定位置的数据
3.3.12、查找数据是否存在
四、全部代码
顺序表是数据结构的基础知识,通过学习次内容,可以让我们了解顺序表中数据中是如何进行存储,删除,插入数据的。
一、顺序表
顺序表在逻辑上与物理上是连续存放的,比如我们之前学习的数组,数组中包含许多数,每个数据在内存中存储都是连续的,在我们逻辑思维上存储也是连续的,当然,据我自己所了解,所以数据都需要是具有逻辑存储的,如果不是逻辑存储,那么根本就无法找到此数据。
以下是数组存储的数据:
顺序表与数组的区别:
顺序表的底层结构是数组,对于数组进行封装,实现了增删查改的接口。
使用数组很单一,无法进行修改,而使用顺序表进行存储数据,我们可以任意进行数据的修改。
二、静态顺序表
使用静态顺序表存储数据:
静态顺序表的大小被限制死了,无法进行扩容。
空间给少了不够用,空间给多了浪费。
三、动态顺序表
SLDataType* a;
为什么在这动态顺序表中,数据需要加上“*”,这是因为创建的是一个动态的顺序表,我们可以使用malloc,realloc进行开辟新空间来存储a,如果使用静态数据“a”的话,数据的存储位置就无法变动,当我们在增加数据时候,数据不够了,我们使用realloc开辟新空间,并且空间的其实位置进行改变,那么a就地址就会更改。
3.1、动态顺序表的实现
我们需要创建三个文件,node.h , node.c , test.c
node.h存储头文件与函数的参数
node.c实现函数
test.c为我们的主函数
代码小提示:
在编写代码的时候,我们需要勤测试,在完成一个函数体时,就需要测试,避免写完一大堆函数以后再测试,这样会无从下手。
3.2、动态顺序表的实现
在这部分,我只进行函数的讲解,想要查看全部源码的同学,可以拉到博客的最后进行查看
3.3.1、结构体创建
typedef int SLDatatype;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
SLDatatype* arr;
int size; // 有效数据个数
int capacity; // 空间容量
}SL;
我们把int类型的名字改变成了SLDatatype,这样可以方便进行数据类型大小的修改。
3.3.2、初始化
test.c
SL s; //新建结构体
SLInit(&s); //取地址传给函数进行初始化
//初始化
void SLInit(SL* ps) //进行传址调用,否则堆栈在运行完如何销毁的时候,内容也会消失
{
ps->arr = NULL; //把数据给置空
ps->size = ps->capacity = 0; //把数据大小与空间也置零
}
因为是顺序表,无需修改首元素的地址,所以我们不需要进行SL* lisk=NULL;把新创建的内容给置为空。我们只要在一个空间进行增删改查。
初始化时我们需要把结构体中的arr置为空,数据大小与空间大小置为0
3.3.3、销毁数据
//销毁
void SLDestroy(SL* ps)
{
if (ps->arr) //相当于ps->arr != NULL
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
当需要销毁数据时,需要把有数据的空间给释放掉,把释放掉空间的地址给置为NULL,避免这个地址变为野指针,影响其他软件的运行,然后我们把size数据大小与capacity 空间大小都置为0。
3.3.4、增容空间
//增容空间
void SLCheckCapacity(SL* ps)
{
//判断空间是否充足
if (ps->size == ps->capacity)
{
//增容//0*2 = 0
//若capacity为0,给个默认值,否则×2倍
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;
}
}
对于动态顺序表来说,每次增容的大小我们统一规定为:新空间大小 = 2 * 原空间大小。
当原空间大小为0时,我们可以增加一行进行判断,如果原空间等于0那么我们就随机给个默认值,我们这里默认值给的是4个整型。
在扩容完以后,我们将原空间的数据与空间大小都指向为新的地址。
3.3.5、尾插数据
//插入数据
void SLPushBack(SL* ps, SLDatatype x)
{
//粗暴的解决方法---断言
assert(ps);//等价于assert(ps != NULL)
温柔的解决方式
//if (ps == NULL)
//{
// return;
//}
SLCheckCapacity(ps);
ps->arr[ps->size++] = x; //每插入一个数据,有效数据大小size就会+1
}
assert(ps)断言是为了判断ps是一个空指针。
当ps->arr[0]=1;进行完这个操作,我们如果想继续插入2,那么我们就要让arr的大小增加1位,此时就变成了ps->arr[1]=2。因此得出代码:ps->arr[ps->size++] = x;
3.3.6、头插数据
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];
}
//下标为0的位置空出来
ps->arr[0] = x;
ps->size++;
}
这段代码里的注释应该是很清楚的表示了
我们需要把表中数据集体往后移动一位,让新数据放到首元素。
3.3.7、删除尾数据
//删除
void SLPopBack(SL* ps)
{
assert(ps); //判断ps不为空,如果为空,代码没必要运行
assert(ps->size); //判断有效数据大小不为空,如果为空,代码没必要运行
//ps->arr[ps->size - 1] = -1; //多余了
ps->size--; //直接将有效数据个数-1就达到我们尾删的效果
}
我们可以直接将有效数据个数-1就达到我们尾删的效果
3.3.8、打印数据
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
这个很好理解,不过多阐述了。
3.3.9、删除头数据
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
//数据整体向前挪动一位
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1]; //i = size-2
}
ps->size--;
}
将数据集体往前移动一位。
移动前:
移动后:
3.3.10、在指定位置之前插入数据
//在指定位置之前插入数据(空间足够才能直接插入数据)
void SLInsert(SL* ps, SLDatatype x, int pos)
{
assert(ps); //判断ps是否为空
assert(pos >= 0 && pos <= ps->size); //判断指定位置的数据是否在顺序表中有效
SLCheckCapacity(ps);
//pos及之后的数据整体向后移动一位
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1]; //pos+1 -> pos
}
ps->arr[pos] = x;
ps->size++;
}
插入前:
插入后:
3.3.11、删除指定位置的数据
//删除指定位置的数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
//还有更多的限制:如顺序表不能为空....
//pos之后的数据整体向前挪动一位
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];// size-2 <- size-1
}
ps->size--;
}
删除前:
删除后:
3.3.12、查找数据是否存在
//查找数据
int SLFind(SL* ps, SLDatatype x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x) //如果arr指向的数据等于x那么就return i
{
return i;
}
}
//没有找到:返回一个无效的下标
return -1;
}
test.c
int find = SLFind(&s, 222);
if (find < 0)
{
printf("没找到\n");
}
else
{
printf("找到了\n");
}
如果return返回的数字小于零,打印没找到,否则就打印找到了
四、全部代码
node.h
typedef int SLDatatype;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
SLDatatype* arr;
int size; // 有效数据个数
int capacity; // 空间容量
}SL;
//初始化
void SLInit(SL* ps);
//销毁数据
void SLDestroy(SL* ps);
//打印数据
void SLPrint(SL* ps);
//扩容
void SLCheckCapacity(SL* ps);
//尾插
void SLPushBack(SL* ps, SLDatatype x);
//头插
void SLPushFront(SL* ps, SLDatatype x);
//尾删
void SLPopBack(SL* ps);
//头删
void SLPopFront(SL* ps);
//查找指定数据
int SLFind(SL* ps, SLDatatype x);
//指定位置之前插⼊
void SLInsert(SL* ps, int pos, SLDatatype x);
//指定位置之前删除数据
void SLErase(SL* ps, int pos);
node.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "node.h"
//初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//销毁
void SLDestroy(SL* ps)
{
if (ps->arr)//相当于ps->arr != NULL
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void SLCheckCapacity(SL* ps)
{
//判断空间是否充足
if (ps->size == ps->capacity)
{
//增容//0*2 = 0
//若capacity为0,给个默认值,否则×2倍
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;
}
}
//插入数据
void SLPushBack(SL* ps, SLDatatype x)
{
//粗暴的解决方法---断言
assert(ps);//等价于assert(ps != NULL)
温柔的解决方式
//if (ps == NULL)
//{
// return;
//}
SLCheckCapacity(ps);
ps->arr[ps->size++] = x;
}
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];
}
//下标为0的位置空出来
ps->arr[0] = x;
ps->size++;
}
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
//删除
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
//ps->arr[ps->size - 1] = -1;//多余了
ps->size--;
}
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
//数据整体向前挪动一位
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];//i = size-2
}
ps->size--;
}
//在指定位置之前插入数据(空间足够才能直接插入数据)
void SLInsert(SL* ps, SLDatatype x, int pos)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
//pos及之后的数据整体向后移动一位
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1]; //pos+1 -> pos
}
ps->arr[pos] = x;
ps->size++;
}
//删除指定位置的数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
//还有更多的限制:如顺序表不能为空....
//pos之后的数据整体向前挪动一位
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];// size-2 <- size-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;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "node.h"
void SLtest01()
{
SL s;
SLInit(&s);
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
//SLPushBack(&s, 5);
//SLPushBack(&s, 6);
//SLPushBack(NULL , 6);
//
//SLPushFront(&s, 1);
//SLPushFront(&s, 2);
//SLPushFront(&s, 3);
//SLPushFront(&s, 4);
SLPrint(&s); //1 2 3 4
//SLPopBack(&s);
//SLPrint(&s);
//SLPopBack(&s);
//SLPrint(&s);
//SLPopBack(&s);
//SLPrint(&s);
//SLPopBack(&s);
//SLPrint(&s);
//SLPopBack(&s);
//SLPrint(&s);
//
//SLPopFront(&s);
//SLPrint(&s);
//SLPopFront(&s);
//SLPrint(&s);
//SLPopFront(&s);
//SLPrint(&s);
//SLPopFront(&s);
//SLPrint(&s);
//SLPopFront(&s);
//SLPrint(&s);
//SLInsert(&s, 11, 0);
//SLPrint(&s);
//SLInsert(&s, 22, s.size);
//SLPrint(&s);
//SLInsert(&s, 33, 1);
//SLPrint(&s);
//SLErase(&s, 1);
//SLPrint(&s); //1 3 4
//SLErase(&s,s.size-1);
//SLPrint(&s);//2 3
int find = SLFind(&s, 222);
if (find < 0)
{
printf("没找到\n");
}
else
{
printf("找到了\n");
}
SLDestroy(&s);
//SLDestroy(&s);//ctrl+d
}
int main()
{
SLtest01();
return 0;
}