线性数据结构:深入探究顺序表

本文详细介绍了C语言中的顺序表,包括静态顺序表和动态顺序表的概念、特点以及它们的操作方法,如尾插、尾删、首插、首删和查找。同时探讨了顺序表与链表的区别。
摘要由CSDN通过智能技术生成

🤖💻👨‍💻👩‍💻🌟🚀

🤖🌟 欢迎降临张有志的未来科技实验室🤖🌟

专栏:  数据结构            

👨‍💻👩‍💻 先赞后看,已成习惯👨‍💻👩‍💻

👨‍💻👩‍💻 创作不易,多多支持👨‍💻👩‍💻

🚀 启动创新引擎,揭秘C语言的密码🚀


💡目录

【目标】

【线性表  (Linear List)】

【顺序表详解】

顺序表主要特性

接口实现

尾插

尾删

首插

首删


【目标】

1.线性表

2.顺序表

3.链表

4.顺序表和链表的区别

【线性表  (Linear List)】

线性表是一种数据结构,其特点是数据元素之间存在一对一的线性关系。简单来说,线性表是由零个或多个相同类型的数据元素构成的一个有限序列。每个数据元素在序列中都有一个确定的位置(称为索引或位置编号),并且除第一个元素(表头)和最后一个元素(表尾)外,其余元素有且仅有一个直接前驱元素和一个直接后继元素。

线性表的逻辑结构清晰,易于理解和实现。根据存储结构的不同,线性表可以分为以下两类:

  • 顺序表:线性表的元素在计算机内存中按照其逻辑顺序依次存储,占据一片连续的存储空间。在C语言中,可以使用数组来实现顺序表。

    int arr[10]; // 定义一个长度为10的整型数组,即一个顺序线性表
    
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    或者
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    #define N number
    
    struct SeqList 
    {
        int arr[N]; //有固定大小的数组
        int size;   //表示有效数据
    };
  • 链表:线性表的元素在内存中不必连续存放,每个元素(称为节点)包含数据域和指针域,指针域用于存储下一个元素的地址。链表分为单链表、双链表和循环链表等多种形式。在C语言中,链表通常通过结构体和指针来实现。

struct ListNode {
    int data; // 数据域
    struct ListNode *next; // 指针域,指向下一个节点
};

struct ListNode *head; // 定义链表头指针


【顺序表详解】

前面我简单介绍了一下顺序表和链表,在本文章中,我打算详解顺序表,链表我将在下一篇文章中为大家详细介绍。顺序表分为两种:

  • 静态顺序表
#define N 10

typedef struct SeqList
{
    int arr[N];  //不可改变大小
    int size;   //有效数据数量
}SeqList;
  • 动态顺序表
typedef struct SeqList
{
    int arr[]; //柔性数组
    int size;  //有效数据数量
    int capacity;  //数据容量
}SeqList;

静态顺序表和动态顺序表比起来,后者更灵活,但是操作难度也会变大。

【顺序表主要特性】

  • 随机访问:借助索引,可以在常数时间内直接访问任一元素。

  • 插入与删除的复杂性:在非尾部位置插入或删除元素时,可能导致后续元素的位移,操作复杂度一般为O(n)。

【接口实现】

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪 费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。

  • 初始化

将结构体中的所有成员初始化,避免遇到问题

