注意:此处我讲解顺序表中画图将下标以指针指向的形式画了出来,在看到下面图片中以指针形式呈现的下标读者不要见怪,只是为了方便理解。
顺序表是一个结构体,如图所示:
#pragma once
#include <stdio.h>
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* arr;
int size;
int capicity;
}SeqList;
由于我们不知道顺序表里要存储的是什么类型的值,所以我们需要typedef一下我们要用的类型,以下讲解均用顺序表存储整型变量来进行讲解。
首先顺序表中的arr指针是用来指向我们要动态开辟的数组的指针,size代表顺序表底层数组存储的有效数据个数,capicity代表我们数组已经开辟好的、可供使用的容量。
以下为我们要实现的顺序表中的各项接口,放到下图以便展示:
// 顺序表初始化
void SeqListInit(SeqList* psl);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl);
// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* psl);
// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* psl);
// 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, int pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, int pos);
// 顺序表销毁
void SeqListDestory(SeqList* psl);
// 顺序表打印
void SeqListPrint(SeqList* psl);
这里特别说明一下,为什么我们传的参数都是结构体指针?
答:因为我们要去改变的是我们传过去的结构体本身,而不是只用函数里面临时拷贝的结构体,改变函数里临时拷贝的结构体本身是没有任何意义的,传址调用可以改变我们传过去的结构体(也就是我们在main函数里定义的那个顺序表结构体)。
// 顺序表初始化
void SeqListInit(SeqList* psl)
{
psl->arr = (SLDataType*)malloc(4 * sizeof(SLDataType));
psl->size = 0;
psl->capicity = 4;
}
上图为顺序表的初始化方法,我这里的初始化方法采用的是先开辟4个存储整型的空间,然后将有效数据个数size=0,容量capicity=4。
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl)
{
if (psl->size == psl->capicity)
{
int newcapicity = 2 * psl->capicity;
psl->arr = (SLDataType*)realloc(psl->arr, newcapicity * sizeof(SLDataType));
psl->capicity = newcapicity;
}
}
上图为顺序表的增容方法(只要检查到我们的有效数据个数和容量相等,我们就要扩容,之后的插入数据我们会用到),我们增容使用realloc函数进行增容,这里增容采用的是2倍扩容(由前辈们广泛实验得到2倍扩容是较好的增容方式)。
接下来是我们顺序表中尾插逻辑的讲解:
// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x)
{
CheckCapacity(psl);
psl->arr[psl->size] = x;
psl->size++;
}
上图为我们顺序表尾插的代码。
1.在我们插入数据之前都要检查容量是否足够我们去插入数据。
2.我们在尾插的时候是在下标为size的位置进行尾插的。
3.插入之后,有效数据个数应该+1。
// 顺序表尾删
void SeqListPopBack(SeqList* psl)
{
psl->size--;
}
尾删逻辑很简单,只要我们把有效数据个数-1,我们就访问不到尾部的那个数据了。
以下为我们顺序表头插的讲解:
首先,在顺序表头插的时候需要我们检查容量够不够,我们再去挪动数据,把之前的所有数据都向后挪动一位。
// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x)
{
CheckCapacity(psl);
int end = psl->size;
while (end >= 1)
{
psl->arr[end] = psl->arr[end - 1];//arr[1]=arr[0]
end--;
}
psl->arr[0] = x;
psl->size++;
}
以上为顺序表头插逻辑的代码,首先我们先记录一下当前的size下标以便挪动数据,我用一张图帮助大家理解这里的逻辑(我们这里采用从后往前向后挪动数据以免先前数据被覆盖掉):
挪动完数据以后,我们就可以在下标为0的起始位置插入我们的数据了。
在这里教大家一个小技巧:如果你实在确认不了你循环的结束条件是什么的时候,你就在你的逻辑中加上自己停止循环条件的注释,以便自己确认循环结束条件。
下面是我们头删逻辑的讲解(以下为逻辑图):
头删整体逻辑就是:
1.将数据从前往后向前挪动数据
2.将有效数据个数size -1。
// 顺序表头删
void SeqListPopFront(SeqList* psl)
{
int src = 0;
while (src <= psl->size - 2)
{
psl->arr[src] = psl->arr[src + 1];//arr[size-2]=arr[size-1]
src++;
}
psl->size--;
}
下面是我们顺序表查找数据的讲解(在这里只讲解查找到第一个符合值的下标):
我们要通过src下标来遍历这个数组查找。
// 顺序表查找,如果找不到我们就返回size下标
int SeqListFind(SeqList* psl, SLDataType x)
{
int src = 0;
while (src < psl->size)
{
if (psl->arr[src] == x)
{
return src;
}
src++;
}
printf("抱歉,没有找到此数据\n");
return src;
}
以上为代码实现,逻辑为:
1.如果找到该数据,则返回这个位置的下标
2.找不到则返回size
以下为在指定的pos下标位置插入数据的讲解,其中包含了我们上面讲到的挪动数据的知识
这里我们还要采用的就是从后往前向后挪动数据的方法,最后在pos位置插入我们想要插入的数据。
由于我们这里的逻辑和头插的逻辑基本一致,所以这里不做过多讲解,直接代码奉上:
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, int pos, SLDataType x)
{
CheckCapacity(psl);
int end = psl->size;
while (end >= pos + 1)
{
psl->arr[end] = psl->arr[end - 1];//arr[pos+1]=arr[pos]
end--;
}
psl->arr[pos] = x;
psl->size++;
}
以下为在指定的pos下标位置删除数据的讲解,其中也包含了我们上面讲到的挪动数据的知识
此处采用的是从前往后向前挪动数据的方法。
由于我们这里的逻辑和头删的逻辑基本一致,所以这里不做过多讲解,直接代码奉上:
// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, int pos)
{
int src = pos;
while (src <= psl->size - 2)
{
psl->arr[src] = psl->arr[src + 1];//arr[size-2]=arr[size-1]
src++;
}
psl->size--;
}
下面是销毁顺序表的代码:
// 顺序表销毁
void SeqListDestory(SeqList* psl)
{
psl->size = psl->capicity = 0;
free(psl->arr);
}
本质就是将psl->arr所指向的数组空间释放掉。
下面是打印顺序表中数据的代码:
// 顺序表打印
void SeqListPrint(SeqList* psl)
{
int src = 0;
while (src < psl->size)
{
printf("%d ", psl->arr[src]);
src++;
}
}
打印顺序表本质上就是在遍历底层的数组。
至此,我们的利用C语言对顺序表的实现就算是讲解完毕了,后续本管家还会持续更新C语言数据结构部分的讲解,展开庖丁解牛般地讲解,感谢观看!!!