顺序表详解

顺序表是用一块 连续的存储单元 连续存储数据

一、顺序表的结构

数组是一块 连续的空间,因此只需在数组上 存储连续的数据即可

注意: 顺序表中 不允许任意两个相邻的数据之间不存储数据
顺序表

1. 静态顺序表

若只创建一个数组 data,存储数据时,需要保证数据的存储是连续的,此时便无法确定数据应该存储在数组 data 的什么位置,因此这里需要增加一个变量 size,用来 记录当前存储了多少数据
顺序表
静态顺序表的结构如下:

#define N 100

//存储数据的类型
typedef int SLDataType;

//静态顺序表
typedef struct SeqList
{
	SLDataType data[N];
	int size;				//存储的数据个数
}SL;

2. 动态顺序表

在静态顺序表中,在运行之前需要指定数组的大小,当存储的数据较少时,会造成空间的浪费,当需要存储 N + 1 个数据时,便无法存储,带来了许多不便,为了满足按需申请连续空间的需求,可以采用动态顺序表

malloc,realloc等动态开辟内存的函数,便可以实现按需申请一块连续的空间,并用 data指针来管理,size 变量记录当前存储了多少数据,开始时容量开小一点,当容量不够时,进行扩容即可,所以这里还需要一个变量 capacity 用来记录容量

动态顺序表的结构如下:

//存储数据的类型
typedef int SLDataType;

//动态顺序表
typedef struct SeqList
{
	SLDataType* data;
	int size;			//存储的数据个数
	int capacity;		//当前容量
}SL;

二、顺序表的函数接口

本篇介绍动态顺序表的函数接口,静态顺序表插入删除的函数接口与其类似,读者掌握了动态顺序表,静态顺序表也就容易了

1. 初始化及销毁

创建一个顺序表之后,顺序表结构中的成员变量存储的都是一些随机值,所以需要对其进行初始化,这里采用 初始化时不分配空间的方式,也可以在初始化时就为其分配一些空间

初始化函数如下:

