【数据结构与算法】顺序表

顺序表的概念和结构

顺序表是数据结构中线性表的一种。
用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
在这里插入图片描述
像之前的通讯录,其本质上就是顺序表。

那么很显然,顺序表相应的就有动态和静态两个版本。

其中静态版本中数组大小固定,容易发生空间不足或空间冗余。

所以,下面主要介绍动态顺序表各个接口功能的实现。


顺序表各个接口的实现

创建顺序表

由于我们要知道顺序表实时大小和实时容量,所以创建一个结构体变量能同时存储三个数据:

//重定义顺序表存储的数据类型,方便后续维护
typedef int SLDataType; 

typedef struct SeqList
{
	SLDataType* data;//指向后续动态内存分配的数据块的首地址
	int size;        //存储当前顺序表中的数据数量
	int capacity;    //存储当前顺序表的最大容量
}SeqList;

初始化顺序表

这一步我们需要创建顺序表并对其进行初始化,
将其 size 置零,capaticy 置为传入参数。
如果想把顺序表中每个数据都初始化为 0 的话可以使用 calloc 函数。

//初始化顺序表
void SeqListInit(SeqList* ps, size_t capacity);

void SeqListInit(SeqList* ps, size_t capacity)
{
	ps->data = (SLDataType*)malloc(sizeof(SLDataType) * capacity);
	if (ps->data == NULL)
	{
		perror("SeqListInit");
		return;
	}
	ps->size = 0;
	ps->capacity = capacity;
}

顺序表打印

打印顺序表中的数据,
因为顺序表中只有 size 个数据,
所以只需要循环 size 次打印。

void SeqListPrint(SeqList* ps);

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

检查空间,如果满了,进行增容

后面无论是要尾插、头插还是中间插,
都有可能出现顺序表满的情况,
所以在此之前要进行空间检查,
如果空间存满了就需要扩容。
这里每次将容量扩大为原来的 2 倍,
需要使用 realloc 函数。
扩容之后不要忘了重置 capacity

void CheckCapacity(SeqList* ps);

void CheckCapacity(SeqList* ps)
{
	SLDataType* tmp = NULL;
	if (ps->capacity == ps->size)
		tmp = realloc(ps->data, ps->capacity * 2);
	if (tmp != NULL)
		ps->data = tmp;
	else
		return;
	ps->capacity *= 2;
}

顺序表尾插

插入数据之前先判断是否需要扩容。
在顺序表的最后插入一个数据 x
由于 size 的值就是现有数据中最后一个数据的下标 +1
所以直接给 size 位置处的数据赋值。
尾插之后记得 size++

void SeqListPushBack(SeqList* ps, SLDataType x);

void SeqListPushBack(SeqList* ps, SLDataType x)
{
	CheckCapacity(ps);
	ps->data[ps->size] = x;
	ps->size++;
}

顺序表尾删

尾删,并不需要我们操作现有的最后一个数据。
因为我们看到的数据是前 size 个,
所以只需要改变 size 就可以减少呈现在我们眼前的数据。

void SeqListPopBack(SeqList* ps);

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

顺序表头插

插入数据之前先判断是否需要扩容。
头插的话后面的数据就要往后移,
从后往前来即可:
在这里插入图片描述
然后将空出的位置赋成我们想要的 x 即可。
最后将 size++

void SeqListPushFront(SeqList* ps, SLDataType x);

void SeqListPushFront(SeqList* ps, SLDataType x)
{
	CheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->data[end + 1] = ps->data[end];
		end--;
	}
	ps->data[0] = x;
	ps->size++;
}

顺序表头删

头删只需要把头上的元素覆盖掉,
覆盖顺序如下:
在这里插入图片描述
最后别忘了 size–

void SeqListPopFront(SeqList* ps);

void SeqListPopFront(SeqList* ps)
{
	assert(ps->size > 0);
	int start = 1;
	while (start < ps->size)
	{
		ps->data[start - 1] = ps->data[start];
		start++;
	}
	ps->size--;
}

顺序表查找

我们还需要实现在指定位置处插入或删除元素,
这就需要知道指定位置是哪个位置,
所以在此之前先完成查找接口。
只需遍历一遍顺序表,
这里返回的是指定元素第一次出现的位置。

int SeqListFind(SeqList* ps, SLDataType x);

int SeqListFind(SeqList* ps, SLDataType x)
{
	for (int i = 0; i < ps->size; ++i)
		if (ps->data[i] == x)
			return i;
	return -1;
}

