🤖💻👨💻👩💻🌟🚀
🤖🌟 欢迎降临张有志的未来科技实验室🤖🌟
专栏: 数据结构
👨💻👩💻 先赞后看,已成习惯👨💻👩💻
👨💻👩💻 创作不易,多多支持👨💻👩💻
🚀 启动创新引擎,揭秘C语言的密码🚀
💡目录
【目标】
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 语句