一、顺序表的含义
顺序表是什么?
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。但是静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。
顺序表底层是怎样的,首先是data用来存储数据,capacity表示data的内存大小,size表示存储了多少的数据,所以使用struct定义出来的结构如下
typedef int SLDataType;//将变量类型重定义方便修改变量类型而不用重新修改;
typedef struct SeqList
{
SLDataType *data;//存储数据
int capacity;//表示顺序表的存储容量
int size;//表示该顺序表存储了多少数据
}SeqList;
二、顺序表的基本操作
1.顺序表的初始化
void SeqListInit(SeqList *psl)
{
psl->data = NULL;//将存储变量的指针初始化为0
psl->capacity = psl->size = 0;//将容量和存储的数据多少初始化为0
}
2.顺序表的扩容
void CheckCapacity(SeqList *psl)
{
if (psl->capacity == psl->size)
{
int newcapacity = psl->capacity == 0 ? 4 : 2 * psl->capacity;//使用三元表达式来判断是第一次扩容,如果不是第一次扩容就是二倍扩
int *newdata = realloc(psl->data, sizeof(int) * newcapacity);//使用realloc函数如果是空就会像malloc一样扩容
if (newdata == NULL)
{
perror("reallac errno ");
exit(-1);
}
psl->data = newdata;
psl->capacity = newcapacity;
}
}
3.顺序表的尾插
void SeqListPushBack(SeqList *psl, SLDataType x)
{
CheckCapacity(psl);//检查是否空间足够,如果不够就扩容
psl->data[psl->size] = x;
psl->size++;
}
4.顺序表的尾删
void SeqListPopBack(SeqList *psl)
{
assert(psl->size > 0);//防止顺序表内没有数据还在删除
psl->size--;
}
5.顺序表的头插
void SeqListPushFront(SeqList *psl, SLDataType x)
{
CheckCapacity(psl);//检查空间是否有剩余
int end = psl->size;
while (end > 0)
{
psl->data[end] = psl->data[end - 1];//将数据向后挪动
end--;
}
psl->data[end] = x;//将数据在头部插入
psl->size++;
}
6.顺序表的头删
void SeqListPopFront(SeqList *psl)
{
assert(psl->size > 0);//防止顺序表为空
int begin = 1;
while (begin < psl->size)
{
psl->data[begin - 1] = psl->data[begin];//数据挪动覆盖
begin++;
}
psl->size--;
}
7.顺序表的查找
int SeqListFind(SeqList *psl, SLDataType x)
{
for (int i = 0; i < psl->size; i++)
{
if (psl->data[i] == x)
{
return x;
}
}
return -1;
}
8.顺序表的任意位置插入
void SeqListInsert(SeqList *psl, size_t pos, SLDataType x)
{
assert(pos >= 0 && pos <= (int)(psl->size));
CheckCapacity(psl);
int end = psl->size + 1;
while (end >= (int)pos)//这里使用强转因为size_t是unsigned long ,比较会因为发生整型提升,出现错误
{
psl->data[end] = psl->data[end - 1];//挪动数据
end--;
}
psl->data[pos] = x;//在pos位置插入
psl->size++;
}
9.顺序表的任意位置删除
void SeqListErase(SeqList *psl, size_t pos)
{
assert((int)pos >= 0 && pos < (int)(psl->size));
int begin = (int)(pos);这里使用强转因为size_t是unsigned long ,在后面的判断比较会因为发生整型提升,出现错误
int end = psl->size;
while (begin < end)
{
psl->data[begin] = psl->data[begin + 1];//挪动数据覆盖
begin++;
}
psl->size--;
}
10.测试用例
#include "SeqList.h"
void testpushback()
{
SeqList l;
SeqListInit(&l);
SeqListPushBack(&l, 10);
SeqListPushBack(&l, 10);
SeqListPushBack(&l, 10);
SeqListPushBack(&l, 10);
SeqListPushBack(&l, 10);
SeqListPrint(&l);
SeqListPopBack(&l);
SeqListPopBack(&l);
SeqListPopBack(&l);
SeqListPopBack(&l);
SeqListPrint(&l);
}
void testpushfront()
{
SeqList seq;
SeqListInit(&seq);
SeqListPushFront(&seq, 10);
SeqListPushFront(&seq, 205);
SeqListPushFront(&seq, 100);
SeqListPushFront(&seq, 12);
SeqListPushFront(&seq, 127);
SeqListPrint(&seq);
int ret = SeqListFind(&seq, 12);
printf("%d\n", ret);
SeqListPopFront(&seq);
SeqListPopFront(&seq);
SeqListPopFront(&seq);
SeqListPopFront(&seq);
SeqListPopFront(&seq);
SeqListPrint(&seq);
}
void testin_ear()
{
SeqList seq;
SeqListInit(&seq);
SeqListInsert(&seq, seq.size, 1);
SeqListInsert(&seq, seq.size, 2);
SeqListInsert(&seq, seq.size, 3);
SeqListInsert(&seq, seq.size, 4);
SeqListInsert(&seq, seq.size, 5);
SeqListInsert(&seq, seq.size, 6);
SeqListInsert(&seq, seq.size, 7);
SeqListInsert(&seq, seq.size, 8);
// SeqListInsert(&seq,-1,4);
SeqListPrint(&seq);
SeqListErase(&seq, seq.size - 1);
SeqListErase(&seq, seq.size - 1);
SeqListErase(&seq, seq.size - 1);
SeqListErase(&seq, seq.size - 1);
SeqListErase(&seq, seq.size - 1);
SeqListErase(&seq, seq.size - 1);
SeqListErase(&seq, seq.size - 1);
SeqListErase(&seq, 0);
SeqListErase(&seq, 1);
SeqListErase(&seq, 1);
SeqListErase(&seq, 1);
SeqListErase(&seq, 0);
SeqListErase(&seq, 0);
SeqListErase(&seq, 0);
SeqListErase(&seq, 0);
SeqListErase(&seq, 0);
SeqListPrint(&seq);
}
int main()
{
testpushback();
testpushfront();
testin_ear();
return 0;
}
三、总结
以上就是顺序表的基本操作了,现在来说一下顺序表有哪些优缺点呢?
优点如下:
- 逻辑相邻,物理相邻:顺序表通过数据元素在物理存储单元中的相邻关系来反映数据元素在逻辑上的相邻关系,这使得访问逻辑上相邻的数据元素变得非常高效。
- 随机存取:顺序表支持通过索引直接访问任何元素,这种随机访问的能力对于许多算法和数据操作来说是非常高效的。
- 存储空间使用紧凑:顺序表的所有元素都存储在连续的内存空间中,没有额外的指针开销,这使得存储空间的使用非常紧凑。
- CPU缓存命中率高:由于数据存储在连续的内存地址中,CPU缓存命中的几率较高,从而提高了数据访问的速度。
这样看起来顺序表的优点挺多的,但是没有完美的数据结构,有优点就会有缺点,缺点有哪些呢?
缺点如下:
- 插入和删除操作效率低:当在顺序表的中间或末尾插入或删除元素时,需要移动插入或删除点之后的所有元素,这导致插入和删除操作的效率较低,时间复杂度为O(n)。
- 预先分配空间:顺序表需要预先分配足够的存储空间,如果预先分配的空间过大,会造成空间的浪费;如果预先分配的空间不足,则可能需要频繁地重新分配和复制数据,导致额外的开销。
- 表容量难以扩充:一旦预先分配的空间用完,顺序表的容量就很难再扩大,除非重新分配更大的连续内存空间。
- 空间利用不够灵活:顺序表需要一次性申请大量的连续空间,这在内存碎片化严重或数据量不确定的情况下可能不太适用。
所以在使用中要衡量自己实际需求去使用合适的数据结构。