void SeqList_init(SeqList* ps)
{
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
  • 判断是否扩容

在本文章,这个函数起到关键作用

 void SeqList_build(SeqList* ps)  //初始化或调整柔性数组大小
{
	if (ps->capacity==ps->size)
	{
		int new_capacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;

		ps->arr = (int*)realloc(ps->arr, new_capacity * sizeof(int));

		ps->capacity = new_capacity;
	}
	if (ps->arr == NULL)
	{
		perror("realloc:");
		return;
	}
} 

首先需要对size和capacity进行比较,如果相等,即开辟空间,这里有两种可能性:我们从未开辟过空间,或者没有足够的空间去储存下一个元素。对于前者,我们仅仅需要开辟一块大小为4个int类型的空间;后者我们则需要重新开辟。

这里创建new_capacity变量是为了增加可读性,如果不创建该变量,虽然可以将代码行数减少,但增加了开发难度。

最后不要忘记将原有capacity重新赋值。

  • 顺序表的销毁

由于顺序表是对动态内存进行开辟,所以我们需要销毁

//顺序表的销毁
void SLDestory(SeqList* ps)
{
	if (ps->arr)
	{
		free(ps->arr);
	}
	ps->capacity = ps->size = 0;
	ps = NULL;
}
  • 尾插

顾名思义,我们将从数组的最后插入一个数据

首先,我们将基本框架建出来

void SLPUSHBACK(SeqList* ps,int task)
{
	ps->arr[ps->size++] = task;
}

但是这样会出现很多安全问题,比如数据超出了 ps->arr 的容量怎么办,如果传参NULL怎么办,因此我们需要对它做一些优化:判断arr是否需要扩容,判断传参是否为 NULL

判断是否需要扩容需要我们自己写一个函数,具体如下:

void SeqList_build(SeqList* ps)  //初始化或调整柔性数组大小
{
	if (ps->capacity==ps->size)
	{
		int new_capacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		ps->arr = (int*)realloc(ps->arr, new_capacity * sizeof(int));
		ps->capacity = new_capacity;
	}

	if (ps->arr == NULL)
	{
		perror("realloc:");
		return;
	}
} 

以上代码看起来唬人,其实逻辑并不复杂。我们先判断原有容量和有效数据数量是否相等,只有两种情况:我们并未进行初始化、我们的数组容量已经达到上限。因此,我们需要对每种情况进行分析:如果我们未初始化,初始化就好了;如果达到上限,扩容就好了。为了方便,我们可以采用三目运算符(当然,if也不失为一种好方法)。注意,不要忘记将capacity的值进行修改,并判断空间是否成功开辟。

针对传参为NULL的问题,断言就可以完美解决。

在原有函数中加入:

//尾插
void SLPushBack(SeqList* ps,int task)
{
	assert(ps);

	SeqList_build(ps); //判断是否需要扩容

	ps->arr[ps->size++] = task;
}
  • 尾删

删除柔性数组的最后一个元素

//尾删
void SLPopBack(SeqList* ps)
{
	assert(ps);
	assert(ps->arr);

	//ps->arr[ps->size--] = -1;这一行并不重要,并不影响顺序表增删查改
	ps->size--;
}

其实并不能真正意义上叫做删除,上述代码仅仅是将 ps->size-- ,并没有对数据进行任何操作

  • 首插

顾名思义,在柔性数组的首元素插入数据

和尾插的思路相似,在尾插的基础上将所有数据往后移一个位置

//首插
void SLPushFront(SeqList* ps,int task) 
{
	assert(ps);
	SeqList_build(ps);//判断是否需要开辟空间

	for (int i = ps->size; i>0; i--) //每个元素往后移
	{
		ps->arr[i] = ps->arr[i-1];
	}

	ps->arr[0] = task;//在首位置插入目标
	ps->size++;//有效数字+1
}
  • 首删

与尾删有着较大区别,我们需要将第一个元素删除并用下一个元素补齐

//首删
void SLPopFront(SeqList* ps)
{
	assert(ps);
	assert(ps->arr);

	for (int i = 0; i < ps->size; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}
  • 中插

将元素在数组的指定位置插入

//中插
void SLPushInert(SeqList* ps,int pos,int task)
{
	assert(ps);
	assert(ps->arr); //判断ps和柔性数组是否为空指针

	SeqList_build(ps); //判断是否需要扩容

	for (int i = ps->size-1; i > pos; i--) //将pos(包含)后每个元素向后移
	{
		ps->arr[i + 1] = ps->arr[i];
	}

	ps->arr[pos] = task; //插入
	ps->size++; //有效数量+1
}

基本思路就是将 pos 及其之后的数据向后挪一位,然后将数据插入 pos 位置。安全问题需要考虑ps 是否为空指针,ps->arr 是否需要扩容

  • 中删

将元素从指定位置删除,本质上是将该元素用下一个元素覆盖

//中删
void SLPopInsert(SeqList* ps, int pos)
{
	assert(ps);
	assert(ps->arr);
	assert(pos >= 0 && pos <= ps->size);

	for (int i = pos; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	ps->size--;
}

将 pos 位置的数据用下一个元素覆盖,最后将size--,安全问题参考上文。

  • 查找

在数组中查找指定元素

//查找
void SLFound(SeqList* ps, int task)
{
	assert(ps);
	assert(ps->arr);

	for (int i = 0; i < ps->size; i++)
	{
		if (ps->arr[i] == task)
		{
			printf("找到%d了,下标为:[%d]\n", task, i);
			return;
		}
	}
	printf("没找到\n");
}

这个同样没有技术含量,不过是循环中嵌套 if 语句

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值