void SLInit(SL* ps)
{
	//ps 不能为空指针
	assert(ps);

	ps->data = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

在动态顺序表中:存储数据的空间是由自己开辟的,当不使用时应将其释放

销毁函数如下:

void SLDestroy(SL* ps)
{
	//ps 不能为空指针
	assert(ps);
	
	//顺序表中存储数据的空间是由 ps->data 指向的
	if (ps->data)
	{
		free(ps->data);
		ps->data = NULL;
		ps->size = ps->capacity = 0;
	}
}

2. 检测容量及打印顺序表

当插入数据时,无论是没有开辟空间还是数据已经放满了,都需要进行扩容,此时顺序表的 size 等于 capacity
顺序表

检测容量函数如下:

void CheckCapacity(SL* ps)
{
	//ps 不能为空指针
	assert(ps);

	//未开辟空间时需要扩容
	//存储容量满时需要扩容
	if (ps->size == ps->capacity)
	{
		//未开辟空间时,这里以开辟 4 个数据为例,读者可以自行定义一个宏来指定初始值
		//数据放满时,这里以扩二倍的方式来增长空间,读者可以自行决定扩多少空间
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;

		//realloc 第一个参数为 NULL 时,函数相当于 malloc
		SLDataType* tmp = (SLDataType*)realloc(ps->data, newcapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			//开辟空间失败,打印错误信息
			perror("realloc");
			
			//结束程序
			exit(-1);
		}

		ps->data = tmp;
		ps->capacity = newcapacity;
	}
}

为了验证插入、删除等得到的结果是否正确,提供打印顺序表的函数,这里数据类型以 int 为例,当读者采用的类型不同时,自行更改该函数即可
顺序表

打印顺序表数据的函数如下:

void SLPrint(SL* ps)
{
	int i;

	//ps 不能为空指针
	assert(ps);

	//打印数据
	for (i = 0; i < ps->size; ++i)
	{
		printf("%d ", ps->data[i]);
	}
	
	printf("\n");
}

3. 尾插尾删

尾插:在顺序表的最后一个位置之后插入数据

顺序表
尾插函数如下:

void SLPushBack(SL* ps, SLDataType x)
{
	//ps 不能为空指针
	assert(ps);

	//当容量满时,需要扩容
	CheckCapacity(ps);

	ps->data[ps->size] = x;
	ps->size++;
}

尾删:删除顺序表中最后一个位置的数据
顺序表

尾删函数如下:

void SLPopBack(SL* ps)
{
	//ps 不能为空指针
	assert(ps);

	//数据为空时,不能继续删除
	assert(ps->size > 0);

	ps->size--;
}

4. 头插头删

头插:在顺序表的第一个位置插入数据

顺序表

头插函数如下:

void SLPushFront(SL* ps, SLDataType x)
{
	int end = ps->size - 1;

	//ps 不能为空指针
	assert(ps);

	//当容量满时,需要扩容
	CheckCapacity(ps);

	//移动数据
	while (end >= 0)
	{
		ps->data[end + 1] = ps->data[end];
		--end;
	}

	ps->data[0] = x;
	ps->size++;
}

头删:删除顺序表的第一个位置
顺序表

头删函数如下:

void SLPopFront(SL* ps)
{
	//这里采用第二种方式
	int begin = 1;

	//ps 不能为空指针
	assert(ps);

	//数据为空时,不能继续删除
	assert(ps->size > 0);

	//移动数据
	while (begin < ps->size)
	{
		ps->data[begin - 1] = ps->data[begin];
		++begin;
	}

	ps->size--;
}

5. 中间插入和删除

中间插入:在合法的 pos 位置插入数据,与头插类似,不同的是应该将 pos 位置以及之后的数据依次向后移动,空出 pos 的位置,将数据 x 插入到 pos 位置即可

中间插入函数如下:

void SLInsert(SL* ps, int pos, SLDataType x)
{
	int end = ps->size - 1;

	//ps 不能为空指针
	assert(ps);

	//pos 应该是合法的位置
	assert(pos >= 0);
	assert(pos <= ps->size);

	//当容量满时,需要扩容
	CheckCapacity(ps);

	//移动数据
	while (end >= pos)
	{
		ps->data[end + 1] = ps->data[end];
		--end;
	}

	ps->data[pos] = x;
	ps->size++;

}

在调用中间插入函数 SLInsert 时

  • 如果在顺序表尾部插入数据,便和尾插函数的功能一样
  • 如果在顺序表头部插入数据,便和头插函数的功能一样

因此在尾插和头插函数的实现中可以直接调用中间插入函数 SLInsert

尾插和头插函数更改如下:

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	SLInsert(ps, ps->size, x);
}

//头插
void SLPushFront(SL* ps, SLDataType x)
{
	SLInsert(ps, 0, x);
}

中间删除:在合法的 pos 位置删除数据,与头删类似,不同的是因将 pos 位置之后的数据依次向前移动,覆盖 pos 的位置即可

中间删除函数如下:

void SLErase(SL* ps, int pos)
{
	//这里采用第二种方式
	int begin = pos + 1;

	//ps 不能为空指针
	assert(ps);

	//pos 应该是合法的位置
	assert(pos >= 0);
	assert(pos < ps->size);

	//移动数据
	while (begin < ps->size)
	{
		ps->data[begin - 1] = ps->data[begin];
		++begin;
	}

	ps->size--;
}

在调用中间删除函数 SLErase 时

  • 如果在顺序表尾部删除数据,便和尾删函数的功能一样
  • 如果在顺序表头部删除数据,便和头删函数的功能一样

因此在尾删和头删函数的实现中可以直接调用中间删除函数 SLErase

尾删和头删函数更改如下:

//尾删
void SLPopBack(SL* ps)
{
	SLErase(ps, ps->size - 1);
}

//头删
void SLPopFront(SL* ps)
{
	SLErase(ps, 0);
}

6. 查找

查找:如果数据存在返回数据在顺序表中的下标,不存在则返回非下标,这里返回 -1

查找函数如下:

int SLFind(SL* ps, SLDataType x)
{
	int i;

	//ps 不能为空指针
	assert(ps);

	//遍历顺序表
	for (i = 0; i < ps->size; ++i)
	{
		if (ps->data[i] == x)
			return i;
	}

	return -1;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值