前言:
线性表的介绍:
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表:
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
1. 静态顺序表:使用定长数组存储元素。
2.动态顺序表:使用动态开辟的数组存储。
介绍顺序表的创建与销毁:
第一步:
创建顺序表,初始化顺序表。
第二步:
进行顺序表的头插或尾插(如果空间不够,先开辟空间,后进行插入)
第三步:
可以实现删除,查找等功能,具体实现在下文有详细说明,最后释放掉开辟的空间,防止内存泄漏!
注:该篇重点介绍静态顺序表!
为了方便展示更加详细的顺序表的建立使用过程,我将建立三个文件
分别是:
test.c文件(测试文件)
seqlist.c文件(函数的定义)
seqlist.h文件(函数的声明,结构体的定义)
静态顺序表的定义:
定义结构体:
在test.h头文件中定义结构体:
#include<stdio.h>
#include<stdlib.h>
#define SLDataType int
typedef struct Seqlist//typedef类型重定义
{
SLDataType* a;
int size;//记录元素的个数
int capacity;//记录开辟空间的总个数
}SL;
这里由于不知道顺序表中元素的类型,所以定义一个常量,如果需要更改,可以直接改SLDtatType!!
初始化顺序表:
由于结构体前面只是做了一个定义,并没有进行初始化,现在进行结构体的初始化:
在test.c中定义结构体变量:
#include"seqlist.h"
int main()
{
SL s1;//定义结构体变量
//相当于struct Seqlist s1
return 0;
}
seqlist.c中初始化结构体变量:
目的:
1、将指针指向NULL
2、将顺序表的容量置为0
3、将顺序表的指向个数置为0
注意事项:
1、在传参的时候必须传入变量的地址,用指针接收,如果只是传入变量本身,形参只是实参的临时拷贝!!
#include"seqlist.h"
void SLInit(SL* s1)
{
s1->a = NULL;
s1->capacity = 0;
s1->size = 0;
}
顺序表的尾插:
首先检查空间够不够,不够用要开辟空间,接着进行尾插操作。
void SLPushBack(SL* s1, SLDataType x)
{
if (s1->size == s1->capacity)
{
int newcapacity = s1->size > 0 ? s1->size * 2:4;
SLDataType* tmp = malloc(newcapacity*sizeof(SLDataType));
if (tmp == NULL)
{
perror("SLPushBack :: malloc");
exit(-1);
}
s1->a = tmp;
s1->capacity = newcapacity;
}
s1->a[s1->size] = x;
s1->size++;
}
为了后续写其他函数时,也要进行空间的检查,所以这里空间的检查可以封装成一个函数,在需要的时候进行调用!!
void CheckCapacity(SL* s1) { if (s1->size == s1->capacity) { int newcapacity = s1->size > 0 ? s1->size * 2 : 4; SLDataType* tmp = malloc(newcapacity * sizeof(SLDataType)); if (tmp == NULL) { perror("SLPushBack :: malloc"); exit(-1); } s1->a = tmp; s1->capacity = newcapacity; } }
改造后变为:
void SLPushBack(SL* s1, SLDataType x)
{
CheckCapacity(s1);
s1->a[s1->size] = x;
s1->size++;
}
顺序表的头插:
如图解读:
假设此时对该顺序表进行头插。
第一步:将所有元素向后覆盖!
第二部:在第一个位置插入元素。
代码如下:
void SLPushFront(SL* s1, SLDataType x)
{
CheckCapacity(s1);
int num = s1->size;
while (num)
{
s1->a[num] = s1->a[num - 1];
num--;
}
s1->a[0] = x;
s1->size++;
}
顺序表的尾删:
需要注意的点:1、尾删的个数不能为0.
2、尾删传过来的指针不能为空。
3、尾删需要将个数减一。
代码如下:
void SLPopBack(SL* s1)
{
assert(s1);
assert(s1->size > 0);
s1->size--;
}
顺序表的头删:
需要注意的点:
1、头删的个数不能为0.
2、头删传过来的指针不能为空。
3、采用覆盖删除,而且必须是从前往后覆盖!
4、头删需要将个数减一。
代码如下:
void SLPopFront(SL* s1)
{
assert(s1);
assert(s1->size > 0);
int i = 0;
for (i = 0; i < s1->size-1; i++)
{
s1->a[i] = s1->a[i + 1];
}
s1->size--;
}
综合插入(想往哪放就往哪放):
前面介绍了头插和尾插,为了方便我们可以想往哪放就往哪放。
这时候就需要传入三个参数,分别是
1、顺序表地址
2、想要插入的位置
3、想要插入的数字
代码如下:
void Insert(SL* s1, int pos, SLDataType x)
{
CheckCapacity(s1);
assert(s1);
assert(pos-1>=0 && pos-1<=s1->size);
int num = s1->size ;
while (num >= pos-1)
{
s1->a[num+1] = s1->a[num];
num--;
}
s1->a[pos - 1] = x;
s1->size++;
}
综合删除(想删哪里就删哪里):
注意事项:
1、删除时不能传空指针。2、删除的位置必须在size的范围内
3、删除方式为覆盖删除,覆盖的次数必须要清楚。
代码如下:
void Earse(SL* s1, int pos)
{
assert(s1);
assert(pos-1>= 0 && pos-1< s1->size);
int num = pos-1;
for (num = pos-1; num < s1->size; num++)
{
s1->a[num+1] = s1->a[num];
}
s1->size--;
}
空间的释放:
void SLDestroy(SL* s1)
{
assert(s1);
if (s1->a != NULL)
{
free(s1->a);
s1->a = NULL;
s1->capacity = 0;
s1->size = 0;
}
}