顺序表属于数据结构中的一种。我们之前学过的数组就是最简单的一种数据结构。那么我们为什么要学习其他的数据结构呢?因为在处理数据量较大,较复杂的实际情况里,只有数组一种数据结构显然是不够用的,只用数组处理复杂和大量的数据会使程序的效率大大降低,所以我们需要学习其他的数据结构。
1.顺序表的概念与结构
1.1线性表的概念
线性表是指具有相同性质的数据(比如整型、浮点型、结构体.....)组成的有限的序列。常见的顺序表有:顺序表、链表、栈、队列、字符串...
1.2顺序表的结构
顺序表的底层是数组,结构体作为载体,增加了增删查改功能的一些接口。同时,顺序表分为静态顺序表和动态顺序表。静态顺序表的容量是固定的,也就是说一旦定容之后就无法扩容,这对需要经常添加数据的工作非常不利。所以,我们还有另外一种动态顺序表,这种顺序表可以按照需要对数组进行扩容,以保证可以源源不断地插入数据同时保证空间不会过多地浪费。
我们先来定义一下顺序表的结构:
struct Seqlist
{
SLdatatype* arr;
int size;
int Capacity;
};
arr : 作为顺序表的底层。(也就是说顺序表的底层逻辑还是数组)
size : 记录顺序表中的有效数据个数。
Capacity :记录顺序表可以使用的空间大小。(空间容量)
以结构体作为载体存储这几个数据。
2.顺序表的实现
2.1准备工作
首先我们将所有代码分为三个文件:seqlist.h(顺序表的声明,头尾件的包含) , seqlist.c (实现顺序表各种功能的函数的代码), test.c(测试顺序表的功能)。
2.2seqlist.h头文件
头文件里应该含有要包含的头文件以及对顺序表的声明,对函数的声明可有可不有。
#pragma once
#include<stdio.h>
#include <stdlib.h>
#include<assert.h>
typedef int SLdatatype;
typedef struct Seqlist
{
SLdatatype* arr;
int size;
int Capacity;
}SL;
注意:(1)为了节约代码书写时间,我们将 struct Seqlist 重命名为SL。
(2)顺序表底层的数组可以存储多种类型的数据,如果要更换数据类型一个一个改会非常麻烦,所以先将 SLDataType 代替了 int,如果需要更换数据类型只要修改 int就行了。
2.3seqlist.c 和 test.c
注:有关顺序表的函数都是被存在seqlist.c中的。
(1)顺序表的初始化
//初始化函数
void SLCreate(SL* sl)
{
sl->arr = NULL;
sl->size = 0;
sl->Capacity = 0;
}
如图所示成功创建了一个顺序表。
(2)顺序表的销毁函数
//销毁函数
void SLDestroy(SL*sl)
{
if (sl->arr)
{
free(sl->arr);
}
sl->arr = NULL;
sl->Capacity = 0;
sl->size = 0;
}
因为顺序表的内存是用realloc函数申请的(后文会提到),所以如果在结束时指针不为空的话就要用free函数释放申请的空间。然后再将顺序表的三个数据恢复到初始化状态。
(3)顺序表打印函数
void PrintSL(SL sl)
{
for(int j=0;j<sl.size;j++)
{
printf("%d ", sl.arr[j]);
}
}
因为不需要修改顺序表内的任何数据所以用“.”直接引用即可。
(4)顺序表数据插入的准备工作——判断空间是否够用并自动扩容的函数
在插入数据之前,我们要先判断顺序表的空间是否够用,如果不够用就要向内存申请一块空间(使用我们之前学到的realloc函数进行扩容)。
void SLCheckCapacity2(SL* sl)
{
if (sl->size == sl->Capacity)
{
//三目操作符目的:当空间为0时时申请空间,当有空间但是不够用的时候自动以2倍扩容空间大小
int NewCapacity = sl->Capacity == 0 ? 4 : 2 * sl->Capacity;
SL* tmp = (SL*)realloc(sl->arr,NewCapacity*sizeof(SL));
sl->arr = tmp;
sl->Capacity = NewCapacity;
}
}
(5)尾插函数
void SLPushBehind(SL* sl, SLdatatype x)
{
SLCheckCapacity(sl);
sl->arr[sl->size] = x;
sl->size++;
}
尾插函数先保证空间足够可用后,然后在数组已经存储数据的后一位存储新数据,记录有效数据的size++就可以了。
我们可以在test.c里检验一下:
(6)头插函数
void SLPushFront(SL*sl, SLdatatype x)
{
//检查空间是否够用
SLCheckCapacity(sl);
//所有元素向后移动一个单位
for (int i = sl->size; i >0; i--)
{
sl->arr[i] = sl->arr[i-1];
}
sl->size++;
sl->arr[0] = x;
}
和尾插不同的是,当从头部插入一个数据,数组中已经存储的数据需要都向后移动一位。为防止数据之间相互覆盖,所以采用了从最末尾的数据开始移动直至最前面的数据向后移动一位给新插入的数据让位。
我们继续用上一段代码检验,如果此时向1 2 3 4中头插一个5,那么显示出来的就是 5 1 2 3 4.
(7)尾删函数
void SLPopBehind(SL* sl)
{
assert(sl);
assert(sl->size);
sl->size--;
}
assert断言是因为防止传进函数的是空指针(也就是顺序表的数据为空),此时肯定不能再进行删除了,所以用assert阻止。
(8)头删函数
void SLPopFront(SL*sl)
{
assert(sl);
assert(sl->size);
//向前覆盖
for (int i=1;i<=sl->size;i++)
{
sl->arr[i-1] = sl->arr[i];
}
sl->size--;
}
头删函数也需要进行断言。同时,删除第一个数据后后面的数据要向前依次覆盖,从第二个数据开始直到最后一个数据也向前一个单位了。
下面我们对头删,尾删函数集中测试一下:
(9)查找函数
void SLFind(SL*sl,SLdatatype x)
{
for (int j=0;j<sl->size;j++)
{
if (sl->arr[j] == x)
{
printf("找到了!\n");
return;
}
}
printf("没有找到\n");
}
查找函数没有什么技术含量,本质就是遍历整个数组。
对这个函数进行测试:
(10)在指定位置前插入数据函数
void SLAppointPushFront(SL* sl,int pos, SLdatatype x)
{
//检查空间够不够用
SLCheckCapacity(sl);
if (pos == 1)
{
SLPushFront(sl,x);
}
else
{
//指定位置之后的所有元素都要向后移动
for (int i = sl->size+1; i >= pos; i--)
{
sl->arr[i] = sl->arr[i-1];
}
sl->arr[pos - 1] = x;
sl->size++;
}
}
(11)在指定位置删除数据函数
void SLAppointDelete(SL* sl, int pos)
{
assert(sl->size);
//直接覆盖(指定位置之后的元素全向前移动一个单位)
for (int i=pos;i<=sl->size;i++)
{
sl->arr[i-1] = sl->arr[i];
}
sl->size--;
}
下面对在指定位置前插入数据函数和在指定位置删除数据函数进行测试:
3.seqlist.c完整代码
#include"seqlist.h"
SL sl;
//初始化函数
void SLCreate(SL* sl)
{
sl->arr = NULL;
sl->size = 0;
sl->Capacity = 0;
}
//销毁函数
void SLDestroy(SL*sl)
{
if (sl->arr)
{
free(sl->arr);
}
sl->arr = NULL;
sl->Capacity = 0;
sl->size = 0;
}
//判断空间是否够用函数
void SLCheckCapacity(SL*sl)
{
if (sl->size == sl->Capacity)
{
int NewCapacity = sl->Capacity == 0 ? 4 : 2 * sl->Capacity;
SLdatatype * tmp = (SLdatatype * )realloc(sl->arr,NewCapacity*sizeof(SLdatatype));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
sl->arr = tmp;
sl->Capacity = NewCapacity;
}
}
//打印函数
void PrintSL(SL sl)
{
for(int j=0;j<sl.size;j++)
{
printf("%d ", sl.arr[j]);
}
}
//尾插函数
void SLPushBehind(SL* sl, SLdatatype x)
{
SLCheckCapacity(sl);
sl->arr[sl->size] = x;
sl->size++;
}
//头插函数
void SLPushFront(SL*sl, SLdatatype x)
{
//检查空间是否够用
SLCheckCapacity(sl);
//所有元素向后移动一个单位
for (int i = sl->size; i >0; i--)
{
sl->arr[i] = sl->arr[i-1];
}
sl->size++;
sl->arr[0] = x;
}
//头删函数
void SLPopFront(SL*sl)
{
assert(sl);
assert(sl->size);
//向前覆盖
for (int i=1;i<=sl->size;i++)
{
sl->arr[i-1] = sl->arr[i];
}
sl->size--;
}
//尾删函数
void SLPopBehind(SL* sl)
{
assert(sl);
assert(sl->size);
sl->size--;
}
//查找函数
void SLFind(SL*sl,SLdatatype x)
{
for (int j=0;j<sl->size;j++)
{
if (sl->arr[j] == x)
{
printf("找到了!\n");
return;
}
}
printf("没有找到\n");
}
//在指定位置之前插入
void SLAppointPushFront(SL* sl,int pos, SLdatatype x)
{
//检查空间够不够用
SLCheckCapacity(sl);
if (pos == 1)
{
SLPushFront(sl,x);
}
else
{
//指定位置之后的所有元素都要向后移动
for (int i = sl->size+1; i >= pos; i--)
{
sl->arr[i] = sl->arr[i-1];
}
sl->arr[pos - 1] = x;
sl->size++;
}
}
//在指定位置删除
void SLAppointDelete(SL* sl, int pos)
{
assert(sl->size);
//直接覆盖(指定位置之后的元素全向前移动一个单位)
for (int i=pos;i<=sl->size;i++)
{
sl->arr[i-1] = sl->arr[i];
}
sl->size--;
}
感谢观看,若有错误请批评指出