什么是数据结构
数据结构 是 计算机存储、组织数据的形式。
1)能够存储数据(如顺序表、链表等结构)
2)存储的数据能方便查找
最基础的数据结构——数组
但是对于数据量庞大的情况来说,最基础的数据结构能够提供的操作已经不能完全满足复杂算法的实现
线性表
顺序表
底层结构是数组
通过对数组的封装,实现了常用的增删改查等接口
顺序表分类
1.静态顺序表
概念:使用定长数组存储元素。
//静态顺序表
//用typedef定义数据类型
typedef int SLDataType;
#define M 7
typedef sturct SeqList
{
SLDataType a[M];//定长数组
int size;//有效数据个数
}SL;
缺陷:容量固定,空间给少了不够用,空间给多了浪费。
2.动态顺序表
2.1结构
//动态顺序表——按需申请
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;//动态内存的指针
int size; //有效数据的个数
int capacity; //空间容量
}SL;
以上是动态顺序表的结构
2.2初始化
void SLInit(SL*ps)
{
ps->a=NULL;
ps->capacity=ps->size=0;
}
bool SLIsEmpty(SL*ps)
{
assert(ps);
return ps->size==0;
}
把指针置空
把有效数据个数清零
同时把空间大小初始化。
2.3销毁
void SLDestroy(SL*ps)
{
if(ps->a) //先判断指针下是否动态开辟空间
{
free(ps->a);//将a动态开辟的内存释放
}
ps->a=NULL; //将指针置空
ps->size=ps->capacity=0;//将空间大小和有效数据个数清零
}
容易忘记的是先判断指针下是否开辟动态内存
2.3打印数据
void SLPrint(SL*ps)
{
for(size_t i=0;i<ps->size;i++)
{
printf("%d ",ps->a[i]);
}
printf("\n");
}
2.4扩容
void SLCheckCapapcity(SL*ps)
{
if(ps->size==ps->capacity)
{
//判断空间容量是否已满
//扩容(原容量的两倍 )
int newCapacity =ps->capacity==0?4:2*ps->capacity;
//如果原容量为零就给4个空间,非零就给原容量两倍
//动态开辟内存
SLDataType* tmp=(SLDataType*)realloc(p->a,newCapacity*sizeof(SLDataType));
if(tmp==NULL)
{
perror("realloc fail!");
return 1;
}
ps->a=tmp;
tmp=NULL;
//更新原来空间大小
ps->capacity=newCapacity;
}
}
流程:
判断容量是否满(ps->size==ps->capacity)–>
满就继续–>新空间大小的声明(三目操作符的使用)–>动态内存的扩容(开辟)–>更新ps->capacity.
2.5尾部插入数据
void SLPushBack(SL*ps,SLDataType x)
{
//碰到有关指针的函数,记得断言
assert(ps);
//先检查容量并扩容
SLCheckCapacity(ps);
//然后插入数据
ps->a[size++]=x;
}
//思路很简单,别忘了size++
还有!assert断言!
2.6头部插入数据
void SLPushFront(SL*ps)
{
assert(ps);//还是要断言
SLCheckCapacity(ps);
for(size_t i=ps->size;i>0;i--)
{
ps->a[i]=ps->a[i-1];
}
ps->size++
}
2.7尾部删除数据
void SLPopBack(SL*ps)
{
assert(ps);
//判断顺序表是否为空
assert(!SLIsEmpty(ps));//assert括号为0,不会报错,括号非0,就会报错
ps->size--;
}
----------------------2024.02.21--------------------
----------------------2024.03.02--------------------
2.8头部删除数据
void SLPopBack(SL*ps)
{
//判断指针
assert(ps);
//判断顺序表是否为空
assert(!SLIsEmpty);
for(size_t i;i=0;i<ps->size-1;i++)
{
ps->a[i]=ps->a[i+1];
}
ps->size--;
}
思路很简单,直接覆盖前一个数据就行,不用管最后一个数据的值,因为size–.
2.8在指定位置插入数据
void SLInsert(SL*ps,int pos,SLDatatType x);
这里的pos是指数据的下标
void SLInsert(SL*ps,int pos)
{
//经典assert断言
assert(ps);
assert(pos>=0&&pos<size)
//经典判断空间是否足够
SLCheckEmpty(ps);
//开始插入数据,同时将插入数据下标的原来数据后挪(**后面的数据先挪**,然后依次往前)
for(size_t i=ps->size;i>pos;i--)
{
ps->a[i]=ps->a[i-1];
}
ps->a[pos]=x;
ps->size++
}
容易忘记的地方:插入位置的下标一定要限定好!(最后一个元素的下标是size-1!)
2.9在指定位置删除数据
void SLErase(SL*ps,int pos);
void SLErase(SL*ps,int pos)
{
//经典得不能再经典的assert
assert(ps);
//不要忘记pos的范围控制
assert(pos>=0&&pos<ps->size);
//删除数据的函数,记得检查原来空间**是否没有数据**
assert(!SLIsEmpty(ps));
//开始删除数据(pos位置的**后一个数据**开始往前移动)
for(size_t i=pos;i<size-1;i++)
{
p->a[i]=p->a[i+1];
}
ps->size--;
}
注意i的取值范围
2.10顺序表的小总结
以上是通过以数组为底层,实现动态顺序表初始化、销毁、扩容、增加数据、删除数据的的顺序表实现。
其中重点
-
Check函数
-
assert断言的使用
-
size的更新
-
删除数据记得判断是否空间没有数据
-
i的下标
---------------------顺序表完结--------------------