各位帅哥靓女们,我是吴所试试,这期给大家带来的是《线性表和链表》,也就是陈述一下我对线性表的所知,老规矩,如果吴所试试有哪里说的不对的地方还请于评论区斧正,如果觉得吴所试试讲的还可以的话,还请给吴所试试一键三连,毕竟吴所试试码字不易。
【一】线性表的概念
线性表是具有n个相同数据的有限序列,线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表,链表,栈,队列,字符串~~~。
线性表在逻辑上是线性结构,也就是一条连续的直线,但是在物理结构上不一定是连续的,线性表在物理上存储时是按照链式结构的形式存储的。
线性表的基础构造含有三个部分:线性表,记录存储的容量,表示当时顺序表中含有铬数据的个数,所以这些数据我们使用结构体来存储是最好的。所以这个结构体应该是这样构建的:
typedef struct Message
{
//线性表
int* p;
//存储的容量
int capacity;
//线性表现存的大小
int size;
}Message;
这个时候线性表有了,我们需要对这个线性表实现存储数据,想想我们存储数据的时候需要哪些操作呢?初始化顺序表,销毁顺序表,打印顺序表,尾插循序表,等等,这些操作。代码如下:
void SLPrint(const SL* psl)
{
assert(psl);
for (int i = 0; i < psl->size; ++i)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
void SLInit(SL* psl)
{
assert(psl);
psl->a = NULL;
psl->capacity = psl->size = 0;
}
void SLDestory(SL* psl)
{
assert(psl);
/*if (psl->a)
{*/
free(psl->a);
psl->a = NULL;
psl->capacity = psl->size = 0;
//}
}
void SLCheckCapacity(SL* psl)
{
// 检查容量
if (psl->size == psl->capacity)
{
int newCapcity = psl->capacity == 0 ? 4 : psl->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(psl->a, newCapcity*sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
//exit(-1);
}
psl->a = tmp;
psl->capacity = newCapcity;
}
}
// https://cplusplus.com/reference/
void SLPushBack(SL* psl, SLDataType x)
{
/*assert(psl);
SLCheckCapacity(psl);
psl->a[psl->size] = x;
psl->size++;*/
SLInsert(psl, psl->size, x);
}
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++;
SLInsert(psl, 0, x);
}
void SLPopBack(SL* psl)
{
//assert(psl);
温柔的检查
///*if (psl->size == 0)
//{
//return;
//}*/
暴力的检查
//assert(psl->size > 0);
//psl->size--;
SLErase(psl, psl->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;
//}*/
//int begin = 1;
//while (begin < psl->size)
//{
// psl->a[begin-1] = psl->a[begin];
// ++begin;
//}
//--psl->size;
}
int SLFind(SL* psl, SLDataType x)
{
assert(psl);
for (int i = 0; i < psl->size; ++i)
{
if (psl->a[i] == x)
{
return i;
}
}
return -1;
}
//void SLInsert(SL* psl, int pos, SLDataType x)
void SLInsert(SL* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(pos <= psl->size);
SLCheckCapacity(psl);
// 挪动数据
/*int end = psl->size - 1;
while (end >= (int)pos)
{
psl->a[end + 1] = psl->a[end];
--end;
}*/
size_t end = psl->size;
while (end > pos)
{
psl->a[end] = psl->a[end-1];
--end;
}
psl->a[pos] = x;
++psl->size;
}
// 顺序表删除pos位置的值
void SLErase(SL* psl, size_t pos)
{
assert(psl);
assert(pos < psl->size);
size_t begin = pos;
while (begin < psl->size - 1)
{
psl->a[begin] = psl->a[begin + 1];
++begin;
}
psl->size--;
}
void SLModify(SL* psl, size_t pos, SLDataType x)
{
assert(psl);
assert(pos < psl->size);
psl->a[pos] = x;
}
【二】线性表操作的实现
1.打印,就是将数据根据数据的个数挨个打印没啥好说的
2.初始化线性表,将结构体内部的容量和现有数据个数全部置为0,切将数组指针置为空。
3.销毁线性表,将内部的数据个数和现有容量全部置为0且将数组指针内部的空间全部free释放掉,
这时候就有小伙伴要问了,为啥不讲指针置为空?其实这个道理很简单,在函数内部你将指针置为空的仅仅是将其拷贝的一份置为了空,形参不影响实参。
4.检查线性表的大小,如果这个链表为空,或者链表满了,就重新开出原本的容量的两倍大小的空间,且将这块空间赋予给线性表。这时候就有小伙伴要问了,为啥赋予两倍的空间?而不是三倍或者四倍呢?其实你赋予几倍就可以赋予几倍,但是从考虑空间的角度来说2呗是刚刚好的。
5.向线性表中塞入元素,其实就是在数组中size的位置写入一个元素且size的大小加一
6.头插数据。就是在数组的第0位置上写入一个数据,且将后面的数据依次后挪动,从后往前挪哦,因为你从前往后挪的话,你就会发现其实这这就会发生一个重复写入的结果。
7.头删除数据,这其实也是一个操作非常简单,但是你特别容易搞混的操作,因为你觉得头插入数据是从后面往前面挪,这是你就会将数据从后面往前面插,这个时候你就会发现你最后得到的结果就是最后就是将这个链表的数据全部都是最后一个数,其实这个道理是特别简单的,你只需要将这个数据链表从前面往后面挪动,这个时候第一个数据就会被覆盖掉,其实在我们计算机中不存在所谓的删除数据,我们都是将数据直接覆盖,因为覆盖后的数据是你所需要的,删除写入的数据成本极大。
8.搜寻数据:从这个链表的头节点开始遍历,找到你所需要的那个节点位置,并将其返回,有的小伙伴就要问了为啥不适用二分查找呢?好兄弟二分查找的前提是有序啊,你插入数据的时候是不考虑数据的大小的。
9.从中间插入数据,其实这也是非常简单的,在你所需要插入的那个位置上将前面的数据直接挪向后面,前面起始的位置就可以写入数据了,当然在挪动之前你必须要检查这个链表是不是满的。
10.删除这个指定位置的值,就是从指定位置开始将后面的数据依次向前覆盖,且这个size减一
11.查看当前位置的是啥,就是将数组这个位置的值直接返回即可,前提是查看的位置合法。