删除指定位置的值

删除指定位置的值,
只需要将指定位置处的数据覆盖掉。
覆盖方式为从前往后走:
在这里插入图片描述

最后记得 size–

void SeqListErase(SeqList* ps, size_t pos);

void SeqListErase(SeqList* ps, size_t pos)
{
	assert(pos < ps->size);
	int start = pos + 1;
	while (start < ps->size)
	{
		ps->a[start - 1] = ps->a[start];
		start++;
	}
	ps->size--;
}

在指定位置插入

插入数据还是要先判断是否需要扩容。
首先将 pos 处及其后部元素向后挪一位,
然后把 pos 处元素赋成 x
在这里插入图片描述

void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
	assert(pos <= ps->size);
	SeqListCheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;
	ps->size++;
}

顺序表销毁

由于顺序表是动态开辟来的,
所以程序结束时一定要将空间释放,
以免造成内存泄漏。

void SeqListDestory(SeqList* ps);

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

总结

对于顺序表,很明显它是连续存放数据的,且支持随机访问,可以直接访问任意位置处的数据。

但是,每次头插或指定位置插入、删除元素都需要遍历数组,时间复杂度为 O(N),效率较低。

此外,这里动态增容一次增加原来的一倍,当数据量很大的时候,增容占用的空间也会更多,而如果剩余数据很少,那同样会造成大量空间的浪费。


完整代码

SeqList.h

将有关变量、符号及函数的声明、头文件的引用放在头文件中:

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLDataType;

typedef struct SeqList
{
	SLDataType* data;
	int size;
	int capacity;
}SeqList;

//初始化顺序表
void SeqListInit(SeqList* ps, size_t capacity);

// 顺序表打印
void SeqListPrint(SeqList* ps);

// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* ps);

// 顺序表尾插
void SeqListPushBack(SeqList* ps, SLDataType x);

// 顺序表尾删
void SeqListPopBack(SeqList* ps);

// 顺序表头插
void SeqListPushFront(SeqList* ps, SLDataType x);

// 顺序表头删
void SeqListPopFront(SeqList* ps);

// 顺序表查找
int SeqListFind(SeqList* ps, SLDataType x);

// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x);

// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos);

// 顺序表销毁
void SeqListDestory(SeqList* ps);

Seqlist.c

相关接口函数的实现放在源文件 Seqlist.c 中:

#include "SeqList.h"

//初始化顺序表
void SeqListInit(SeqList* ps, size_t capacity)
{
	ps->data = (SLDataType*)malloc(sizeof(SLDataType) * capacity);
	if (ps->data == NULL)
	{
		perror("eqListInit");
		return 0;
	}
	ps->size = 0;
	ps->capacity = capacity;
}

// 顺序表打印
void SeqListPrint(SeqList* ps)
{
	assert(ps->size > 0);
	for (int i = 0; i < ps->size; i++)
		printf("%d ", ps->data[i]);
	printf("\n");
}

// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* ps)
{
	SLDataType* tmp = NULL;
	if (ps->capacity == ps->size)
		tmp = realloc(ps->data, ps->capacity * 2);
	if (tmp != NULL)
		ps->data = tmp;
	ps->capacity *= 2;
}

// 顺序表尾插
void SeqListPushBack(SeqList* ps, SLDataType x)
{
	CheckCapacity(ps);
	ps->data[ps->size] = x;
	ps->size++;
}

// 顺序表尾删
void SeqListPopBack(SeqList* ps)
{
	assert(ps->size > 0);
	ps->size--;
}

// 顺序表头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
	CheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->data[end + 1] = ps->data[end];
		end--;
	}
	ps->data[0] = x;
	ps->size++;
}

// 顺序表头删
void SeqListPopFront(SeqList* ps)
{
	assert(ps->size > 0);
	int start = 1;
	while (start < ps->size)
	{
		ps->data[start - 1] = ps->data[start];
		start++;
	}
	ps->size--;
}

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

// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
	assert(pos <= ps->size);
	SeqListCheckCapacity(ps);
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;
	ps->size++;
}

// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos)
{
	assert(pos < ps->size);
	int start = pos + 1;
	while (start < ps->size)
	{
		ps->a[start - 1] = ps->a[start];
		start++;
	}
	ps->size--;
}

// 顺序表销毁
void SeqListDestory(SeqList* ps)
{
	free(ps->data);
	ps->data = NULL;
	ps->capacity = 0;
	ps->size = 0;
}
  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LeePlace

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值