【数据结构初阶】第二篇——顺序表(实现+动图演示)[建议收藏]

本篇博客我要给大家分享一下顺序表,这个存储数据元素的结构我在之前通讯录的文章也提到过,今天我来带大家再深入了解一下~
博主本片篇博客代码码云链接:https://gitee.com/byte-binxin/data-structure/tree/master/SeqList_2(代码已经上传至gitee)

目录

  • 顺序表的概念与结构
    • 静态顺序表
    • 动态顺序表
  • 顺序表的函数接口
  • 顺序表接口实现
    • 初始化顺序表
    • 打印顺序表
    • 销毁顺序表
    • 尾插
    • 尾删
    • 头插
    • 头删
    • 顺序表查找
    • 任意位置插入
    • 任意位置删除
  • 顺序表的问题及思考
  • 总结


顺序表的概念与结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
一般分为两种:

静态顺序表

我们先来看一下顺序表的结构

#define N 100
typedef int SLDataType;//以便可以存储不同类型的数据

typedef struct SeqList
{
     
	SLDataType a[N];
	int size;
}SL;

【数据结构初阶】第二篇——顺序表(实现+动图演示)[建议收藏]_第1张图片
顺序表最大的缺点就是存储空间大小被固定了,空间有限。所以实际上我们不怎么会用这种静态的,所以我们这里不做实现。只实现下面的动态顺序表。

动态顺序表

看一下动态顺序表的结构:

typedef int SLDataType;//以便可以存储不同类型的数据

typedef struct SeqList
{
     
	SLDataType* a;
	int size;
	int capacity;
}SL;

【数据结构初阶】第二篇——顺序表(实现+动图演示)[建议收藏]_第2张图片

顺序表的函数接口

//打印顺序表
void SeqListPrint(SL* ps);
//初始化顺序表
void SeqListInit(SL* ps);
//销毁顺序表
void SeqListDestory(SL* ps);
//尾插
void SeqListPushBack(SL* ps, SLDataType x);
//尾删
void SeqListPopBack(SL* ps);
//头插
void SeqListPushFront(SL* ps, SLDataType x);
//删除
void SeqListPopFront(SL* ps);
// 顺序表查找
int SeqListFind(SL* ps, SLDataType x);
//任意位置插入
void SeqListInsert(SL* ps, int pos, SLDataType x);
//任意位置删除
void SeqListErase(SL* ps, int pos);

顺序表接口实现

首先,上来我们实现三个简单和基础的接口,为后面做准备。

初始化顺序表

