数据结构-顺序表
0.前言
> 在数据结构的世界里,顺序表作为最基本的一种线性数据结构,广泛应用于各种场景。它通过连续的存储空间来存储元素,操作简单、效率高,是理解和掌握更复杂数据结构的基础。 本篇文章将带你深入了解顺序表的定义、特点以及常见操作,帮助你打下扎实的编程基础。 |
1.概念及结构
1.1 基础概念
概念:顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
静态顺序表:使用定长数组存储元素。
动态顺序表:使用动态开辟的数组存储。
1.2 顺序表结构
静态:
动态:
2.实现逻辑
对于顺序表的实现来说,基础实现就是 “增删查改”
2.1 增删查改函数声明
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define INIT_CAPACITY 4
typedef int SLDataType;
//动态顺序表---按需申请
typedef struct SeqList
{
int size; //有效数据个数
int capacity;//空间容量
SLDataType* a;
}SL;
//增删查改
void SLInit(SL* ps);
void SLDestory(SL* ps);
//尾插,尾删
void PushBack(SL* ps,SLDataType x);
void PopBack(SL* ps);
//头插,头删
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
//显示
void SLPrint(SL* ps);
//检查容量
void SLCheckcapacity(SL* ps);
//某个位置插入/删除
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
//查找
int SLFind(SL* ps, SLDataType x);
以上都是顺序表逻辑所需要实现的基础函数
2.2 函数逻辑实现
2.2.1 初始化
//初始化
void SLInit(SL* ps)
{
ps->a = (SLDataType*)malloc(sizeof(SLDataType) * INIT_CAPACITY);
if (ps->a == NULL)
{
perror("malloc fail");
return;
}
ps->size = 0;
ps->capacity = INIT_CAPACITY;
}
2.2.2 销毁
//销毁
void SLDestory(SL* ps)
{
free(ps->a);//前面申请空间a
ps->a = NULL;
ps->capacity = ps->size = 0;
}
2.2.3 尾插
//尾插
void PushBack(SL* ps, SLDataType x)
{
/*ps->a[ps->size] = x;
ps->size++;*/
//扩容
void SLCheckcapacity(ps);
SLInsert(ps, ps->size, x);
//ps->a[ps->size++] = x;
}
2.2.4 尾删
//尾删
void PopBack(SL* ps)
{
// ps->a[ps->size - 1] = 0;
//暴力检查
assert(ps->size>0);
//温柔检查
//if (ps->size == 0)
//{
// return 0;
//}
ps->size--;
return 0;
}
2.2.5 头插
//头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLInsert(ps, 0, x);
//注意可能性需要扩容,防止越界
//SLCheckcapacity(ps);//扩容
//int end = ps->size - 1;
//while (end >= 0)
//{
// ps->a[end + 1] = ps->a[end];
// --end;
//}
}
2.2.6 头删
//头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);//如果表为空,报错
int begin = 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
++begin;
}
ps->size--;//数量减少
}
2.2.7 扩容
//扩容
void SLCheckcapacity(SL* ps)
{
assert(ps);
//扩容
if (ps->size == ps->capacity)
{
//原地扩容?
//异地扩容?
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->capacity *= 2;
ps->a = tmp;
}
}
2.2.8 某个位置插入
//某个位置插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckcapacity(ps);
//插入
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
2.2.9 某个位置删除
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin] = ps->a[begin - 1];
begin++;
}
ps->size--;
}
2.2.10 查找
//查找
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] = x)
{
return i;
}
}
return -1;
}
3.顺序表问题思考
中间/头部的插入删除,时间复杂度为O(N)
增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?
下一篇博客文章,用链表的结构解决实现。
感谢您的阅读支持!!!
后续会持续更新的!!!
文末投票支持一下!!!