【数据结构】顺序表

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:数据结构
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注


一、概念

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

二、结构

顺序表一般可以分为:
1. 静态顺序表:使用固定长度的数组来存储元素。

#define N 4

typedef int SLDataType;

typedef struct SeqList
{
	SLDataType a[N]; //固定长度的数组
	int size;        //有效的数据个数
}SeqList;

但静态顺表表有个缺点,当它开的长度太少,会导致不够用;当长度开的过长,会导致空间浪费。因此一般都使用动态顺序表。

2. 动态顺序表:使用动态开辟的数组存储

typedef int SLDataType;

typedef struct SeqList
{
	SLDataType* a; // 指向动态开辟的数组
	int size;      //有效数据的个数
	int capacity; //容量空间的大小
}SeList;

几个问题

  1. 为什么要将int类型重命名为SLDataType
    原因是:当内存中不是存储整型类型的数据,假设存储的是double,如果不嫌麻烦就要把每个接口的int改为double。将类型重命名,就会省去很多麻烦。
  2. 为什么还要定义capacity容量大小?
    原因是:这是一个动态顺序表,当有效数据的个数大于容量空间的大小,就要扩容。

三、代码实现

3.1 准备工作

为了方便管理,我们可以创建多个文件来实现

  1. test.c - 测试代码逻辑 (源文件)
  2. SeqList.c - 动态的实现 (源文件)
  3. SeqList.h - 存放函数的声明 (头文件)
    在这里插入图片描述

3.2 实现内容

在开头的概念中说了,顺序表一般是在数组上完成数据的增删查改。这里我给出常见的接口实现内容。

【SeqList.h】

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

typedef int SLDateType;

typedef struct SeqList
{
	SLDateType* a;
	int size;
	int capacity;
}SeqList;

//接口

//数组初始化
void SeqListInit(SeqList* ps);
//内存释放
void SeqListDestroy(SeqList* ps);
//打印数组内容
void SeqListPrint(SeqList* ps);
//数组的尾插
void SeqListPushBack(SeqList* ps, SLDateType x);
//数组的头插
void SeqListPushFront(SeqList* ps, SLDateType x);
//数组的头删
void SeqListPopFront(SeqList* ps);
//顺序表之尾删
void SeqListPopBack(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos);

3.3 顺序表之结构体初始化

注:以下代码都在SeqList.c中实现

void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->a = (SLDateType*)malloc(sizeof(SLDateType) * 4);
	if (ps->a == NULL)
	{
		perror("ps->a :: malloc");
		return;
	}
	ps->size = 0;
	ps->capacity = 4;
}

【笔记总结】

  1. assert(断言)是什么
    assert其实是一个报错函数,其作用是如果它括号中的条件为假,就会终止程序执行,然后返回错误原因。这里就要提提为什么要断言ps指针,首先ps是指向整个结构体的。如果ps是一个空指针,这个顺序表玩个“屁”啊hh。所以为了防止有人传错,最好加一个断言。
    在这里插入图片描述
  2. 动态内存开辟函数回顾 —> 动态内存管理
  3. 什么是perror函数 —> 常见的内存操作函数

3.4 顺序表之内存空间释放

void SeqListDestroy(SeqList* ps)
{
	assert(ps);
	free(ps->a);
	ps->a == NULL;
	ps->size = 0;
	ps->capacity = 0;
}

【笔记总结】

  1. 为什么要释放内存空间?
    因为在结构体初始化中,malloc向内存申请了空间,如果由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。(内存泄漏)
  2. 动态内存开辟函数回顾 —> 动态内存管理

3.5 顺序表之打印

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

3.6 顺序表之尾插

void SeqListPushBack(SeqList* ps, SLDateType x)
{
	assert(ps);
	//扩容
	if (ps->size == ps->capacity)
	{
		//扩容原来容量的2倍
		SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * ps->capacity  * 2);
		if (tmp == NULL)
		{
			perror("realloc");
			return;
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}
	ps->a[ps->size] = x;
	ps->size++;
}

3.7 顺序表之头插

void SeqListPushFront(SeqList* ps, SLDateType x)
{
	assert(ps);
	//头插也需要扩容
	if (ps->size == ps->capacity)
	{
		//扩容原来容量的2倍
		SeqList* tmp = (SeqList*)realloc(ps->a, sizeof(SeqList) * 2 * ps->capacity);
		if (tmp == NULL)
		{
			perror("realloc");
			return;
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}

	//头插
	int end = ps->size - 1;
	while (end >= 0)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[0] = x;
	ps->size++;
}

讲讲头插过程
end一定要从最后一个数据开始往后挪动。
在这里插入图片描述
从头开始往后挪数据会造成后面数据被前一个数据覆盖了(错误演示)
在这里插入图片描述

3.8 顺序表之头删

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

	//头删
	int begin = 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
	ps->size--;
}

【笔记总结】

  1. 首先要保证顺序表不能为空,空的顺序表是不能删数据的,所以要加个断言assert(ps->size > 0)
  2. 头删过程动图展示(begin从下标1开始)
    在这里插入图片描述

3.9 顺序表之尾删

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

【笔记总结】

  1. 空顺序表也不能尾删,所以要断言一下.
  2. 尾删时不需要让尾数据赋值成0,因为顺序表中的数据也有可能为0。只需要让size--,因为顺序表下标的访问都依靠size

3.10 顺序表之查找

int SeqListFind(SeqList* ps, SLDateType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
		{
			//找到返回下标
			return i; 
		}
	}
	//循环跳出就代表找不到
	//假设找不到返回-1
	return -1;
}

3.11 顺序表之插入

void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
	assert(ps);
	assert(pos >= 0 && ps <= ps->size);
	//插入也可能需要扩容
	if (ps->size == ps->capacity)
	{
		//扩容原来容量的2倍
		SLDateType* tmp = (SLDateType*)realloc(ps->a, sizeof(SLDateType) * 2 * ps->capacity);
		if (tmp == NULL)
		{
			perror("realloc");
			return;
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}
	//插入
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[pos] = x;
	ps->size++;
}

【学习笔记】

  1. 插入pos的范围必须在[0,size]pos = size相当于尾插
  2. 插入动图展示
    在这里插入图片描述

3.12 顺序表之删除

void SeqListErase(SeqList* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	int begin = pos + 1;
	while (begin < ps->size)
	{
		ps->a[begin - 1] = ps->a[begin];
		begin++;
	}
	ps->size--;
}

【学习笔记】

  1. 首先pos的范围是[0,size),原因是以size为下标是没有元素的,而空顺序表是不能够删除的
  2. 动图展示
    在这里插入图片描述

四、总结

  1. 顺序表的好处是在于它尾插和尾删的时间复杂度是O(1),但头删和头插时间复杂度是O(N)
  2. 增容需要申请新的空间,拷贝数据还要释放空间,都会有不少的消耗。
  3. 空间不够时,需要扩容。而扩容其实也会造成一定的空间浪费。例如,当前的空间是5,满了以后扩容到10(因为上面规定扩容要是当前空间的2倍),而后面只需要再插入1个数据,那么就会浪费4个数据空间。
  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值