数据结构之顺序表

🎉welcome🎉
✒️博主介绍:博主大一智能制造在读,热爱C/C++,会不定期更新系统、语法、算法、硬件的相关博客,浅浅期待下一次更新吧!
✈️算法专栏:算法与数据结构
😘博客制作不易,👍点赞+⭐收藏+➕关注


前言

正如文章标题所说,本文将带大家了解数据结构中线性表之一——顺序表,那再描述这种数据结构之前,让我们先了解,什么是数据结构?数据结构,是计算机中用于存储数据的一种方式,这种方式是数据之间一种特定的关系,比如本博客的顺序表,再存储的时候是按照一段连续空间存储的,除了顺序表,还有很多种数据结构,如同是线性表的链表、栈等,那接下来博主将分享自己对于顺序表的见解。


顺序表

概念

顺序表是一段物理地址上连续空间存储的数据的数据结构,也就是我们常用的数组,但是数组是固定大小的,这也就是静态顺序表,相对,也存在动态顺序表,动态和静态的区别在于空间,会了动态的,可以改为静态的,本文将会讲解动态顺序表的增删查改。


动态顺序表

动态顺序表,顾名思义,它是可以动的,它的动是再空间上动,它的空间可以是无限大的,也就是说,再理想状态下,你的电脑可以存储多少数据,那动态的顺序表就可以存储多少数据。

动态顺序表结构体成员

动态顺序表,因为我们需要扩容,则需要两个数据,一个是当前的顺序表内部有多少个元素,一个是这个顺序表容量是多少,同时,表示内部多少个元素还可以知道我们的最后一个元素的下标是多少,让后面的尾删和尾插方便很多,那除了这两个元素还需要什么?我们的数据存放在哪里?所以这个时候,还需要一个成员用于存放数据,这里因为要存在int类型的数据,便以int类型来给大家演示:

typedef int SLDateType;//到时候想改存放数据类型,直接改掉这里,就改掉了全部

typedef struct SeqList
{
	SLDateType* a;//存放数据
	int size;//当前存放多少元素
	int capacity;//最大容量
}SeqList;

动态顺序初始化

那我们的动态顺序表的初始化应该是什么?size是0,因为初始化的时候,内部没有元素,那最大容量应该是多少?我们初始化顺序表的时候开辟的空间应该开辟多少?这是个问题,这里,开辟多少是凭借大家的喜好,那博主在这里初始化的时候开辟四个int类型的空间,同时,这里我们将使用#define来定义我们的初始化空间,这样在后期改的时候,直接改掉#define就改掉了全部:

 //放在.h文件当中
#define INIT_CAPACITY 4

//放在.c文件当中
//初始化顺序表
void SeqListInit(SeqList* ps)
{
	ps->a = (SeqList*)malloc(sizeof(SeqList) * INIT_CAPACITY);//开辟空间
	if (ps->a == NULL)
	{
		perror("SeqList");
	}
	ps->size = 0;//顺序表中的元素个数
	ps->capacity = INIT_CAPACITY;//顺序表容器的大小
}

动态顺序表动态实现

动态顺序表的动态实现其实不难,只需要再每次增加空间的时候,判断空间是否已经满了,如果满了,那就利用动态内存malloc或者realloc来开辟空间,那这个空间开辟多少才是最合适的呢?因为如果每次都多三个或者几个,那是不是开辟的太频繁了,是影响效率的,则我们每次都开辟到它原本容量的两倍,这个时候,开辟会变得不是很频繁,同时,对于动态顺序表存在弊端,那就是因为我们申请的空间是连续的,则对于这片空间而言,当我们删除了大量的数据的时候,后面的空间是空的,这个时候,因为是连续的,则不能释放掉空间,因为释放哪里,都是整个空间释放掉,意思是,再数据大量删除之后,顺序表是会浪费很多空间的,链表则不会存在这种问题,但是相比较链表,顺序表也存在优势,会再链表的博客提到,这里不做过多讲解,那接下来,就是动态顺序表的实现:

