1.何为线性表
- 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
- 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表
2.1概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
-
静态顺序表:使用定长数组存储元素。
-
动态顺序表:使用动态开辟的数组存储
2.2 接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
#pragma once
#include<stdio.h>
#include <stdlib.h>
#include <assert.h>
//静态的顺序表结构
//#define N 1000
//typedef int SQDataType;
//
//typedef struct SeqList
//{
// SQDataType a[N];
// int size;
//
//}SLT;
//typedef struct SeqList SLT;
//动态的顺序表结构
typedef int SQDataType;
typedef struct SeqList
{
SQDataType* a;//声明了一个指向顺序表的指针,姑且称它为“顺序表指针”
int size;//有效数据的个数
int capacity;//容量空间大小
}SLT;
// 增删查改
void SeqListInit(SLT* psl);//初始化
void SeqListDestory(SLT* psl);//释放
void SeqListPrint(SLT* psl);//打印数组内容
void SeqListPushBack(SLT* psl, SQDataType x);//尾插 O(1)
void SeqListPushFront(SLT* psl, SQDataType x);//头插 O(n)
void SeqListPopBack(SLT* psl);//尾删 O(1 )
void SeqListPopFront(SLT* psl);//头删
int ListFind(SLT* psl, SQDataType x);//判断存在某一元素
void SqwListInsert(SLT* psl, size_t pos, SQDataType x);//查找某一元素
void SqeListErase(SLT* psl, size_t pos);//删除某一元素
size_t SqeListSize(SLT* psl);//查看数据内容
int SqeListAt(SLT* psl, size_t pos, SQDataType x);//修改元素内容
初始化
void SeqListInit(SLT* psl)
{
assert(psl);
psl->a = NULL;//刚开始时顺序表为空,顺序表指针为NULL
psl->size = psl->capacity = 0;//起始时元素个数为0,容量为0
}
销毁顺序表
因为顺序表所用的内存空间是动态开辟在堆区的,所以我们在使用完后需要及时对其进行释放,避免造成内存泄漏。
void SeqListDestory(SLT* psl)
{
assert(psl);
if (psl->a)
{
free(psl->a);//释放顺序表指针指向的空间
psl->a=NULL;
}
psl->size = psl->capacity = 0;
}
打印顺序表
void SeqListPrint(SLT* psl)
{
assert(psl);
for (int i = 0; i < psl->size; ++i)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
查看数据容量,设置增容
我们每次需要增加数据的时候,首先都应该先检查顺序表内元素个数是否已达顺序表容量上限。若已达上限,那么我们就需要先对顺序表进行扩容,然后才能增加数据。
void SeqListCheckCapcity(SLT* psl)
{
assert(psl);
// 是否满了,满了就要增容
if (psl->size == psl->capacity)
{
//判断顺序表容量是否为0,若为0,则先开辟用于存放4个元素的空间大小,若不为0,则扩容为原来容量的两倍
size_t newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
psl->a = realloc(psl->a, newcapacity * sizeof(SQDataType));//开辟成功,将顺序表指针更新
psl->capacity = newcapacity;
}
}
注意:若传入realloc的指针为空指针(NULL),则realloc函数的作用相当于malloc函数。
尾插
直接在表尾插入数据即可。
void SeqListPushBack(SLT* psl, SQDataType x)//尾插
{
assert(psl);
SeqListCheckCapcity(psl);
psl->a[psl->size] = x;
psl->size++;
}
头插
要想在顺序表的表头插入数据,那么就需要先将顺序表原有的数据从后往前依次向后挪动一位,最后再将数据插入表头。
void SeqListPushFront(SLT* psl, SQDataType x)//头插
{
assert(psl);
SeqListCheckCapcity(psl);
//挪动数据
int end = psl->size - 1;
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
end--;
}
psl->a[0] = x;
psl->size++;
}
指定下标位置插入
要做到在指定下标位置插入数据,首先我们需要得到一个下标位置,然后从该下标位置开始(包括该位置),其后的数据从后往前依次向后挪动一位,最后将数据插入到该下标位置。
void SqwListInsert(SLT* psl, size_t pos, SQDataType x)//插入某一元素
{
assert(psl);
assert(pos<=psl->size&&pos>=0);
SeqListCheckCapcity(psl);
size_t end = psl->size;
while (end > pos)
{
psl->a[end] = psl->a[end-1];
end--;
}
psl->a[pos] = x;
psl->size++;
}
头插和尾插实际上就是在下标为0的位置和下标为ps->size的位置插入数据,我们也可以使用该函数来实现头插和尾插。
尾删
直接将顺序表的元素个数减一即可。
void SeqListPopBack(SLT* psl)//尾删
{
assert(psl->size>0);//尾删>0断言
psl->size--;
}
头删
要删除表头的数据,我们可以从下标为1的位置开始,依次将数据向前覆盖即可。
void SeqListPopFront(SLT* psl)//头删
{
assert(psl->size>0);//尾删>0断言
psl->a[0] = 0;
for (int i = 0; i < psl->size; i++)
{
psl->a[i]=psl->a[i + 1];
}
psl->size--;
}
指定下标位置删除
要删除指定下标位置的数据,我们只需要从下标位置开始,其后的数据从前向后依次覆盖即可。
void SqeListErase(SLT* psl, size_t pos)//删除某一元素
{
assert(psl);
assert(pos < psl->size);
size_t begin = pos + 1;
while (begin < psl->size)
{
psl->a[begin-1] = psl->a[begin];
begin++;
}
psl->size--;
}
头删和尾删实际上也就是删除下标为0的位置和下标为ps->size - 1的位置的数据,也就意味着我们可以统一使用该函数来实现头删和尾删。
查找数据
直接遍历一次顺序表即可,若找到了目标数据,则停止遍历,并返回该数据的下标,否则返回-1。
int ListFind(SLT* psl, SQDataType x)//判断存在某一元素
{
assert(psl);
for (int i = 0; i < psl->size; ++i)
{
if (psl->a[i] == x)
printf("查找元素位置在:%d\n",i+1);
}
return -1;
}
修改数据
修改数据,就直接对该位置的数据进行再次赋值即可。
int SqeListAt(SLT* psl,size_t pos, SQDataType x)//修改元素内容
{
assert(psl);
assert(pos >= 0 && pos < psl->size);
psl->a[pos] = x;
}
The end