自学内容网 自学内容网

数据结构(2)——线性表与顺序表实现

目录

前言

一、线性表

二、顺序表

2.1概念

2.2类型的选择

2.3实现

1.初始化

2.检查是否需要扩容

3.尾插

4.尾删

5.头插

6.头删

7.某一个位置添加

8.某一个位置删除

9.基于某一位置的尾插删

10.查找

11.修改

12.销毁

总结


前言

今天对顺序表进行学习,可以把它了解成一个连续的表,类似数组。


一、线性表

线性表(linear list)是N个具有相同特性的数据元素的有限数列。线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表、链表、队列、字符串等等。

线性表在逻辑上是线性结构,也就是说连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储的时候,通常以数组和链式的形式存储。数组就是一段连续的地址,链表就是一个指向一个,一个个连在一起像一个链子一样。

线性表是一种数据结构,它由一系列按照顺序排列的元素组成。线性表的特点是元素具有相同的数据类型,元素之间有一个特定的顺序关系。线性表可以简单理解为一维数组,它可以有零个或多个元素。线性表的插入和删除操作比较方便,可以在任意位置插入或删除元素。线性表的查找操作比较耗时,需要遍历整个线性表来查找目标元素。

常见的线性表有顺序表和链表两种。

二、顺序表

2.1概念

顺序表使用的是一段连续的物理地址的存储单元一次存储数据元素的线性结构,一般情况下采用的是数组存储,在数组上完成数据的增删改查,因为基本的就是掌握增删改查就可以。

2.2类型的选择

顺序表可以分为:

1、静态顺序表:使用定长数组存储元素

2、动态顺序表:使用动态开辟实现变长数组存储元素

静态顺序表:

typedef int SLDataType;
typedef struct SeqList
{
SLDataType data[20];
int size;
}SL;

这里使用的是一个定长数组实现的数据存储,但是有一个弊端,当我们使用的时候难道就放这么多吗?如果我们就往里放一个数据,那那些剩余的就出现了内存的浪费,如果我们多于20,那么又会出现越界,空间不够。也许这时候,你会这么规定,使用define来在开头定义一个变量,后续改动这里就可以了:

#define N 20
typedef int SLDataType;
typedef struct SeqList
{
SLDataType data[N];
int size;
}SL;

但是你又是怎么确定我一定要用我给的空间个,倘若用的多了,你还要改代码,多麻烦,所以这里就直接使用的是动态开辟空间,先给出一个少一点的空间,如果比原来的空间大了,我可以自动开辟出几个空间,方便下一回使用,这样才是最方便的。

#define InitNum 4
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* data;
int size;
int capacity;
}SL;

我们结构体选用一个方便后续动态开辟的,成员包括数据数组,当前有数据的个数,当前的容量。 

2.3实现

这里实现增删改查,分别实现的功能有初始化,头删,头插,尾删,尾插,修改,查元素。我们开始实现:

1.初始化

void SL_Init(SL* pc)
{
pc->data = (SLDataType*)malloc(sizeof(SLDataType) * InitNum);
if (pc->data == NULL)
{
perror("Data malloc::");
return;
}
pc->size = 0;
pc->capacity = InitNum;
}

这里通过动态开辟一个大小为InitNum(4)的数组,当前有效数据的数量变为0,容量大小为4。

2.检查是否需要扩容

当我们在插入数据的时候,需要判断是否容量已经满了,所以这里来封装一个函数用来检查当前是否已经满了,满了就增加容量,没有满就不加。

void SLCheckCapacity(SL* pc)
{
assert(pc);
if (pc->size == pc->capacity)
{
SLDataType* pf = (SLDataType)realloc(pc->data, sizeof(SLDataType) * pc->capacity*2);
if (pf == NULL)
{
perror("SL Calloc::");
return;
}
pc->data = pf;
}
pc->capacity *= 2;
}

这里进行检查顺序表pc,如果有效数据的个数等于了容量,那就证明了满了,就可以动态开辟内存了。这里每一次开辟二倍。

3.尾插

尾插就是在后面插入数据

void SLPushBack(SL* pc, SLDataType x)
{
assert(pc);
//需要检查是否需要扩容
SLCheckCapacity(pc);
//pc->data[pc->size] = x;
//pc->size++;
pc->data[pc->size++] = x;
}

