1.什么是顺序表
顺序表本质就是数组,就是用数组来存储数据,对数组进行增删查改的管理。
顺序表一般可以分为:
1.静态顺序表:使用已经定好长度的数组
2.动态顺序表:使用动态开辟的数组存储
2.顺序表的实现(动态顺序表)
重点实现动态的顺序表,静态的顺序表实践中没有任何的价值,所以不实现。
2.1顺序表的定义
顺序表结构体的定义:
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* Data;
int size;//记录存储的有效数据的个数
int capacity;//空间的大小
}SL;
将类型进行typedef重定义,方便后面我们想存其他类型的时候可以更换,size的作用是记录有效数据的个数,方便后续的管理,capacity记录空间的大小,当capacity和size相等时就说明存储数据的空间已经满了,需要扩容。
2.2初始化和销毁
1.初始化:
void SLInit(SL* ps)
{
assert(ps);//断言一下指针是否为空
ps->Data = NULL;
ps->size = 0;
ps->capacity = 0;
}
在初始化这部分,将指针设为空,将size和capacity设置为0。参数要用到指针的原因是因为形参的改变不会影响到实参。assert的作用就是断言,当assert的参数为假时就会报错,并且指出代码具体出错的所在行数。
2.销毁:
void SLDestroy(SL* ps)
{
assert(ps);
free(ps->Data);
ps->Data = NULL;
ps->capacity = 0;
ps->size = 0;
}
在顺序表销毁的时候重点就是将堆上申请的空间给释放掉,不然会造成内存泄漏。
2.3扩容:
static void SLCheckCapacity(SL* ps)
{
assert(ps);
if( ps->size == ps->capacity )//扩容
{
if( ps->capacity == 0 )
{
ps->Data = (SLDataType*)malloc(sizeof(SLDataType) * 4);
ps->capacity = 4;
}
else
{
SLDataType* tmp = (SLDataType*)realloc(ps->Data,sizeof(SLDataType) * 2 * ps->capacity);
ps->Data = tmp;
ps->capacity *= 2;
}
if(ps->Data == NULL)//扩容失败,直接终止掉程序
{
perror("SLCheckCapacity failed");
exit(-1);
}
}
}
由于在初始化的时候没有选择在堆上申请空间,所以在扩容部分麻烦了点。扩容最重要的一点就是检查一下扩容是否成功。
2.4尾插,头插:
1.尾插:
尾插可以直接在size的位置插入,如下图中数据5:
插入数据后,size++,代表着有效数据个数增加,如下图:
当size和capacity相等时,说明空间已经满了,需要进行扩容,如下图:
void SLPushBack(SL* ps,SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);//检查容量,是否需要扩容
ps->Data[ps->size] = x;
ps->size++;//增加数据个数
}
2.头插:
在头部的插入需要将数据往后去挪,如下图我们要在0的前面插入数据:
将数据往后挪后,就可以在数组0号下标处插入数据,也就是头插,如下图:
插入数据后的重点就是szie需要增加,如下图插入数据-1后:
当size和capacity相等后也需要进行扩容,从上图可以看出顺序表头插需要数据向后挪动代价是比较大的。
void SLPushFront(SL* ps,SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
int end = ps->size - 1;
while(end >= 0)//重后往前挪
{
ps->Data[end + 1] = ps->Data[end];
end--;
}
ps->Data[0] = x;
ps->size++;
}
2.5尾删,头删
1.尾删:
如果我们想要尾删下图中的数据4,只需要将size--即可
size统计的是有效个数,将size--后尾部的数据就被删除了,我们不需要将数据给设置为0这一步操作,万一尾部的数据就是0或者顺序表如果存的是其他数据呢?所以这一步我们完全是不需要去写的。
尾插的重点就是如果size减成0后我们就不能再去删除了,否则size变成了负数我们再去插入数据就会造成非法访问,如下图:
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size > 0);
ps->size--;
}
2.头删:
如果我们想删除下图中的数据0,需要数据往前挪动。
挪动后最重要的是将size--,如下图
头删完成,如下图:
头删的重点和尾删一样,需要检查一下size数据的有效个数
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
int pos = 0;
while(pos < ps->size)
{
ps->Data[pos] = ps->Data[pos + 1];
pos++;
}
ps->size--;
}
2.6在指定位置插入和删除
1.在指定位置插入:
如果我们想在下图数据中2的位置插入一个10,我们需要将包括2在内的后面所有数据都要向后挪动
挪动完成后就可以插入数据数据了,最后将size++
这样插入数据就完成了
在写代码前需要注意的点:
1.先检查一下容量;
2.检查pos,pos的值必须大于等于0和小于等于size(等于0的时候是头插,等于size是尾插)
void SLInsert(SL* ps,int pos,SLDataType x)
{
assert(ps);
assert(pos <= ps->size && pos >= 0);
SLCheckCapacity(ps);
int end = ps->size - 1;
while(end >= pos)
{
ps->Data[end + 1] = ps->Data[end];
end--;
}
ps->Data[pos] = x;
ps->size++;
}
2.在指定位置删除:
如果想删除下图中的数据2,需要将2之后的数据往前挪
挪完数据后,2就被删除了,之后再让size--
这样删除就完成了
在写代码前需要注意的点:检查pos,pos的值必须小于size,当size为0就不要删了
void SLErase(SL* ps,int pos)
{
assert(ps);
assert(pos < ps->size && ps->size > 0);
int end = ps->size;
int begin = pos;
while (begin < end)
{
ps->Data[begin] = ps->Data[begin + 1];
begin++;
}
ps->size--;
}
2.7查找
查找就是从前向后依次将数据一一对比,如果相等就返回数据所在的下标,找不到就返回-1
int SLFind(SL* ps,SLDataType x)
{
assert(ps);
for(int i = 0; i < ps->size; i++)
{
if(ps->Data[i] == x)
{
return i;
}
}
return -1;
}