一、顺序表的概念及特点(优缺点)
顺序表是一种线性结构,由一组连续的内存空间依次存储数据元素,其每一个元素在内存中的物理地址相邻。顺序表具有以下特点:
-
容量固定:顺序表在创建时就需要指定容量,无法动态扩容或缩容;
-
索引访问:顺序表中的元素可以直接通过索引访问,时间复杂度为 O(1);
-
插入删除开销较大:在顺序表中插入或删除元素需要移动其他元素,时间复杂度为 O(n);
-
适合大量随机访问:由于元素在内存中的存储位置是连续的,所以适合进行大量的随机访问操作。
顺序表可以用数组来实现,每个元素在数组中的下标就是该元素在顺序表中的索引。
二、顺序表的存储
顺序表的存储方式可以分为 静态存储 和 动态存储 两种。
静态存储:即预先给其分配适当的空间,但在以后得程序中不可更改。若想更改,可以通过宏定义的方式定义一个变量,将其作为数组的大小,在之后可以通过改变这个宏变量的方式改变数组的大小。
动态存储:即不预先给数组分配空间,在插入数据时判断数组是否还有剩余空间(或者最初数组是否已经分配空间),在酌情动态给数组分配适当空间。
1.顺序表的静态存储
#define N 7
typedef int SLDataType;
typedef struct SeqList
{
SLDataType array[N]; // 定长数组
size_t size; // 有效数组的个数
}SL;
2.顺序表的动态存储
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a; // a是指向动态开辟的数组的指针
int size; // 有效数据个数
int capacity; // 空间容量
}SL;
静态存储存在明显的缺点:
不确定事先要给多大空间--(给大浪费,给小不够)。
而动态存储能够很好的解决这个问题,所以更推荐动态存储的存储方式。
在接下来我们也是用动态的存储方式来实现顺序表的一些简单操作。
三、顺序表的简单操作
直接看函数声明 ↓ (增删查改)
// 顺序表的初始化
void SlInit(SL* ps1);
// 顺序表的销毁
void SLDestroy(SL* ps1);
// 打印顺序表
void SLPrint(SL* ps1);
// 检查数组空间
void SLCheckCapacity(SL* ps1);
// 尾插
void SLPushBack(SL* ps1, SLDataType x);
// 头插
void SLPushFront(SL* ps1, SLDataType x);
// 尾删
void SLPopBack(SL* ps1);
// 头删
void SLPopFront(SL* ps1);
// 指定位置插、删
void SLInsert(SL* ps1, int pos, SLDataType x);
void SLErase(SL* ps1, int pos);
// 查找
int SLFind(SL* ps1, SLDataType x);
四、顺序表功能的实现
1.初始化
void SLInit(SL* ps1)
{
assert(ps1); // 断言:判断指向结构体的指针是否为空指针
ps1->a = NULL; // 指向数的组指针初始化为NULL
ps1->size = 0; // 数组已经存储的数据个数 初始置为 0
ps1->capacity = 0; // 数组的大小 初始值置为 0
}
2.插入数据
在插入数据前,要先检查数组是否还有剩余空间 --- SLCheckCapacity
void SLCheckCapacity(SL* ps1)
{
assert(ps1); // 同理,检查结构体指针是否为空,若为空则直接报错
if (ps1->size == ps1->capacity) // 判断数组是否已经存满了
{
/*
这里预先定义一个整形变量 newCapacity,用来将要开辟的新空间的大小(其实就是数组的
类型的几倍)
*/
int newCapacity = ps1->capacity == 0 ? 4 : ps1->capacity * 2;
// 这里用 realloc 开辟空间
SLDataType* tmp = (SLDataType*)realloc(ps1->a, sizeof(SLDataType) * newCapacity);
if (tmp == NULL) // 检查空间开辟成功了没
{
perror("realloc fail");
return;
}
ps1->a = tmp; // 把新空间给 数组
ps1->capacity = newCapacity; // 更新数组大小
}
}
然后在插入的函数里直接调用 SLChekCapacity 即可 ↓
尾插 --- SLPushBack:
// 尾插
void SLPushBack(SL* ps1, SLDataType x)
{
assert(ps1); // 同上
SLCheckCapacity(ps1); // 直接调用检查数组空间的函数
ps1->a[ps1->size] = x; // ps1->size 就是数组的最后一个数据的下标+1,所以直接在size处插入数据就可以了
ps1->size++; // 数据插进来后,要将size更新
}
头插 --- SLPushFront:
// 头插
void SLPushFront(SL* ps1, SLDataType x)
{
assert(ps1);
SLCheckCapacity(ps1);
//挪动数据(把数据挨个向后移一位)
int end = ps1->size - 1; // end 标记下一个要挪动的数据
while (end >= 0)
{
ps1->a[end + 1] = ps1->a[end]; // 这里要注意:向后挪动数据要从最后一位开始从后往前挪
--end;
}
ps1->a[0] = x; // 在数组的下标0处插入数据,因为前面已经将数据挨个向后移动一位了,所以此时 下标0 处是空着的
ps1->size++; // 更新size
}
3.删除数据
尾删 --- SLPopBack:
// 尾删
void SLPopBack(SL* ps1)
{
assert(ps1);
// 暴力检查 -- 检查顺序表是否为空
assert(ps1->size > 0);
// 顺序表的删除数据特别简单,只需要把数组存储的数据的个数减一下就好了
ps1->size--;
}
头删 --- SLPopFront:
// 头删
void SLPopFront(SL* ps1)
{
//暴力检查
assert(ps1->size > 0);
int begin = 1;
// 将数据挨个向前移,直到把第一个数据覆盖
while (begin < ps1->size)
{
ps1->a[begin - 1] = ps1->a[begin];
begin++;
}
ps1->size--; // 跟新size
}
4.查找
// 查找指定数据,返回下标
int SLFind(SL* ps1, SLDataType x)
{
assert(ps1);
for (int i = 0; i < ps1->size; i++) // 循环遍历数组 匹配 目标值,找到返回下标
{
if (ps1->a[i] == x)
{
return i;
}
}
return -1;
}
有了 SLFind 函数,就可以先查找数据,然后再用 SLErase 删除指定数据了
5.删除指定位置的数据
void SLErase(SL* ps1, int pos)
{
assert(ps1);
assert(pos >= 0 && pos < ps1->size); // 判断 指定位置的合理性
// 挪动覆盖, 同头删类似,将指定位置后面的数据挨个向前移一位
int begin = pos;
while (begin < ps1->size)
{
ps1->a[begin - 1] = ps1->a[begin];
begin++;
}
ps1->size--;
}
6.在指定位置插入数据
void SLInsert(SL* ps1, int pos, SLDataType x)
{
assert(ps1);
assert(pos >= 0 && pos <= ps1->size); // 判断 下标 pos 的合理性
SLCheckCapacity(ps1);
//挪动数据, 同头插类似
int end = ps1->size - 1;
while (end >= pos)
{
ps1->a[end + 1] = ps1->a[end];
--end;
}
ps1->a[pos] = x;
ps1->size++;
}
7. 打印顺序表
// 打印
// 循环遍历数组,把数据挨个printf一下
void SLPrint(SL* ps1)
{
assert(ps1);
for (int i = 0; i < ps1->size; i++)
{
printf("%d ", ps1->a[i]);
}
printf("\n");
}
8.销毁顺序表
最后,在退出程序时要把结构体内容重新置为空并释放掉内存。
// 销毁
void SLDestroy(SL* ps1)
{
assert(ps1);
if (ps1->a != NULL) // 判断数组是否为空
{
free(ps1->a); // 释放掉分配给数组的空间
ps1->a = NULL; // 将结构体的内容置空
ps1->size = 0;
ps1->capacity = 0;
}
}
至此,顺序表的一些简单操作就都实现啦。
总代码 ↓
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;
int size; // 有效数据个数
int capacity; // 空间容量
}SL;
void SlInit(SL* ps1);
void SLDestroy(SL* ps1);
void SLPrint(SL* ps1);
void SLCheckCapacity(SL* ps1);
// 尾插
void SLPushBack(SL* ps1, SLDataType x);
// 头插
void SLPushFront(SL* ps1, SLDataType x);
// 尾删
void SLPopBack(SL* ps1);
// 头删
void SLPopFront(SL* ps1);
// 指定位置插、删
void SLInsert(SL* ps1, int pos, SLDataType x);
void SLErase(SL* ps1, int pos);
// 查找
int SLFind(SL* ps1, SLDataType x);
void SLInit(SL* ps1)
{
assert(ps1);
ps1->a = NULL;
ps1->size = 0;
ps1->capacity = 0;
}
void SLDestroy(SL* ps1)
{
assert(ps1);
if (ps1->a != NULL)
{
free(ps1->a);
ps1->a = NULL;
ps1->size = 0;
ps1->capacity = 0;
}
}
void SLPrint(SL* ps1)
{
assert(ps1);
for (int i = 0; i < ps1->size; i++)
{
printf("%d ", ps1->a[i]);
}
printf("\n");
}
void SLCheckCapacity(SL* ps1)
{
assert(ps1);
if (ps1->size == ps1->capacity)
{
int newCapacity = ps1->capacity == 0 ? 4 : ps1->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps1->a, sizeof(SLDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps1->a = tmp;
ps1->capacity = newCapacity;
}
}
void SLPushBack(SL* ps1, SLDataType x)
{
assert(ps1);
SLCheckCapacity(ps1);
ps1->a[ps1->size] = x;
ps1->size++;
}
void SLPushFront(SL* ps1, SLDataType x)
{
assert(ps1);
SLCheckCapacity(ps1);
//挪动数据(把数据挨个向后移一位)
int end = ps1->size - 1;
while (end >= 0)
{
ps1->a[end + 1] = ps1->a[end];
--end;
}
ps1->a[0] = x;
ps1->size++;
}
void SLPopBack(SL* ps1)
{
assert(ps1);
//温柔检查
/*if (ps1->size == 0)
{
return;
}*/
//暴力检查
assert(ps1->size > 0);
ps1->size--;
}
void SLPopFront(SL* ps1)
{
//暴力检查
assert(ps1->size > 0);
int begin = 1;
while (begin < ps1->size)
{
ps1->a[begin - 1] = ps1->a[begin];
begin++;
}
ps1->size--;
}
void SLInsert(SL* ps1, int pos, SLDataType x)
{
assert(ps1);
assert(pos >= 0 && pos <= ps1->size);
SLCheckCapacity(ps1);
//挪动数据
int end = ps1->size - 1;
while (end >= pos)
{
ps1->a[end + 1] = ps1->a[end];
--end;
}
ps1->a[pos] = x;
ps1->size++;
}
void SLErase(SL* ps1, int pos)
{
assert(ps1);
assert(pos >= 0 && pos < ps1->size);
// 挪动覆盖
int begin = pos;
while (begin < ps1->size)
{
ps1->a[begin - 1] = ps1->a[begin];
begin++;
}
ps1->size--;
}
int SLFind(SL* ps1, SLDataType x)
{
assert(ps1);
for (int i = 0; i < ps1->size; i++)
{
if (ps1->a[i] == x)
{
return i;
}
}
return -1;
}
// TestSL 是测试功能函数
void TestSL1()
{
SL s1;
SLInit(&s1);
SLPushBack(&s1, 1);
SLPushBack(&s1, 2);
SLPushBack(&s1, 3);
SLPushBack(&s1, 4);
SLPushBack(&s1, 5);
SLPushBack(&s1, 6);
SLPushBack(&s1, 7);
SLPushBack(&s1, 8);
SLPushBack(&s1, 9);
SLPrint(&s1);
SLPushFront(&s1, 10);
SLPushFront(&s1, 20);
SLPushFront(&s1, 30);
SLPushFront(&s1, 40);
SLPrint(&s1);
SLDestroy(&s1);
}
void TestSL2()
{
SL s1;
SLInit(&s1);
SLPushBack(&s1, 1);
SLPushBack(&s1, 2);
SLPushBack(&s1, 3);
SLPushBack(&s1, 4);
SLPushBack(&s1, 5);
SLPrint(&s1);
SLPopBack(&s1);
SLPrint(&s1);
SLDestroy(&s1);
}
void TestSL3()
{
SL s1;
SLInit(&s1);
SLPushBack(&s1, 1);
SLPushBack(&s1, 2);
SLPushBack(&s1, 3);
SLPushBack(&s1, 4);
SLPushBack(&s1, 5);
SLPrint(&s1);
SLPopFront(&s1);
SLPopFront(&s1);
SLPopFront(&s1);
SLPopFront(&s1);
SLPrint(&s1);
}
void TestSL4()
{
SL s1;
SLInit(&s1);
SLPushBack(&s1, 1);
SLPushBack(&s1, 2);
SLPushBack(&s1, 3);
SLPushBack(&s1, 4);
SLPushBack(&s1, 5);
SLPrint(&s1);
SLInsert(&s1, 4, 10);
SLPrint(&s1);
SLErase(&s1, 2);
SLPrint(&s1);
int pos = SLFind(&s1, 3);
if (pos != -1)
{
SLErase(&s1, pos+1);
}
SLPrint(&s1);
}
// 主函数
int main()
{
//TestSL1();
//TestSL2();
//TestSL3();
TestSL4();
return 0;
}