最近本哲♂学家学习了链表这一新的数据结构,接下来由我带领大家实现链表:
一 、头文件
注:本写法是无头的单链表,所以传参为二级指针。
我们事先写好所要完成的函数,在 .c文件中进一步去完成。
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
//创建新节点
SLTNode* SLTBuyNode(SLTDataType x);
//打印节点
void SLTPrint(SLTNode* phead);
//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
二、.C文件
1、创建新的节点
链表与顺序表不同,不需要扩容空间,所以直接用malloc即可。
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)//如果开辟失败,直接退出
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
2、打印链表
链表是由每一个节点里面的next指针指向的下一个节点的地址联系起来的,所以我们可以通过对next的访问来实现访问链表。
oid SLTPrint(SLTNode* phead)
{
assert(phead);//不能对空指针解引用
SLTNode* prive = phead;
while (prive != NULL)
{
printf("%d->", prive->data);
prive = prive->next;
}
printf("NULL");
}
3、尾插和尾删
1、尾插
如果该链表没有节点(phead默认为NULL),则我们通过对phead解引用访问next程序会崩溃,所以我们要对上面的情况单独考虑,如果有节点,我们让第一个节点的next指向下一个节点的地址即可。
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
if (*pphead == NULL)//我们创建时只创建了头节点的地址,所以如果传一级指针这就无法实现尾插,后面的更是执行不到
{
*pphead = newnode;
}
else
{
//找到尾部
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
//void MYSLTPushBack(SLTNode* pphead, SLTDataType x)
注意我们创建时只创建了头节点的地址,所以如果传一级指针就无法实现没有节点情况下的尾插,后面的尾插更是执行不到。所以要传二级指针。
2、尾删
一般思路时找到链表尾节点将尾节点释放,前一个节点的next为NULL;但是如果为一个节点上面思路并不能满足我们要达到的效果,所以要对只有一个节点的情况单独考虑。
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);//链表不能为空
//只有一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
//找到尾部的前一个节点
SLTNode* ptail = *pphead;
SLTNode* pptail = *pphead;
while (ptail->next)
{
pptail = ptail;
ptail = ptail->next;
}
pptail->next = NULL;//这已经把尾节点前的一个节点和尾节点找到了,不用考虑先将哪个置为空
free(ptail);
ptail = NULL;
}
}
4、头插和头删
1、头插
按照前面尾插的思路,我们知道得分为只有一个节点,和有多个节点的情况,所以我们很容易写出下面的代码:
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);//如果只有头节点且为null
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
newnode->next = (*pphead);
(*pphead) = newnode;
}
}
但是事实上要不要这么麻烦呢?
实际上将只有一个节点的情况带入第二种情况中发现也是可行的所以代码可以简化如下:
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
//newnode *pphead
newnode->next = *pphead;
*pphead = newnode;
}
2、头删
同样的我们根据前面尾删的思路可以知道要分为两种情况,但事实上,不需要那么麻烦,不用考虑只有一个节点的情况。
void SLTPopFront(SLTNode** pphead)
{
//链表不能为空
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next; //-> 优先级高于*
free(*pphead);
*pphead = next;
}
5、查找
只要遍历所以链表找到匹配的链表的地址返回就可以了
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur)//等价于pcur != NULL
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//pcur == NULL
return NULL;
}
6、指定位置前添加节点和删除指定位置节点
1、指定位置前添加
这里我们需要考虑特殊情况:就是当链表只有一个元素时,相当于变成了头插,其他情况时我们要找到pos的前一个节点,所以我们可以写出如下代码:
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead);//*pphead 不能为空是因为后面有对*pphead解引用的操作
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
//若pos == *pphead;说明是头插
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)//当prev的next指针指向pos是prev为前一个节点
{
prev = prev->next;
}
//prev -> newnode -> pos
newnode->next = pos;
prev->next = newnode;
}
}
2、删除指定位置节点
同样的如果pos为头节点,则为头删:
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
//pos是头结点/pos不是头结点
if (pos == *pphead)
{
//头删
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//prev pos pos->next
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
7、指定位置后添加和删除节点
1、指定位置后添加
这里我们还要考虑其为尾结点的情况:
void SLTInsertAfter(SLTNode* pos, SLTDataType x, SLTNode** pphead)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
if (pos->next == NULL)
{
SLTPushBack(pphead,x);
}
else
{
SLTNode* aft = pos->next;
newnode->next = aft;
pos->next = newnode;
}
}
2、指定位置之后删除
同理我们也得考虑其为尾节点的情况:
void SLTEraseAfter(SLTNode* pos, SLTNode** pphead)
{
assert(pos);
//SLTNode* newnode = SLTBuyNode(x);
if (pos->next == NULL)
{
SLTPopBack(pphead);
}
else
{
SLTNode* del = pos->next;
//pos del del->next
pos->next = del->next;
free(del);
del = NULL;
}
}
8、销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//pcur
*pphead = NULL;
}
总结:
链表相对于顺序表来说更好实现,但比较难的是理解其是如何实现将各个节点连起来的。期待下一次我们一起van♂。