【数据结构】顺序表

前言

从本篇我们开始学习数据结构初阶的内容。

首先我们了解一下什么是线性表:

线性表是n个具有相同特性的数据元素的有限序列。

常见的线性表有:顺序表(Sequence List)、链表(Linked List)、栈(Stack)、队列(Queue)、字符串(String)等
堆(Heap)、二叉树(Binary Tree)、图(Graph) 这些是非线性结构。

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表的存储结构主要分为两种:顺序存储结构和链式存储结构。

而今天我们主要讲解的是线性表中的顺序表

一、顺序表的概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,这种存储结构称为顺序存储结构。顺序表一般情况下采用数组存储,在数组上完成数据的增删查改等基本操作。
在这里插入图片描述

二、顺序表的基本操作实现

1.顺序表的创建

顺序表是用数组来进行存储的,因此我们可以在一个结构体中使用一个数组来存储数据,一个变量来记录数据个数。

静态顺序表:使用定长数组存储元素

typedef int SLDataType;//方便后续随时改数据类型
#define N 100 //数组最大长度

typedef struct SeqList
{
	SLDataType a[N];
	int size;	//有效数据个数
}SL;//命名为SL

但静态顺序表只适用于确定知道需要存多少数据的场景。
因为静态顺序表的数组长度N如果定大了,空间开多了浪费;N太小了则不够用。

所以我们一般使用动态顺序表,避免空间的浪费的同时也能随时扩容。

动态顺序表:根据需要来动态地分配空间大小。

#define INIT_SZ 10 //初始空间大小
#define INC_SZ 4   //每次扩容的数量
typedef int SLDataType;//方便改存储的数据类型

typedef struct SeqList
{
	SLDataType* a;
	int size;	//有效数据个数
	int capacity;//空间容量
}SL;//命名为SL

2.顺序表初始化

void SLInit(SL* ps)
{
	assert(ps);
	SLDataType* ptr = (SLDataType*)calloc(INIT_SZ, sizeof(SLDataType));
	if (NULL == ptr)
	{
		perror("calloc");//打印错误信息
		return;
	}
	ps->a = ptr;
	ps->size = 0; //初始存储的数据个数为0
	ps->capacity = INIT_SZ; //初始容量
}

assert()函数如果传入参数是空(NULL),vs会报错并显示具体行数。头文件#include<assert.h>。这样写可以提高代码的健壮性。

我们可以用malloc或者calloc来动态开辟数组,calloc能直接将开辟的空间初始化为0。
忘了怎么使用的小伙伴,可以复习一下另一篇动态内存管理的详解。

3.顺序表扩容

因为我们初始化时只开辟了一定的空间大小,后续在插入数据的时候,要检查可用空间是否足够,不够的话我们需要对原有空间进行扩容。

//检查容量是否已满
void CheckCapacity(SL* ps)
{
	assert(ps);
	if (ps->size == ps->capacity)//数据个数与容量相等说明可用空间已满,需要扩容
	{
		SLDataType* tmp = (SLDataType*)realloc(ps->a, (ps->capacity + INC_SZ) * sizeof(SLDataType));
		if (NULL == tmp)
		{
			perror("realloc");
			return;
		}
		ps->a = tmp;
		ps->capacity += INC_SZ;
	}
}

用realloc进行扩容,每次扩容的大小可以根据需要来改变,每次扩容一定数量,或者扩为原有空间的2倍。

4.头插和尾插

顺序表是用一维数组来存储的,顺序表的基本操作都是在数组的基础上进行的。

我们要对一个一维数组进行头插,也即在第一个元素前面插入一个数据,需要将数组整体往后挪一位。
具体方法为:数组从后往前,依次往后移动一位。只能从后往前,如果从前往后会覆盖掉原有数据。

void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	CheckCapacity(ps);//检查容量,如果已满则扩容
	for (int i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;//插入数据
	ps->size++;	//有效数据个数+1
}

而顺序表尾插就较为简单,直接在末尾插入。

void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	CheckCapacity(ps);
	ps->a[ps->size++] = x;
}

