线性表(linear list)是n个具有相同特征的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表,链表,栈,队列,字符串
线性表在逻辑上是线性结构,也就说是连续的一条直线。但在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储
顺序表
概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表与数组的关系:
对数组而言:
数组的元素是可以随机存放的,因为:数组通过下标访问是可以任意存储的
对于顺序表而言:
顺序表是要连续存放的,这个连续存放,不是指的是值连续,而是空间连续(意思是你存放的时候,不能挑着存,必须从0下标开始,依次向后存放)
简单地讲:
顺序表不能随意存储,顺序表必须从0下标开始,依次向后存放,意思就是它是顺序存储的,不会出现空一个位置的情况。
顺序表一般可以分为:动态和静态顺序表,
动态顺序表:也是基于数组实现的,只不过它可以实现扩容,而数组是固定长度大小的
定义静态顺序表:
#define N 7
Typedef int SLDataType;//这个地方可以避免写死类型是int,以后想换就换
Typedef struct SeqList
{
SLDatatype array[N];//为了避免写死数组大小,需要改的时候到处改,那就使用宏定义
int size;
};
2. 动态顺序表:使用动态开辟的数组存储
Typedef int SLDatatype;//这个位置
Typedef struct SeqList
{
SLDatatype* a;//指向动态开辟的数组
Int size;//存储的有效数据的个数
Int capacity;//容量空间的大小
}SL;//类型名重定义
- 接口实现
- 初始化:SLInit
Void SLInit(SL* psl); 动态顺序表初始化
Void SLInit(SL* psl)
{
psl->a=(SLDatatype*)malloc(sizeof(SLDatatype)*4);
if(psl->a==NULL)
{
perror("malloc fail");
return;
}
PSL->capacity=4;//使用结构体指针指向访问对象的成员,当前容量
psl->size=0;//当前有效信息个数
}
2. 销毁:SLDestroy
SLDestory(SL* psl);//顺序表销毁
void SLDestory(SL* psl)
{
free(psl->a);
psl->=NULL;
psl->size=0;
psl->capacity=0;
}
3. 检查容量:SLCheckCapacity
realloc用法说明:
void* realloc(void* ptr, size_t size);
-
如果新的大小大于原来的大小,
realloc
会尝试扩展内存块。 -
如果当前内存块后面有足够的空闲空间,
realloc
会直接在原地扩展内存块。 -
如果没有足够的空闲空间,
realloc
会分配一个新的内存块,并将旧内存块中的数据复制到新内存块中。
-
如果
realloc
失败,返回NULL
,但原始内存块仍然有效。 -
因此,应该先用一个临时指针接收
realloc
的返回值,检查是否成功后再赋值给原指针。
void SLCheckCapacity(SL* psl)
{
if(psl->size==psl->capacity)
{
SLDatatype* tmp=(SLDatatype*)realloc(psl->a,sizeof(SLDatatype)*psl->capacity*2);
if(tmp==NULL)
{
perror("realloc fail");
return;
}
psl->a=tmp;
psl->capacity*=2
}
}
4. 顺序表打印SLPrint
void SLPrint(SL* psl)
{
for (int i = 0; i < psl->size; i++)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
5. 尾部插入:SLPushBack
void SLPushBack(SL* psl, SLDatatype x)
{
assert(psl);
SLCheckCapacity(psl);//容量初始值为4,检查当前容量,不够则扩容
psl->a[psl->size++] = x;
}
6. 头部插入:SLPushFront
void SLPushFront(SL* psl, SLDatatype x)
{
assert(psl);//应对可能传过来的NULL指针,而且好处是直接告诉你哪个文件的哪一行出问题
SLCheckCapacity(psl);
//挪动数据
int end = psl->size-1;
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[0] = x;
psl->size++;
}
7.尾部删除:SLPopBack
//尾删
void SLPopBack(SL* psl)
{
assert(psl);
//暴力检查
assert(psl->size > 0);
psl->size--;
}
8. 头部删除:SLPopFront
void SLPopFront(SL* psl)//从后往前
{
assert(psl);//应对可能传过来的NULL指针,而且好处是直接告诉你哪个文件的哪一行出问题
//暴力检查
assert(psl->size > 0);
int start = 0;
while (start < psl->size - 1)
{
psl->a[start] = psl->a[start + 1];
start++;
}
psl->size--;
}
9. 中间插入:SLInsert
//中间插入:首先要看容量,调用检查容量的函数,接着挪动
void SLInsert(SL* psl,int pos,SLDatatype x)
{
assert(psl);//应对可能传过来的NULL指针,而且好处是直接告诉你哪个文件的哪一行出问题
//assert(0 <= pos && pos <= psl->size);//说明这个位置可以是顺序表的头和尾
SLCheckCapacity(psl);
int end = psl->size - 1;
while (end >= pos)
{
psl->a[end + 1] = psl->a[end];
--end;
}
psl->a[pos] = x;
psl->size++;
}
10. 中间删除:SLErase
void SLErase(SL* psl, int pos)//从中间删除元素,也可以是首或者尾
{
assert(psl);//应对可能传过来的NULL指针,而且好处是直接告诉你哪个文件的哪一行出问题
assert(0 <= pos && pos < psl->size);//说明这个位置可以是顺序表的头和尾
//assert(psl->size>0);//这句代码也可以不加,因为
int start = pos + 1;
while (start < psl->size)
{
psl->a[start - 1] = psl->a[start];
++start;
}
psl->size--;
}
11. 查找:SLFind
int SLFind(SL* psl, SLDatatype x)//查找
{
assert(psl);//应对可能传过来的NULL指针,而且好处是直接告诉你哪个文件的哪一行出问题
for (int i = 0; i < psl->size; i++)
{
if (psl->a[i] == x)
{
return i;//返回要查找的元素的下标
}
}
return -1;//走完一遍了还没找到就返回-1,因为下标不可能是负数
}
12. 修改:SLModify
void SLModify(SL* psl, int pos, SLDatatype x)
{
assert(psl);//应对可能传过来的NULL指针,而且好处是直接告诉你哪个文件的哪一行出问题
assert(0 <= pos && pos < psl->size);
psl->a[pos] = x;
}
int removeElement(int* nums, int numsSize, int val) {
int src = 0;
int dst = 0;
while(src<numsSize)
{
if(nums[src]!=val)
{
nums[dst++]=nums[src++];
}
else
{
src++;
}
}
return dst;
}