数据结构(一)——顺序表的介绍

目录

什么是数据结构?

为什么需要数据结构?

顺序表

1.线性表

2.顺序表的分类

3.动态顺序表的实现

(1).顺序表的初始化和销毁

(2).顺序表的扩容

(3).顺序表的插入

尾插:

头插:

(4).顺序表的删除

尾删

头删

随机删除

(5).顺序表的打印

小结


什么是数据结构?

概念:计算机存储和组织数据的方式叫数据结构。指相互之间存在一种或多种特定关系的数据元素的集合。数据结构反应数据的内部构成,即:数据由哪部分构成,以什么方式构成,以及数据元素之间呈现的结构。

为什么需要数据结构?

例如,一个小区中有若干外观相似的住宅楼,如果我们没有按顺序对每一栋楼进行标号,那么当外卖员向你配送外卖时就会造成迷路、等待时间长、客户差评等等。同理,对于程序中的各种数据,如果我们不对数据进行管理,那么就会造成数据丢失、数据操作困难等问题,所以我们引入了数据结构,帮助我们对数据进行有效的管理。

顺序表

1.线性表

在介绍顺序表之前,我想先向大家介绍顺序表的概念。线性表是n个具有相同特性的数据元素的有限序列。是一种在实际中广泛存在的数据结构,常见的线性表有:顺序表、链表、栈、字符串等

特点:在逻辑上是线性的在物理结构上不一定是线性的。

2.顺序表的分类

顺序表分为静态顺序表动态顺序表,其本质区别是动态顺序表可以在使用的过程中扩容,而动态顺序表需要在使用之前指定好空间的大小,如果空间不够则会导致数据丢失。

静态顺序表定义如下所示:

cpp 复制代码
typedef int SLDataType;
#define N 100
typedef struct SeqList
{
	SLDataType arr[N];
	int size;
}SL;

动态顺序表定义如下所示:

cpp 复制代码
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* arr;
	int capacity;
	int size;
}SL;

3.动态顺序表的实现

顺序表的底层是数组,我们通过对数组的封装,实现了常用的增删查改等接口,其中的数据是按照顺序存放的。下面我将逐步分析如何实现上述操作。

首先,我们创建一个.h文件,并在其中定义一个结构体,如下所示:

cpp 复制代码
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* arr;
	int capacity;
	int size;
}SL;

我们看到,这个结构体里面包含了一个指针数组*arr,以及空间容量capacity和有效数据个数size,同时为了方便我们书写,我们将这个结构体重命名为SL,为了方便修改存放数据的类型,我们将数据类型重命名为SLDataType。

随后,我们在该文件中进行顺序表增、删、查、改等函数的声明。

cpp 复制代码
//顺序表初始化
void SLInit(SL* ps);
//顺序表销毁
void SLDestroy(SL* ps);

//顺序表的尾插/尾删
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps, SLDataType x);

//顺序表的头插/头删
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps, SLDataType x);

//顺序表的随机插入/删除
void SLInsert(SL* ps,int pos,SLDataType x);
void SLErase(SL* ps,int pos);
//顺序表的打印
void SLPrint(SL* ps);

在.h文件中完成函数的声明之后我们创建一个.c文件,并在此文件中包含.h文件,函数的具体实现是在这个文件中进行的。

(1).顺序表的初始化和销毁

我们在使用顺序表之前首先要对顺序表进行初始化,其目的是为了为其分配一个预定大小的内存空间,如果没有初始化,则会出现野指针问题;同理,我们在使用完顺序表之后,要对其中的数据进行销毁,如果没有销毁则会导致内存泄漏,因此,为了防止上述问题的出现,我们务必要对顺序表进行初始化和销毁的操作。

其具体实现代码如下:

cpp 复制代码
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->CapaCity = ps->size = 0;
}
void SLDestroy(SL* ps)
{
	assert(ps);

	free(ps);
	ps->arr = NULL;
	ps->CapaCity = ps->size = 0;
}
(2).顺序表的扩容

顺序表的插入分为头插、尾插以及在指定位置插入,而在这之前,都绕不开一个共同的问题:顺序表的扩容,这也是顺序表的难点,因此在介绍顺序表的插入之前,我想先向大家介绍顺序表的扩容。

