提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
顺序表
前言
提示:这里可以添加本文要记录的大概内容:
顺序标的基本概念和代码详解
1.基本概念
也就是说顺序表在结构和逻辑上都连续。>>>典型的数组结构
不允许其中有空的地址,未存取数据
2.结构体定义
2.1 顺序表静态存储
静态特点:如果满了就不让插入 缺点:给多少的合适呢?这个很难确定
N给小了不够用,N给大了浪费
2.2 顺序表动态存储
// 动态顺序表
typedef int SLDataType;//如果数据类型不为int,方便随时变更
typedef struct SeqList
{
SLDataType* a; //可以采用relloc扩容,指向存储数据的空间
int size; // 表示数组中存储了多少个数据
int capacity; // 数组实际能存数据的空间容量是多大
}SL;
注意:顺序表都是存储在结构体的SLDataType* a的这份空间中的,有relloc来开辟。
这份空间估且叫A空间吧,A空间在申请时,不够会一次申请n个字节,所以其容量和有效数据的个数不等。决定当前顺序表有多少个有效数据的为结构体的size的值。
3.顺序表的初始化、增删改查、和检测容量并扩容等重要功能。
3.1 顺序表初始化
为什么需要这个呢?这个对于顺序表动态存储来使用的,因为动态可以采用relloc扩容,然后在扩容时(SeqListCheckCapacity()函数中),根据扩容的空间给ps->size和ps->capacity赋值,进入扩容之前,以初始化的 ps->a = NULL; ps->size = ps->capacity = 0;为前提来执行后面的逻辑的。
这样:所有任意长度的数据都可以采用初始化。
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->size = ps->capacity = 0;
}
3.2 顺序表检测容量并扩容
void SeqListCheckCapacity(SL* ps)
{
// 如果没有空间或者空间不足,那么我们就扩容
if (ps->size == ps->capacity)
{
//初始化后,第一次进入时,capacity=size=0
//容量满了,capacity=size 不等于0
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//当第一次进入时,容量赋值为4,之后如果容量不满了,容量扩容为原来的2倍
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity*sizeof(SLDataType));
//realloc申请的地址类型为char* 以字节为单位
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
3.3 顺序表删除
删除数据前需要检测是否无数据。
3.3.1 顺序表指定下标pos删除
//逻辑上认为应该先删除再挪动数据的
//但是其实直接挪动数据,覆盖即可
//删除其实就说size(有效数据)的减小。
void SeqListDelet(SL* ps, int pos)//删除指定pos位置数据
{
assert(ps->size > 0); //需要检测是否无数据。
assert((pos <= ps->size - 1)//可以删除的位置 0<=pos<=size-1
&& (pos>=0)); //&& 且
//挪动数据
int begin = pos;
while (begin<=ps->size-2)//当pos为size-1,尾删时,不会进入循环 头删可以进入循环
{
ps->a[begin] = ps->a[begin+1];
begin++;
}
ps->size--;
}
怎么理解呢?挪动的理解
向前挪动的二种方法:
方法一:
//挪动数据
int begin = pos;
while (begin<=ps->size-2)
{
ps->a[begin] = ps->a[begin+1];
begin++;
}
ps->size--;
方法二:推荐此方法
//挪动数据
int begin = pos+1;
while (begin<=ps->size-1)
{
ps->a[begin-1] = ps->a[begin];
begin++;
}
ps->size--;
3.3.2 顺序表尾部删除
尾部删除无需移动数据
void SeqListPopBack(SL* ps)
{
// 温柔处理方式
//if (ps->size > 0)
//{
// //ps->a[ps->size - 1] = 0;
// ps->size--;
//}
// 暴力处理方式
assert(ps->size > 0);//需要检测是否无数据。
ps->size--;
}
3.3.3 顺序表头部删除
void SeqListPopFront(SL* ps)//逻辑上认为应该先删除再挪动数据的
//但是其实直接挪动数据,覆盖即可
//但是删除其实就说size的减小。
{
assert(ps->size > 0);
/*挪动数据*/
int begin = 0;
while (begin < ps->size-1)
{
ps->a[begin ] = ps->a[begin+1];
++begin;
}
ps->size--;
}
3.4. 顺序表增加数据
都应该在增加数据之前检查一下是否数据满了,
3.4.1 顺序表指定下标插入数据
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
assert(pos>=0 && pos >=ps->size);// 插入数据时 pos范围为 0<=pos<=size
SeqListCheckCapacity(ps);//检查一下是否数据满了
int end = ps->size-1;
while (end >= pos)//当pos为size,尾插时,不会进入循环 执行挪动数据
// 头插可以进入循环
{
ps->a[end +1] = ps->a[end];
--end;
}
ps->a[pos] = x;
ps->size++;
}
为什么pos范围为[0,size]呢?
pos为0时,会把在第0位的数据挤到后面,pos插完在第0位,即头插
pos为size时,直接在尾部插入数据,即尾差
向后挪动二种方法:
方法一:推荐此方法
int end = ps->size-1;
while (end >= pos)
{
ps->a[end +1] = ps->a[end];
--end;
}
ps->a[pos] = x;
ps->size++;
方法二:
int end = ps->size;
while (end >= pos+1)
{
ps->a[end] = ps->a[end-1];
--end;
}
ps->a[pos] = x;
ps->size++;
注意:插入数据和删除数据,的下标范围不一样
插入数据 可以在数据后面插入一个数据, 0<=pos<=size
而且这样弄好之后,初始化之后,size=capacity=0,也都符合这个范围
那既然能在尾部插入,那应该也能在头部插入数据 此时pos应该为-1,
如果这样理解就错了,pos值是指插入数据后,该数据在数组上的位置。
即pos=0,时就头插,会把原来的数据向后挤
删除数据 只能有数据的下标上删除 0<=pos<=size-1
3.4.2 顺序表尾部插入数据
void SeqListPushBack(SL* ps, SLDataType x)
{
SeqListCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
3.4.3 顺序表头部增加数据
void SeqListPushFront(SL* ps, SLDataType x)
{
SeqListCheckCapacity(ps);
// 挪动数据
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
3.5 向前挪动数据和向后挪动数据的区别
3.5.1 a[b]=a[b+1] 增加 向前挪动 减少 a[b]=a[b-1]向后挪动
3.5.2 向前挪动,前面的数据先挪动 向后挪动,后面的数据先挪动
3.6 销毁顺序表
void SeqListDestory(SL* ps)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->size = 0;
}
3.6 打印顺序表中现有数据
void SeqListPrint(SL* ps)
{
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}