void SeqListdilata(SeqList* ps)
{
	assert(ps);//断言如果为空则说明传过来的数据有问题
	if (ps->size == ps->capacity)//当我们存放的元素个数和容器最大容量一样的时候,说明需要扩容
	{
		SeqList* tmp = (SeqList*)realloc(ps->a, sizeof(SeqList) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("dilata");
			return;
		}
		ps->a = tmp;//因为realloc开辟的空间的时候可能换到另一个地方
		ps->capacity *= 2;
	}
}

动态顺序表的增

对于顺序表而言,我们是可以在它的任意位置添加数据的,则对于顺序表,我们可以进行头插、尾插、任意位置的插入,那这里就一一讲解。

动态顺序表的头插

头插,在第一个位置插入数据,对于顺序表而言,头插会影响到全部数据,都向后挪一个格,因为从第一个元素开始挪动的话,会覆盖掉后面的数据,导致数据丢失,所以我们从最后面开始向后覆盖:

//顺序表头插
void SeqListPushFront(SeqList* ps, SLDateType x)
{
	//判断是否需要扩容
	SeqListdilata(ps);
	//头插需要先将元素全部后移一位,将头空出来
	for (int i = ps->size - 1; i >= 0; i--)
	{
		ps->a[i + 1] = ps->a[i];
	}
	ps->a[0] = x;
	ps->size++;
}
动态顺序表的尾插

尾插,则不需要和头插一样,因为尾插就是在最后插入:

//顺序表尾插
void SeqListPushBack(SeqList* ps, SLDateType x)
{
	//先判断是否需要扩容
	SeqListdilata(ps);
	//然后插入数据,注意size要增加
   ps->a[ps->size++] = x;
}
动态顺序表的随机位置插入

随机插入,其实如同头插一样,只不过不用整体挪动,就只需要插入之后的部分挪动即可:

 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);//判断pos位置是否合法
	//先扩容
	SeqListdilata(ps);
	int end = ps->size - 1;
	while (end >= pos)
	{
		ps->a[end + 1] = ps->a[end];
		--end;
	}
	ps->a[pos] = x;
	ps->size++;
}

可以看到,三个插入的重复代码非常多,那其实,对于头插和尾插,可以直接调用随机插入,这里就不做演示了。

动态顺序表的删

删除和插入一样,分为了头删、尾删、随机位置的删除。

动态顺序表的头删

动态顺序表的头删和头插一样,都需要挪动全部数据,但是不同的时候,挪动的是除了头数据的全部数据,全部向前挪动一位,覆盖掉即可,但是这个是从第一个数据开始向前覆盖,不是向后覆盖了:

//顺序表头删
void SeqListPopFront(SeqList* ps)
{
	//断言检查
	assert(ps->a);
	//头删只需要讲全部元素前移一位即可
	for (int i = 0; i < ps->size; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}
动态顺序表的尾删

动态顺序表的尾删其实很简单,我们只要将size–,访问不到我们的尾部的这个元素,就相当于删除:

//顺序表尾删
void SeqListPopBack(SeqList* ps)
{
	//断言检查
	assert(ps->a);
	//只需要size--,就访问不到最后的元素了
	ps->size--;
}
动态顺序表的随机位置删除

随机位置的删除和头删也是大同小异,也只是不用挪动全部元素,只需要挪动一个元素即可:

// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	//如同插入
	if (pos == 0)
		SeqListPopFront(ps);
	if (pos == ps->size)
	 	SeqListPopBack(ps);
	for (int i = pos+1; i < ps->size ; i++)
	{
		ps->a[i-1] = ps->a[i];
   }
	ps->size--;
}

如同随机位置位置插入一样,头删尾删也可以用随机位置删除。

动态顺序表的查找

当想要查找一个值在不在这个顺序表当中时,就要用到查找功能了,查找功能并不复杂,对于顺序表而言,我们的顺序表不是有序的,则只能用遍历,遍历的途中对比数据,如果找到,返回现在的下标,找不到就返回-1,如果这个顺序表是有序的,就可以用二分提升效率,这里用遍历:

// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x) 
{
	assert(ps);
	//遍历数组,对比数组中和x是否相等,相等跳出去,返回x的下标
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
			return i;
	}
	return -1;
}

动态顺序表的其他操作

我们知道,对于用动态内存开辟的空间在堆上,如果不释放掉就会导致内存泄漏,那在不使用的时候,是不是要销毁掉当前的动态顺序表才可以,同时,对于顺序表是否插入删除数据,我们可以打印看看,那也就可以有打印操作:

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

