数据结构:数据结构是计算机储存、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由哪部分构成,以及什么方式构成,以及数据元素之间呈现的结构。
1.线性表
线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串......
线性表在逻辑结构上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表
I.概念及结构
顺序表是一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
顺序表一般可分为:
1.静态顺序表:使用定长数组存储元素
2.动态顺序表:使用动态开辟的数组存储
II.接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致了N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,接下来实现动态顺序表。
typedef int SL_Type;//以整型数据为例
typedef struct SeqList
{
SL_Type* arr;//存放数据的数组
int size;//有效数据个数
int capacity;//空间的大小
}SL;
//初始化声明
void Init_SL(SL* p);
//初始化实现
void Init_SL(SL* p)
{
p->arr = NULL;//把数组置为空
p->capacity = p->size = 0;
}
//检查空间容量(capacity)声明
void Check_SL(SL* p);
//检查空间容量(capacity)实现
void Check_SL(SL* p)
{
assert(p);
if (p->capacity == p->size)//空间已满
{
int new_capacity = p->capacity == 0 ? 4 : 2 * p->capacity;//三目表达式,二倍增容
SL_Type* tmp = realloc(p->arr, new_capacity * sizeof(SL_Type));
if (tmp == NULL)
{
perror("realloc error: ");
exit(1);
}
else
{
p->capacity = new_capacity;
p->arr = tmp;
}
//tmp == NULL ? ((perror("realloc error: "), exit(1), 0)) : ((p->arr = tmp, p->capacity = new_capacity, 1));
}
}
注:顺序表增容为了减少插入操作的频率和提高性能。当顺序表空间不足时,需要进行扩容操作。以二倍增加的方式进行扩容是因为这样可以在每次扩容时将数据复制到新的更大的数组中,同时保持时间复杂度为O(n)。如果每次扩容增加的空间太小,会导致频繁的扩容操作,增加了时间复杂度。而以二倍增加的方式,每次扩容后的空间大小都是上一次的两倍,这样可以有效减少频繁扩容的次数,提高性能。
要想向顺序表中插入数据时,有三种方法:头插、尾插、任意位置插入
//头插声明
void PushFront_SL(SL* p, SL_Type x);
//头插实现
void PushFront_SL(SL* p, SL_Type x)
{
Check_SL(p);//检查容量
for (int i = p->size; i>=0; i--)
{
p->arr[i + 1] = p->arr[i];//将数据往后面挪
}
++p->size;//注意增加size
p->arr[0] = x;//将要插入的数据放在头部
}
//尾插声明
void PushBack_SL(SL* p, SL_Type x);
//尾插实现
void PushBack_SL(SL* p, SL_Type x)
{
assert(p);
Check_SL(p);//检查容量
p->arr[p->size++] = x;//直接将要插入的数据放在最后即可
}
//任意位置插入声明
void Insert_SL(SL* p, int spe, SL_Type x);
//任意位置插入实现
void Insert_SL(SL* p, int spe, SL_Type x)
{
assert(p);
Check_SL(p);
for (int i = p->size; i>=spe; i--)
{
p->arr[i + 1] = p->arr[i];//将指定位置后的数据往后挪
}
p->arr[spe] = x;
++p->size;//注意增加size
}
同样,删除数据也有三种方法:头删、尾删、指定位置删除
//头删声明
void PopFront_SL(SL* p);
//头删实现
void PopFront_SL(SL* p)
{
assert(p);//不能传空
for (int i = 0; i < p->size; i++)
{
p->arr[i] = p->arr[i+1];//从第二个数据开始往前挪,将第一个数据覆盖
}
--p->size;//注意减少size
}
//尾删声明
void PopBack_SL(SL* p);
//尾删实现
void PopBack_SL(SL* p)
{
assert(p);//不能传空
--p->size;//直接将size减1即可
}
//任意位置删声明
void Pop_SL(SL* p, int spe);
//任意位置删实现
void Pop_SL(SL* p, int spe)
{
assert(p);//不能传空
for (int i = spe; i<p->size ; i++)
{
p->arr[i] = p->arr[i+1];//从要删除的位置开始往后挪,覆盖的是要删除的位置的数据
}
--p->size;//注意减少size
}
//销毁声明
void Destroy_SL(SL* p);
//销毁实现
void Destroy_SL(SL* p)
{
if (p->arr != NULL)
{
free(p->arr);//动态开辟的空间要用free释放
p->arr = NULL;//及时置为空
}
p->size = p->capacity = 0;
}