目录
1.线性表
线性表:是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表,链表,栈,队列,字符串
线性表在逻辑上是线性结构,但在物理结构上不一定是连续的;线性表在物理上存储时,通常以数组和链式结构的形式存储
2.顺序表
2.1顺序表的概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,并在数组上完成数据的增删查改
顺序表一般可分为:
1️⃣静态顺序表:使用定长数组存储元素
2️⃣动态顺序表:使用动态开辟的数组存储
实现顺序表时,我们采用的是动态的顺序表
2.2顺序表的实现
顺序表初始化
可以看到初始化函数的形参是一个指针类型,因为我们要对顺序表结构中的结构成员进行增删查改,所以需要传址调用
//顺序表初始化
void SLInit(SL* psl)
{
assert(psl);//避免空指针的访问
psl->a = NULL;
psl->capacity = psl->size = 0;
}
顺序表销毁
当创建一个顺序表并增删查改后,其结构如下图
销毁顺序表时,不仅要释放psl指向的结构,其结构成员array指向的数组占用的内存也要释放
📖Note:
结构本身不占用内存空间,只有使用结构创建结构变量时才占用内存,psl是我们创建的结构变量
//顺序表销毁
void SLDestroy(SL* psl)
{
assert(psl);
//如果顺序表不为空,进行内存释放
if (psl->a)
{
free(psl);
psl->a = NULL;
psl->capacity = psl->size = 0;
}
}
顺序表打印
采用for循环打印有效数据即可
//顺序表打印
void SLPrint(SL* psl)
{
assert(psl);
int i = 0;
for (i = 0; i < psl->size; i++)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
首先,插入数据时无论尾插,头插还是指定位置插入,都需要检查顺序表的容量,因为创建的是动态顺序表,所以检查容量发现顺序表已满时,需要为顺序表重新开辟空间
检查容量函数
为了内存的有效使用,第一次动态开辟尽量不要开辟太大的空间,这里我们第一次开辟4块空间,第二次动态开辟时,扩容2倍
为什么扩容2倍?
解:扩容时,扩多了存在空间浪费,扩少了会导致频繁扩容,降低效率,所以扩2倍比较合适
newcapacity是空间容量的大小,每块空间存储的是一个SLDataType类型的数据,所以使用realloc函数扩容时,需要的空间应该为newcapacity*sizeof(SLDataType)
尾插数据:
因为顺序表采用数组存储,所以可以直接通过下标访问顺序表中的元素
顺序表的有效数据为size个,尾插数据即在a[size]位置插入
每次成功插入数据后,记录顺序表有效数据元素个数的变量size要+1
//检查容量
void SLCheckCapacity(SL* psl)
{
if (psl->size == psl->capacity)
{
//第一次开辟4块空间,否则开辟原来的两倍
int newcapacity = (psl->capacity == 0) ? 4 : (psl->capacity) * 2;
SLDataType* tmp = (SLDataType*)realloc(psl->a, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
psl->a = tmp;
psl->capacity = newcapacity;
}
}
//尾插
void SLPushBack(SL* psl, SLDataType x)
{
assert(psl);
//检查容量
SLCheckCapacity(psl);
//尾插
psl->a[psl->size] = x;
psl->size++;
}
首先,删除数据时无论尾删,头删还是指定位置删除,都需要检查顺序表的容量,如果顺序表为空则不能进行删除操作
尾删
如下代码所示,有两种方法检查顺序表是否为空,较常用的是检查2
删除元素只需要有效数据元素个数减一即可,这样访问不到a[size]位置的元素即已经删除
//尾删
void SLPopBack(SL* psl)
{
assert(psl);
//检查:如果顺序表为空则不能进行删除
//检查1:
/*if (psl->size == 0)
{
return;
}
psl->size--;*/
//检查2:顺序表为空则报错
assert(psl->size > 0);
psl->size--;
}
头插
头插需要先检查顺序表的容量
对于数组结构,头插数据需要挪动数据,较高效的挪动方法是从后向前依次挪动,如下图:
插入数据后,记录顺序表有效数据元素个数的变量size要+1
//头插
void SLPushFront(SL* psl, SLDataType x)
{
assert(psl);
//检查容量
SLCheckCapacity(psl);
//头插需要挪动数据,从后向前依次挪动
int end = psl->size - 1;
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
end--;
}
psl->a[0] = x;
psl->size++;
}
头删
头删需要先检查顺序表的容量,顺序表为空则不能进行删除
头删步骤:
1️⃣删除a[0]位置的元素
2️⃣后续元素从前向后依次向前挪动
实际上步骤1和2可以合并为,从第二个元素开始依次覆盖前一个元素
删除数据后,记录顺序表有效数据元素个数的变量size要-1
//头删
void SLPopFront(SL* psl)
{
assert(psl);
//检查:如果顺序表为空则不能进行删除
assert(psl->size > 0);
//从前向后依次向前挪动,覆盖前一个数据即可
int begin = 0;
while (begin<psl->size -1)
{
psl->a[begin] = psl->a[begin + 1];
begin++;
}
psl->size--;
}
查找
使用for循环遍历数组,找到符合要求的数据则返回下标,遍历未找到则返回-1
//查找
int SeqListFind(SL* psl, SLDataType x)
{
assert(psl);
int i = 0;
for (i = 0; i < psl->size; i++)
{
if (psl->a[i] == x)
{
return i;
}
}
//遍历后未找到
return -1;
}
在指定位置处插入元素
首先要对指定的位置的值进行检查,如果超出数组下标则报错
指定位置插入元素需要检查容量,调用检查容量函数即可
在pos处插入数据需要挪动数据,从pos开始向后的数据需要依次向后挪动一位,如下图:
插入数据后,记录顺序表有效数据元素个数的变量size要+1
//在pos处插入一个元素
void SeqListInsert(SL* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(pos <= psl->size);
//检查容量
SLCheckCapacity(psl);
//pos之后的所有元素向后移动,从后向前依次移动
//pos是size_t类型,防止整型提升导致不能进行头插,end也应定义为size_t类型
size_t end = psl->size;
while (end > pos)
{
psl->a[end] = psl->a[end - 1];
end--;
}
psl->a[pos] = x;
psl->size++;
}
删除指定位置处元素
删除指定位置pos处的元素,从pos+1位置开始,依次向后,每个元素覆盖前一个元素即可
删除数据后,记录顺序表有效数据元素个数的变量size要-1
//删除pos处的元素
void SeqListErase(SL* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);
size_t begin = pos;
//从pos+1开始,从前向后依次覆盖前一个数据
while (begin < psl->size - 1)
{
psl->a[begin] = psl->a[begin + 1];
begin++;
}
psl->size--;
}
修改指定位置处的元素
因为数组可以通过下标访问,所以当已知位置时,可以通过下标直接修改pos处的元素
//修改pos处的数据
void SLModify(SL* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(pos < psl->size);
psl->a[pos] = x;
}