SeqList顺序表
**观前提示:**本人才疏学钱,理解能力有限,若有不对的地方,欢迎指正!
1.顺序表概念及结构
在了解顺序表之前我们先了解下线性表。
线性表(linear list) : 是n个具有相同特性的数据元素的有限序列。
线性表 在逻辑结构上是线性结构,在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
常见线性表有: 顺序表、链表、栈、队列、字符串等。
我们之前在使用数组的时候,在某些情景,比如我要删除、添加、等都需要我们去源代码中去修改,或者重新输入很不方便。于是我们把数组封装起来,把它命名为顺序表,在顺序表中有能够实现数组的增、删、查、改等的函数方法。
我们现在知道顺序表是线性表的一种,所以逻辑结构上是连续的,那么物理结构是不是连续的呢? 我们还知道顺序表的底层是数组,数组在物理结构上是连续的,那么顺序表在物理结构上一定也是连续的。
顺序表也是有类别之分的:静态顺序表和动态顺序表。由于我们希望可以自由的增加数据,所以我们这里选择动态顺序表来讲解。
当我们有以上的了解之后,我们来创建一个动态顺序表,来看一下它的结构。
typedef struct SeqList
{
SLDataType* arr;//该指针指向动态开辟的空间其中存储数据
int size; //有效数据的个数
int capacity; //空间容量
}SL; //重新命名是为了将来使用方便,减少打字量
2.动态顺表的实现
首先我们需要一个头文件,一个方法实现文件,一个测试文件。
头文件:
实现方法的说明和包含所有所需要的头文件
#include<stdio.h>
#include<stdlib.h>
//定义动态顺序表的结构
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr;
int size;
int capacity;
}SL;
//初始化
void SLInit(SL* ps);
//尾插
void SLPushBack(SL*ps,SLDataType x);
//头插
void SLPushFront(SL*ps,SLDataType x);
//头删
void SLPopFront(SL*ps);
//尾删
void SLPopBack(SL*ps);
//检查容量
void SLCheckCapacity(SL*ps);
//打印
void SLPrint(SL s);
//销毁
void SLDestroy(SL* ps);
//指定位置插入/删除
void SLInsert(SL* ps,int pos,SLDataType x);
void SLErase(SL* ps,int pos);
//查找
void SLFind(SL* ps,SLDataType x);
方法实现文件:
对顺序表中所需的方法进行实现
#include"SeqList.h"
//初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = 0;
ps->capacity = 0;
}
//销毁
void SLDestroy(SL* ps)
{
if(ps->capacity == 0)
exit(1);
else
{
free(ps->arr);
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
}
//检查容量
void SLCheckCapacity(SL*ps)
{
if(ps->capacity == 0)
{
SLDataType*newCapacity = (SLDataType*)realloc(ps->arr,4 * sizeof(SLDataType));
if(newCapacity==NULL)
{
perror("malloc");
exit(1);
}
else
{
ps->arr = newCapacity;
ps->capacity = 4;
}
}
else if(ps->capacity == ps->size)
{
SLDataType*newCapacity = (SLDataType*)realloc(ps->arr,2*ps->capacity);
if(newCapacity==NULL)
{
perror("malloc");
exit(1);
}
else
{
ps->arr = newCapacity;
ps->capacity *= 2;
}
}
}
//尾插,头插
void SLPushBack(SL*ps,SLDataType x)
{
SLCheckCapacity(ps);
//插入
ps->arr[ps->size] = x;
ps->size++;
}
void SLPushFront(SL*ps,SLDataType x)
{
SLCheckCapacity(ps);
//插入
int i = 0;
for(i=ps->size;i > 0 ;i--)
{
ps->arr[i] = ps->arr[i-1];
}
ps->arr[0] = x;
ps->size++;
}
//尾删,头删
void SLPopBack(SL*ps)
{
ps->size--;
}
void SLPopFront(SL*ps)
{
for(int i=0 ; i<ps->size-1 ; i++)
{
ps->arr[i] = ps->arr[i+1];
}
ps->size--;
}
//顺序表打印
void SLPrint(SL s)
{
for(int i = 0;i < s.size;i++)
{
printf("%d\n",s.arr[i]);
}
}
//指定位置插入/删除
void SLInsert(SL* ps,int pos,SLDataType x)
{
SLCheckCapacity(ps);
for(int i = 0;i<ps->size-pos;i++)
{
ps->arr[ps->size-i] = ps->arr[ps->size-i-1];
}
ps->arr[pos] = x;
ps->size++;
}
void SLErase(SL* ps,int pos)
{
for(int i = 0;i<ps->size-pos-1;i++)
{
ps->arr[pos+i] = ps->arr[pos+i+1];
}
ps->size--;
}
//查找
void SLFind(SL* ps,SLDataType x)
{
int i = 0;
for(;i<ps->size;i++)
{
if(ps->arr[i] == x)
{
printf("找到该数据,下标为%d\n",i);
return;
}
}
if(i == ps->size)
printf("未能找到该数据\n");
}
测试文件:
对顺序表进行测试
void test1()
{
SL s; //首先要创建一个链表,然后再对链表-->增删查改
SLInit(&s);
SLCheckCapacity(&s);
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushFront(&s,9);
SLPopBack(&s);
SLPrint(s);
SLDestroy(&s);
return 0;
}
void test2()
{
SL s1;
SLInit(&s1);
SLPushBack(&s1,1);
SLPushBack(&s1,2);
SLPushBack(&s1,3);
SLPushBack(&s1,4);
SLPrint(s1);
SLFind(&s1,9);
SLDestroy(&s1);
}
int main()
{
//test1();
test2();
}
3.顺序表的缺点
1. 在中间/头部的插入删除,涉及到数据的移动,时间复杂度为O(N)。
2. 增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗。
3. 增加容量一般是2倍的增长,势必会有一些空间的浪费,例如当前容量为100,满了以后增容到200,我们还想继续插入1个数据,后面没有数据插入了,就浪费了99个数据空间。
为解决顺序表的问题,接下来的需要引用链表来解决问题