目录
1.线性表
线性表(
linear list
)
是
n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般可以分为:
1. 静态顺序表
2. 动态顺序表
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间
大小。
2.1 静态顺序表
使用定长数组存储元素。
//顺序表的静态存储
#define N 12
typedef int SeqListType;
typedef struct SeqList
{
SeqListType array[N]; //定长数据
size_t size; //有效数据的长度
}SeqList;
2.2 动态顺序表
使用动态开辟的数组存储。
//循序表的动态存储
typedef int SLdatetype;
typedef struct Seqlist
{
SLdatetype* a; //指向动态开辟的数组
size_t size; //有效的数据个数
size_t capacity; //容量空间的大小
}SL;
3.顺序表实践功能
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空 间开多了浪费,开少了不够用。
typedef int SLdatetype;
typedef struct Seqlist
{
SLdatetype* a; //指向动态开辟的数组
size_t size; //有效的数据个数
size_t capacity; //容量空间的大小
}SL;
//对数据的初始化
void SeqlistInit(SL* s1);
//输出数据
void SLPrint(SL* s1);
//从起始点输入数据(头插)
void SLPushFront(SL* s1, SLdatetype n);
//从尾部输入数据(尾插)
void SLPushBack(SL* sl, SLdatetype n);
//从尾部删除数据(尾删)
void SLPopBack(SL* sl);
//从头部删除数据(头删)
void SLPopFront(SL* sl);
//查找数据,返回其下标(没有就返回-1)
int SLFind(SL* sl, SLdatetype n);
//向前插入数据(前插)
void SLInsert(SL* sl, int pos, SLdatetype n);
//删除数据
void SLErase(SL* sl, int pos);
4.单链表的实现
4.1 对数据的初始化
//对数据的初始化
void SeqlistInit(SL* s1)
{
assert(s1);
s1->a = NULL;
s1->size = s1->capacity = 0;
}
4.2 输出数据
//输出数据
void SLPrint(SL* sl)
{
assert(sl);
int i = 0;
for (i = 0; i < sl->size; i++)
{
printf("%d ", sl->a[i]);
}
printf("\n");
}
4.3 动态管理进行开辟
在插入数据函数中需要引用,当空间不够时开辟空间。
//动态管理进行开辟
void SLCheckCapacity(SL* sl)
{
assert(sl);
if (sl->size == sl->capacity)
{
int newCapacity = sl->capacity == 0 ? 4 : sl->capacity * 2;
sl->a = (SLdatetype*)realloc(sl->a, newCapacity * sizeof(SL));
assert(sl->a);
sl->capacity = newCapacity;
}
}
4.4 从起始点输入数据(头插)
我们需要注意size与数据的关系:
//从起始点输入数据(头插)
void SLPushFront(SL* sl)
{
//如果空间小了,就用动态管理进行开辟
SLCheckCapacity(sl);
//挪动数据
int end = sl->size - 1;
while (end >= 0)
{
sl->a[end + 1] = sl->a[end];
--end;
}
sl->a[0] = n;
sl->size++;
}
由于,我们是要进行头插,所以我们需要将a[0]腾出来。
最终,再将我们所需要插入的数值(N)放入a[0]中,再将size+1。
4.5 从尾部输入数据(尾插)
//从尾部输入数据(尾插)
void SLPushBack(SL* sl)
{
SLCheckCapacity(sl);
assert(sl);
sl->a[sl->size] = n;
sl->size++;
}
4.6 从尾部删除数据(尾删)
这里只需要注意,我们的顺序表是否有数据可以用来删除。赋不赋值不重要,重要的时要size--。
//从尾部删除数据(尾删)
void SLPopBack(SL* sl)
{
assert(sl != NULL);
sl->a[ps->size - 1] = 0;
if (sl->size == 0)
{
printf("SeqList is empty\n");
return;
}
sl->size--;
}
4.7 从头部删除数据(头删)
//从头部删除数据(头删)
void SLPopFront(SL* sl)
{
assert(sl);
assert(sl->size > 0);
int begin = 1;
while (begin < sl->size)
{
sl->a[begin - 1] = sl->a[begin];
begin++;
}
sl->size--;
}
4.8 查找数据,返回其下标(没有就返回-1)
//查找数据,返回其下标(没有就返回-1)
int SLFind(SL* sl, SLdatetype n)
{
assert(sl);
int i = 0;
for (i = 0; i < sl->size; i++)
{
if (n == sl->a[i])
return i;
}
return -1;
}
4.9 向前插入数据(前插)
我们需要一个(4.8 查找数据,返回其下标(没有就返回-1))SLFind函数。
同样的道理,顺序表是连续的,所以我们当向前插入数据的同时是要通过向后移动数据,给新插入的数据腾位置。
//向前插入数据(前插)
void SLInsert(SL* sl, int pos, SLdatetype n)
{
assert(sl);
assert(pos >= 0 && pos <= sl->size);
//如果空间小了,就用动态管理进行开辟
SLCheckCapacity(sl);
int end = sl->size - 1;
while (end >= pos)
{
sl->a[end + 1] = sl->a[end];
end--;
}
sl->a[pos] = n;
sl->size++;
}
4.10 删除数据
我们需要一个(4.8 查找数据,返回其下标(没有就返回-1))SLFind函数。
同样的道理,顺序表是连续的,所以我们当向前插入数据的同时是要通过向前移动数据,将我们所使用的空间归还。
//删除数据
void SLErase(SL* sl, int pos)
{
assert(sl);
assert(pos >= 0 && pos <= sl->size);
int end = sl->size - 1;
while (pos < end)
{
sl->a[pos] = sl->a[pos + 1];
pos++;
}
sl->size--;
}