其扩容思想无非就是:空间足够就插入,空间不足就扩容。由此,我们便可以写下以下代码:

cpp 复制代码
void SLCheckCapacity(SL* ps)
{
	if (ps->CapaCity == ps->size)
	{
		int newcapacity = ps->CapaCity == 0 ? 4 : 2 * ps->CapaCity;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);
		}
		ps->a = tmp;
		ps->CapaCity = newcapacity;
	}
}

分析上述代码,我们发现,我们首先面临的一个问题就是如何规定扩容空间的大小,开大了会造成空间浪费,开小了则会导致内存不足甚至数据丢失。那么我们该如何解决这个问题呢?答案就是:以原空间的二倍扩容,同时对于刚初始化的顺序表,我们给它4个大小的空间,这样我们可以有效避免上述的问题。在完成扩容的操作后,我们将开辟的空间给给顺序表,以上就是扩容代码的实现方法。

(3).顺序表的插入
尾插:
cpp 复制代码
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);

	SLCheckCapacity(ps);
	ps->arr[ps->size++] = x;
}
头插:
cpp 复制代码
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);

	SLCheckCapacity(ps);
	for (int i =ps->size; i >0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->a[0] = x;
	ps->size++;
}

我们看到,头插代码相对尾插代码要复杂一些,原因在于尾插不存在数据挪动的问题,而头插要先把顺序表中的数据依次向后挪动,然后再将数据插入。

顺序表可以在任意位置进行插入,其思想与头插相似,都是将数据向后挪动将该位置空出来,然后再将数据插入。其代码如下:

cpp 复制代码
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);

	SLCheckCapacity(ps);
	for (int i = ps->size; i > pos; i++)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}
(4).顺序表的删除
尾删
cpp 复制代码
void SLPopBack(SL* ps, SLDataType x)
{
	assert(ps);
	assert(ps->size);

	ps->size--;
}
头删

顺序表的头删同样存在数据挪动的问题,即就是将后面的数据向前挪动,覆盖之前的数据,从而达到删除的效果,代码如下:

cpp 复制代码
void SLPopFront(SL* ps, SLDataType x)
{
	assert(ps);
	assert(ps->size);

	for (int i = 0; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}
随机删除

与头删相似,都是将指定位置数据从后往前覆盖,代码如下:

cpp 复制代码
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(ps->size);

	for (int i = 0; i < pos - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}
(5).顺序表的打印

顺序表的打印就是数组的打印,其代码如下:

cpp 复制代码
void SLPrint(SL* ps)
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}

小结

顺序表就是对数组的一种封装,本质和数组区别不大,只不过通过封装,让我们能够更好的访问数据,这也是数据结构的意义,就是为了让我们能够更好的管理数据而存在。

冰冻三尺非一日之寒,在之后的日子里我会持续更新与数据结构相关的内容,如果喜欢的话希望能够点赞关注加转发,您的支持就是对我最大的鼓励,同时也希望在学习路上的你能够坚持下去,半山腰很挤,我想去山顶看看。

相关推荐
ö Constancy3 分钟前
Linux 使用gdb调试core文件
linux·c语言·vim
lb36363636364 分钟前
介绍一下strncmp(c基础)
c语言·知识点
wellnw8 分钟前
[linux] linux c实现共享内存读写操作
linux·c语言
Hera_Yc.H19 分钟前
数据结构之一:复杂度
数据结构
肥猪猪爸1 小时前
使用卡尔曼滤波器估计pybullet中的机器人位置
数据结构·人工智能·python·算法·机器人·卡尔曼滤波·pybullet
linux_carlos1 小时前
环形缓冲区
数据结构
readmancynn2 小时前
二分基本实现
数据结构·算法
Bucai_不才2 小时前
【数据结构】树——链式存储二叉树的基础
数据结构·二叉树
盼海2 小时前
排序算法(四)--快速排序
数据结构·算法·排序算法
一直学习永不止步2 小时前
LeetCode题练习与总结:最长回文串--409
java·数据结构·算法·leetcode·字符串·贪心·哈希表