注意: 后面我们介绍完 在任意位置插入和删除操作后,头插尾插以及头删尾删都可以用这两个操作完成。
之所以详细写出头插尾插以及头删尾删的具体实现是为了方便大家理解。

5.头删和尾删

头删则需要将数组中每一个元素向前挪动一位。从前往后,将后一个位置的元素赋值给当前位置

void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;//有效个数-1
}

尾删只需要将顺序表长度-1即可。

但是注意,删除的空间不能free掉,因为动态开辟的空间不能局部释放。

void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);
	//free(ps->a[ps->size-1]);//不能这样写!!!
	ps->size--;
}

6.查找

查找函数可以根据数值找到对应的下标并返回,不存在则返回-1

int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x)
			return i;
	}
	return -1;
}

当然也可以根据下标返回对应的数值

int SLFind(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//查找的下标要在有效范围内
	return ps->a[pos];
}

7.任意位置插入

插入函数可以在下标为pos的位置插入数据,需要将pos位置及后面的数据的往后移一位,然后将插入数据存放在pos位置处。

void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);//插入的位置要符合逻辑
	CheckCapacity(ps);
	for (int i = ps->size; i > pos; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;
	ps->size++;
}

实现SLInsert函数后,我们的头插和尾插函数可以用一行代码替代。

SLInsert(ps, 0, x);	//头插
SLInsert(ps, ps->size, x);//尾插

8.任意位置删除

删除pos位置的数据,需要将pos后面的数据整体往前移一位。
往前移需要从前往后遍历顺序表,往后移需要从后往前遍历顺序表。

void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

至此,我们的头删和尾删函数也可以用一行代码替代。

SLErase(ps, 0);//头删
SLErase(ps, ps->size - 1);//尾删

在顺序表的具体实现时,我们就不用再写头插、尾插、头删、尾删了,一个插入函数和一个删除函数足矣。

9.顺序表的销毁

别忘了,我们的顺序表是动态开辟的, 动态开辟空间使用完之后一定要及时free释放,否则会导致内存泄漏。

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

三、总结

顺序表完整代码放在gitee顺序表

顺序表属于顺序存储结构,物理空间连续,一般是以数组的形式存储的。

顺序表的优点

可以随机访问任意位置的元素,效率为O(1),尾插尾删效率很高。

顺序表的缺点

1.中间或者头部的插入删除效率很低,时间复杂度为O(N)。
2.扩容有一定的消耗,可能会有一定空间的浪费。

  • 28
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
顺序表是一种常见的数据结构,用于存储一组具有相同类型的元素。它的基本操作包括创建、查找、插入和删除等。 在C语言中,可以通过使用数组来实现顺序表。我们可以定义一个数组来存储顺序表的元素,然后通过索引来访问和操作其中的元素。引用中的代码展示了一个简单的顺序表的例子,它使用了C语言实现。在这个例子中,我们可以看到如何创建一个顺序表、插入元素、删除元素以及查找元素的前驱和后继。 引用中的代码是一个测试顺序表基本操作的例子。它使用了一个自定义的List类来实现顺序表,并对其进行了各种操作,包括插入元素、删除元素、查找元素的前驱和后继以及遍历整个顺序表顺序表的结构体定义可以根据具体的需求进行定义。在引用中的代码中,定义了一个结构体ConnTimeout,其中包含了一个int类型的fd和一个time_t类型的timeout成员变量。这个结构体可以用来表示连接超时的信息。 总而言之,顺序表是一种常见的数据结构,可以通过使用数组来实现。在C语言中,可以使用数组和相关的操作来创建、查找、插入和删除顺序表中的元素。结构体的定义可以根据具体的需求进行自定义。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [数据结构顺序表c++代码](https://download.csdn.net/download/qq_46546083/13458006)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [C++数据结构顺序表(模板类实现)](https://blog.csdn.net/sagjhdj/article/details/123260460)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [【数据结构顺序表(C++)](https://blog.csdn.net/qq_51604330/article/details/120600041)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值