这里注释的两行代码是和最后一句一样的,用哪一个都可以。

我们可以通过打印来看一看:

我们发现是可以实现的。

4.尾删

尾删这里可以不用真正意义的删去,我们只需要在它显示的时候少去最后的一个就可以:

void SLPopBack(SL* pc, int x)
{
assert(pc->size > 0);
pc->size--;
}

因为首先要看空间里面还有没有数据,没有数据那还少什么,所以首先断言一下。然后有效数据减一就可以了。

我们运行一下,这里尾插四个数,尾删两次,最后只会输出前两个。

5.头插

头插涉及到一个问题,就是你每插入一个数据,后面的数据就会移动一趟,这样才能有地方存放要头插的数据。

void SLPushHead(SL* pc, SLDataType x)
{
assert(pc);
int end = pc->size - 1;//最后一个有效数据下标
SLCheckCapacity(pc);//检查扩容
while (end >= 0)
{
pc->data[end + 1] = pc->data[end];
end--;
}
pc->data[0] = x;
pc->size++;
}

经过一个从后往前的遍历就可以实现。

因为是从前面加的,所以第首先加的数会被遍历到后面。

6.头删

头删就是头插的反向思维,就是后一个数据把前面的数据覆盖,有效数据个数减一。

void SLPopHead(SL* pc)
{
assert(pc);
assert(pc->size > 0);
int begin = 0;
while (begin <= pc->size - 1)
{
pc->data[begin + 1] = pc->data[begin];
begin++;
}
pc->size--;
}

也是通过一个循环就可以搞定。

7.某一个位置添加

也是在某一个地方添加,那么在这个新加入数据的后面的所有元素都要往后移动一个。

void SLInsert(SL* pc, int pos,SLDataType x)
{
assert(pc);
assert(pos >= 0 && pos <= pc->size);
int end = pc->size - 1;
while (end >= pos)
{
SLCheckCapacity(pc);
pc->data[end + 1] = pc->data[end];
--end;
}
pc->data[pos] = x;
pc->size++;
}

这里是在某一个地方进行添加,记住这里添加完新元素的位置是当前位置的后一个元素。

这里就是头插之后再第二个元素的后面插入1,第三个元素的后面插入2,第四个元素的后面插入3。

8.某一个位置删除

void SLEarse(SL* pc, int pos)
{
assert(pc);
assert(pc->size > 0);
assert(pos >= 0 && pos < pc->size+1);
int begin = pos + 1;
while (begin <= pc->size)
{
pc->data[begin - 1] = pc->data[begin];
++begin;
}
pc->size--;
}

这里就是和之前添加相反,但是如果像我一样写的话,这里的pos判断范围就发生了变化,注意一下

这里运行一下,win了。

9.基于某一位置的尾插删

那么之前的尾插尾删就可以这么写:

void SLPushBack(SL* pc, SLDataType x)
{
assert(pc);
//需要检查是否需要扩容
SLCheckCapacity(pc);
SLInsert(pc, pc->size,x);
}

void SLPopBack(SL* pc)
{
assert(pc->size > 0);
SLEarse(pc, pc->size - 1);
}

这样也可以实现,也是同样的道理。

10.查找

查找一个遍历就解决了

void SLFind(SL* pc, SLDataType x)
{
assert(pc);
for (int i = 0; i < pc->size; i++)
{
if (pc->data[i] == x)
{
printf("%d",i);
return;
}
}
printf("-1");
}

如果找到了就打印下标,没有找到就打印-1.

11.修改

void SLChange(SL* pc, int pos, SLDataType x)
{
assert(pc);
assert(pos > 0 && pos < pc->size + 1);
pc->data[pos-1] = x;
}

这里就是直接通过下标访问一下就可以

12.销毁

销毁也就是释放空间,把申请的都释放掉,还给操作系统。其它的数据都置为0.

void SLDestory(SL* pc)
{
assert(pc);
free(pc->data);
pc->data = NULL;
pc->capacity = pc->size = 0;
}

这样基本的就大差不差都完事了。


总结

今天主要对线性表进行了了解,以及顺序表的实现和基本操作进行了编写。


原文地址:https://blog.csdn.net/heart_z/article/details/145432327

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!