使用验证

#include"SeqLish.h"

int main()
{
	SeqList  a;
	SeqListInit(&a);

	for (int i = 0; i < 5; i++)
	{
		SeqListPushBack(&a, i);
	}
	for (int i = 5; i < 10; i++)
	{
		SeqListPushFront(&a, i);
	}
	SeqListPrint(&a);
	SeqListPopFront(&a);
	SeqListPrint(&a);
	SeqListPopBack(&a);
	SeqListPrint(&a);
	SeqListDestroy(&a);
	SeqListPrint(&a);
	return 0;
}

在这里插入图片描述

那通过试验,我们发现我们的顺序表代码是没有问题的,那这样我们的动态顺序表就写好了

源码

test.c

#include"SeqLish.h"

int main()
{
	SeqList  a;
	SeqListInit(&a);

	for (int i = 0; i < 5; i++)
	{
		SeqListPushBack(&a, i);
	}
	for (int i = 5; i < 10; i++)
	{
		SeqListPushFront(&a, i);
	}
	SeqListPrint(&a);
	SeqListPopFront(&a);
	SeqListPrint(&a);
	SeqListPopBack(&a);
	SeqListPrint(&a);
	SeqListDestroy(&a);
	SeqListPrint(&a);
	return 0;
}

seqlist.c

#include"SeqLish.h"

//顺序表扩容
void SeqListdilata(SeqList* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)
	{
		SeqList* tmp = (SeqList*)realloc(ps->a, sizeof(SeqList) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("dilata");
			return;
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}
}
//初始化顺序表
void SeqListInit(SeqList* ps)
{
	ps->a = (SeqList*)malloc(sizeof(SeqList) * INIT_CAPACITY);//开辟空间
	if (ps->a == NULL)
	{
		perror("SeqList");
	}
	ps->size = 0;//顺序表中的元素个数
	ps->capacity = INIT_CAPACITY;//顺序表容器的大小
}
//清空顺序表
void SeqListDestroy(SeqList* ps)
{
	assert(ps);
	free(ps->a);
	//ps->a = NULL;
	ps->size = ps->capacity = 0;
}
//打印顺序表
void SeqListPrint(SeqList* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}
//顺序表尾插
void SeqListPushBack(SeqList* ps, SLDateType x)
{
	//先判断是否需要扩容
	SeqListdilata(ps);
	//然后插入数据,注意size要增加
	ps->a[ps->size++] = x;
}
//顺序表头插
void SeqListPushFront(SeqList* ps, SLDateType x)
{
	//同样判断是否需要扩容
	SeqListdilata(ps);
	//头插需要先将元素全部后移一位,将头空出来
	for (int i = ps->size - 1; i >= 0; i--)
	{
		ps->a[i + 1] = ps->a[i];
	}
	ps->a[0] = x;
	ps->size++;
}
//顺序表头删
void SeqListPopFront(SeqList* ps)
{
	//断言检查
	assert(ps->a);
	//头删只需要讲全部元素前移一位即可
	for (int i = 0; i < ps->size; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}
//顺序表尾删
void SeqListPopBack(SeqList* ps)
{
	//断言检查
	assert(ps->a);
	//只需要size--,就访问不到最后的元素了
	ps->size--;
}
// 顺序表查找
int SeqListFind(SeqList* ps, SLDateType x)
{
	assert(ps);
	//遍历数组,对比数组中和x是否相等,相等跳出去,返回x的下标
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
			return i;
	}
	return -1;
}
 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SLDateType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	//先扩容
	SeqListdilata(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, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	//如同插入
	if (pos == 0)
		SeqListPopFront(ps);
	if (pos == ps->size)
		SeqListPopBack(ps);
	for (int i = pos + 1; i < ps->size ; i++)
	{
		ps->a[i - 1] = ps->a[i];
	}
	ps->size--;
}

seqlist.h

#pragma once

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

typedef int SLDateType;
#define INIT_CAPACITY 4
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);

🚀专栏:算法与数据结构
🙉都看到这里了,留下你们的👍点赞+⭐收藏+📋评论吧🙉

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

封心锁爱的前夫哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值