目录
1.线性表的介绍
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储
2.顺序表
顺序表是数据结构中给的单独的名字,严格来说顺序表就是数组
2.1顺序表的概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
注:顺序表要求数据是连续存储的 、
2.2静态顺序表
使用定长数组存储元素
我们可以用宏(define)来控制数组大小,方便修改数组大小
因为要控制数组是连续存储的 我们需要用size来控制存储顺序
下面是静态顺序表的定义
#define N 100
typedef int DataType
typedef struct StaticSeqList
{
DataType a[N];
int size;// 记录存储多少个有效数据
}SSL;
静态顺序表的增删查改就是数组的移动
静态顺序表的初始化:
void init_SSL(SSL* slt)
{
slt->size = 0;
}
静态顺序表的后插:
void insert_SSL(SSL* slt, DataType x)
{
if (slt->size == N)
{
printf("顺序表是满的!"); exit(1);
}
slt->a[slt->size] = x;
slt->size = slt->size + 1;
}
静态顺序表的打印:
void print_SSL(SSL slt)
{
int i;
if (!slt.size)
printf("\n顺序表是空的!");
else
for (i = 0; i < slt.size; i++)
printf("%5d", slt.a[i]);
}
但由于静态顺序表的N是规定死的,就导致它具有局限性,存储数据满的时候会造成越界,所以静态顺序表不太实用,用的很少,所以这里不展示更多的静态顺序表的算法
2.3动态顺序表
使用动态开辟的数组存储
静态顺序表只适用于确定知道需要存多少数据的场景。
静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。
所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表
2.3.1动态顺序表的定义
typedef struct Seqlist
{
Datatype* a; // 指向动态开辟的数组
int size; // 有效数据个数
int capacity;// 容量空间的大小
}SL;
2.3.2动态顺序表的初始化
注:我们在传递动态顺序表时,不要传结构体变量,而应该传结构体指针
原因:传结构体变量会导致函数内部创建一个新局部结构体变量 ,而初始化完后,局部变量被销毁,形参的改变不会影响实参,所以我们需要传地址
函数接口声明:
void SeqlistInit(SL* phead);
函数接口定义:
void SeqlistInit(SL* phead)
{
assert(phead);//断言空指针
phead->a = NULL;
phead->capacity = phead->size = 0;
}
2.3.3动态循序表的销毁
先释放掉realloc申请的内存
再将指向数组首地址的指针置空
函数接口声明:
void SeqlistDestroy(SL* phead);
函数接口定义:
void SeqlistDestroy(SL* phead)
{
assert(phead);
free(phead->a);
phead->a = NULL;
phead->capacity = phead->size = 0;
}
2.3.4检查动态顺序表空间,满了则增容
在我们进行顺序表的增删查改之前,我们应该想到静态顺序表的缺点,就是不能自动增容顺序表,而在动态顺序表中我们进行这些操作之前需要进行空间的检查
函数接口声明:
void CheckCapacity(SL* phead)
在动态顺序表第一次进行开辟空间时,我们通过realloc函数开辟4个int类型的空间,此后每当容量满时,新容量就扩大二倍
在动态开辟数组时,我们用到realloc函数,realloc函数将参数中的指针进行扩容,但有可能扩容失败返回空指针(扩容内存很大),此时我们需要检查一下
void CheckCapacity(SL* phead)
{
if (phead->size == phead->capacity)
{
int newcapacity = phead->capacity == 0 ? 4 : 2 * phead->capacity;
Datatype* tmp = (Datatype*)realloc(phead->a, newcapacity * sizeof(Datatype));
if (tmp == NULL)
{
perror("realloc fail\n");
exit(-1);
}
phead->a = tmp;
phead->capacity = newcapacity;
}
}
2.3.5动态顺序表的尾插和尾删
尾插:
尾插很简单,就是在顺序表size的位置插入,插入后size++,注意在插入前对动态顺序表进行容量的检查,防止空间溢出
函数接口声明:
void SeqlistPushBack(SL* phead,Datatype x);
函数接口定义:
void SeqlistPushBack(SL* phead, Datatype x)
{
assert(phead);
CheckCapacity(phead);
phead->a[phead->size] = x;
(phead->size)++;
}
尾删:
这里就直接把size-1让顺序表访问不到这个位置就行,注意在尾删前,检查断言指针不为空
函数接口声明:
void SeqlistPopBack(SL* phead);
函数接口定义:
void SeqlistPopBack(SL* phead)
{
assert(phead);
assert(phead->size);
phead->size--;
}
2.3.6动态顺序表的头插和头删
头插:
头插需要先在头插前 先进行顺序表的容量检查,其次需要先移动整个顺序表,把顺序表每个位置的值向后移动一位,然后再在0的位置插入值
函数接口声明:
void SeqlistPushFront(SL* phead, Datatype x);
函数接口定义:
void SeqlistPushFront(SL* phead, Datatype x)
{
assert(phead);
CheckCapacity(phead);
for (int i = phead->size; i > 0; i--)
{
phead->a[i] = phead->a[i - 1];
}
phead->a[0] = x;
phead->size++;
}
头删:
头删则需要先检查顺序表是否为空,然后再把顺序表中1-size-1的数据向前移动一位,最后size再--
函数接口声明:
void SeqlistPopFront(SL* phead);
函数接口定义:
void SeqlistPopFront(SL* phead)
{
assert(phead);
assert(phead->size);
for (int i = 1; i < phead->size; i++)
{
phead->a[i - 1] = phead->a[i];
}
phead->size--;
}
2.3.7在动态顺序表中查找某个数
断言指针不为空
直接遍历顺序表,找到值则返回元素下标,找不到则说明
函数接口声明:
void SeqlistFind(SL* phead, Datatype x);
函数接口定义:
void SeqlistFind(SL* phead, Datatype x)
{
assert(phead);
for (int i = 0; i < phead->size; i++)
{
if (phead->a[i] == x)
{
printf("找到了,是顺序表的第%d个元素\n",i+1);
return;
}
}
printf("没找到,无此数字\n");
}
2.3.8动态顺序表在pos位置插入x
首先判断pos的值小于size,并且进行判空操作
再将pos及pos之后的位置的值向后移动一位,移动前判容量
最后直接将值插在pos位置,size++
函数接口声明:
void SeqlistInsert(SL* phead, size_t pos, Datatype x);
函数接口定义:
void SeqlistInsert(SL* phead, size_t pos, Datatype x)
{
assert(phead);
assert(pos <= phead->size);
CheckCapacity(phead);
size_t end = phead->size;
while (end > pos)
{
phead->a[end] = phead->a[end - 1];
end--;
}
phead->a[pos] = x;
phead->size++;
}
2.3.9动态顺序表删除pos位置的值
首先判断pos的值小于size,并且进行判空操作
再将pos后面的值(不包括pos位置),向前移动一位(这样原本pos位置的值就被覆盖)
再size--
函数接口声明:
void SeqlistErase(SL* phead, size_t pos);
函数接口定义:
void SeqlistErase(SL* phead, size_t pos)
{
assert(phead);
assert(pos < phead->size);
for (int i = pos; i < phead->size-1; i++)
{
phead->a[i] = phead->a[i + 1];
}
phead->size--;
}
2.3.10改变动态顺序表pos位置的值
首先判断pos的值小于size,并且进行判空操作
然后直接修改值即可
函数接口声明:
void SeqlistModify(SL* phead, size_t pos, Datatype x);
函数接口定义:
void SeqlistModify(SL* phead, size_t pos, Datatype x)
{
assert(phead);
assert(pos < phead->size);
phead->a[pos] = x;
}