void SeqListInit(SL* ps)
{
     
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

首先我们要对顺序表进行初始化,为了后面的增删查改做准备。

打印顺序表

void SeqListPrint(SL* ps)
{
     
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
     
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

销毁顺序表

为了防止内存泄漏,我们需要手动释放空间。

void SeqListDestory(SL* ps)
{
     
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}

尾插

尾插当然就是在顺序表的尾部进行插入数据,插入数据的同时我们需要考虑到扩容,否则会导致空间不够,当capacity的大小和size的大小相同时,就说明顺序表容量已经满了,所以我们要对顺序表进行扩容操作,考虑到后面的头插也要可能扩容,所以封装一个函数CheckCapacity来检查顺序表容量,并且看是否需要扩容。实现如下:

//检查顺序表容量
void CheakCapacity(SL* ps)
{
     
	if (ps->capacity == ps->size)
	{
     
		ps->capacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = NULL;
		tmp = (SLDataType*)realloc(ps->a, ps->capacity*sizeof(SLDataType));
		if (tmp == NULL)
		{
     
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
	}
}

实现这个之后,我们就可以对顺序表进行尾插了,尾插唯一一个要注意的就是要检查顺序表容量,否则会导致程序崩溃。
为了方便大家理解,这里放上一个动图来演示一下:
【数据结构初阶】第二篇——顺序表(实现+动图演示)[建议收藏]_第3张图片

尾插实现如下:

void SeqListPushBack(SL* ps, SLDataType x)
{
     
	//检查是否要扩容
	CheakCapacity(ps);
	ps->a[ps->size] = x;
	ps->size++;
}

尾删

尾删要注意的一点:当顺序表中没有数据时,我们不应该再对顺序表进行删除了,为了保证程序不崩溃,我们加上这样一句话:

assert(ps->size > 0);

注意程序就不会崩溃了。尾删操作其实很简单,直接对size进行“–”操作。
看一下尾删的动图展示:
【数据结构初阶】第二篇——顺序表(实现+动图演示)[建议收藏]_第4张图片
尾删代码实现如下:

void SeqListPopBack(SL* ps)
{
     
	assert(ps->size > 0);
	ps->size--;
}

头插

头插值得我们考虑的点就是要对顺序表进行扩容,上面我们也提到了CheckCapacity这个函数,所以这里我们也要用到他,用他来检查顺序表容量。
先看一下头插的动图展示:

【数据结构初阶】第二篇——顺序表(实现+动图演示)[建议收藏]_第5张图片
头插代码实现如下:

void SeqListPushFront(SL* ps, SLDataType x)
{
     
	CheakCapacity(ps);
	int end = 0;
	for (end = ps->size - 1; end >= 0; end--)
	{
     
		ps->a[end + 1] = ps->a[end];
	}
	ps->a[0] = x;
	ps->size++;
}

头删

头删也要注意顺序表中是否还有数据,如果没有就不进行删除操作,否则会导致程序崩溃。
头删动图展示:
【数据结构初阶】第二篇——顺序表(实现+动图演示)[建议收藏]_第6张图片
代码实现:

void SeqListPopFront(SL* ps)
{
     
	assert(ps->size > 0);
	int start = 0;
	for (start = 0; start < ps->size - 1; start++)
	{
     
		ps->a[start] = ps->a[start + 1];
	}
	ps->size--;
}

顺序表查找

顺序表查找,返回类型是int,如果找到了返回顺序表的下标,没找到返回-1。
这个接口也很好实现,我就不多说什么了。
代码实现如下:

// 顺序表查找
int SeqListFind(SL* ps, SLDataType x)
{
     
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
     
		if (ps->a[i] == x)
		{
     
			return i;
		}
	}
	return -1;
}

任意位置插入

看到插入两个字,我们就要考虑是否需要扩容。这一点很重要。还有我们要多pos这个参数进行判断,看是否在顺序表指定的范围中,因为顺序表是连续的,我们任意位置插入要合理,所以要对参数进行合理性判断:

assert(pos >= 0 && pos <= ps->size); 

先给大家来个动图演示;
【数据结构初阶】第二篇——顺序表(实现+动图演示)[建议收藏]_第7张图片
代码实现如下:

//任意位置插入
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
     
	assert(pos >= 0 && pos <= ps->size); 
	CheakCapacity(ps);
	int end = 0;
	for (end = ps->size - 1; end >= pos; end--)
	{
     
		ps->a[end + 1] = ps->a[end];
	}
	ps->a[pos] = x;
	ps->size++;
	
}

任意位置删除

首先要对参数进行判断,顺序表不能为空,pos的位置要合理。

参数合理性判断:

assert(pos >= 0 && pos < ps->size);
assert(ps->size > 0);

老规矩,看动图演示:

【数据结构初阶】第二篇——顺序表(实现+动图演示)[建议收藏]_第8张图片

代码实现如下:

void SeqListErase(SL* ps, int pos)
{
     
	assert(pos >= 0 && pos < ps->size);
	assert(ps->size > 0);
	int start = 0;
	for (start = pos; start < ps->size - 1; start++)
	{
     
		ps->a[start] = ps->a[start + 1];
	}
	ps->size--;
}

这样我们所有的接口就都实现了。

顺序表的问题及思考

  1. 中间/头部的插入删除,时间复杂度为O(N)
  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  3. 增容一般是呈2倍的增长,势必会有一定的空间浪费,不能完全实现按需所取

总结

总的来说,顺序表还是比较好理解的,当然也有一下不足之处,为了弥补这些不足,所有就有了我下一篇博客要给大家分享知识——链表。欢迎大家评论区留言,点赞支持和指正~
【数据结构初阶】第二篇——顺序表(实现+动图演示)[建议收藏]_第9张图片

你可能感兴趣的:(初阶数据结构与算法,数据结构,c语言)