前言
C语言单链表、单链表的创建、打印单链表、尾插、头插、尾删、头删、查找、指定位置之前和之后插入、删除pos节点、删除pos之后的节点、销毁链表等介绍
单链表的创建
- 链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
- 简单来说就是物理上不连续,但是逻辑上连续。由一个一个的节点链接成链表。
- 每个节点的本质是一个结构体
- 结构体的成员为 当前节点保存的数据和下一个节点的地址。
因此单链表节点创建如下:
// SList.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
- typedef int SLTDataType; 是为了方便每个节点数据类型的更改。
一、打印单链表
- 后续每个函数都需要在单链表的头文件(SList.h)中声明,为了方便不过多说明。
- 在测试源文件(test.c)中手动初始化每个节点,然后打印
- 在测试源文件中,测试函数都要放在主函数中调用,为了方便,不过多说明。
// test.c
#include "SList.h"
void SLTNodetest01()
{
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
node2->data = 2;
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node4->data = 4;
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
node3->data = 3;
SLTNode* node5 = (SLTNode*)malloc(sizeof(SLTNode));
node5->data = 5;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = node5;
node5->next = NULL;
SLTNode* plist = node1;
// 打印链表函数
SLTPrint(plist);
}
打印单链表函数的实现:
// SList.c
#include "SList.h"
// 打印单链表
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur != NULL)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
- 我们习惯在接收到链表头节点后,用另一个变量(pcur)接收。
- 如果pcur 不为空,则打印数据(pcur->data), 并让pcur指向下一个节点,循环打印
- 若pcur为空,则退出循环,打印一个NULL和换行符。
- 效果如下:
二、尾插
- 顾名思义,在链表的尾部插入节点
- 先创建一个节点,让原链表最后一个节点的地址,存放新节点的地址,让新节点的地址存放为NULL。
尾插函数的实现:
// SLish.c
// 创建新节点函数
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;
}
// 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newNode = SLTBuyNode(x);
if (*pphead == NULL)
{
*pphead = newNode;
}
else
{
SLTNode* ptail = *pphead;
while (ptail->next != NULL)
{
ptail = ptail->next;
}
ptail->next = newNode;
}
}
- 定义一个创建新节点的函数(SLTBuyNode),创建新节点后返回新节点的地址。
- 判断若头节点的地址为空,说明链表为空,新节点的地址直接赋给头节点的地址。
- 若不为空,则找到链表最后一个节点(ptail)最后一个节点的地址为新节点的地址。
测试尾插函数如下:
void SLTNodetest03()
{
// 测试尾插
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPrint(plist);
SLTPushBack(&plist, 2);
SLTPrint(plist);
SLTPushBack(&plist, 3);
SLTPrint(plist);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLTPushBack(&plist, 5);
SLTPrint(plist);
}
效果如下:
三、头插
- 创建一个新的节点,新节点中存放的地址指向原链表头节点。
- 再将原链表头节点的指针指向新的节点。
头插函数的实现:
// SList.c
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newNode = SLTBuyNode(x);
newNode->next = *pphead;
*pphead = newNode;
}
测试头插函数如下:
void SLTNodetest04()
{
//测试头插
SLTNode* plist = NULL;
SLTPushFront(&plist, 0);
SLTPrint(plist);
SLTPushFront(&plist, -1);
SLTPrint(plist);
SLTPushFront(&plist, -2);
SLTPrint(plist);
}
效果如下:
四、尾删
- 找到单链表的最后一个节点,将其free释放。
- 同时将最后一个节点之前的节点存储的地址(next)置为空(NULL)。
尾删函数的实现:
// SList.c
// 尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
测试尾删函数如下:
// test.c
void SLTNodetest05()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
// 测试尾删
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
}
- 先尾插5个节点,然后一次删除一个节点
- 若对空链表进行删除,会报错
五、头删
- 创建临时变量(next)存储单链表头节点中存放的下一个节点的地址。
- free释放头节点地址指向的空间。
- 再将临时变量next的地址,赋给头节点的地址。
头删函数的实现:
// 头删
void SLTPopFront(SLTNode * *pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
测试头删函数如下:
void SLTNodetest06()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
// 测试头删
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
}
效果如下:
六、查找
- 在单链表的节点中查找传入的数据,若找到返回节点的地址,若没找到返回NULL。
查找函数的实现:
SLTNode* SLTFind(SLTNode* pphead, SLTDataType x)
{
SLTNode* pcur = pphead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
测试查找函数如下:
void SLTNodetest07()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
// 测试查找
SLTNode* find = SLTFind(plist, 1); // 找到了
if (find != NULL)
printf("1 找到了!!\n");
else
printf("1 没找到!!\n");
SLTNode* find1 = SLTFind(plist, 10); // 没找到
if (find1 != NULL)
printf("10 找到了!!\n");
else
printf("10 没找到!!\n");
}
效果如下:
七、指定位置之前插入
- 找到这个位置(pos)的前一个节点(prev)
- 创建一个新的节点,prev的next指向这个新的节点。
- 新节点的next指向pos节点。
指定位置之前插入函数的实现:
// 指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead);
assert(pos);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newNode = SLTBuyNode(x);
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = newNode;
newNode->next = pos;
}
}
- 若传入的位置是头节点,则直接调用头插函数。
测试指定位置之前插入函数如下:
void SLTNodetest08()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
// 找到3的位置
SLTNode* find = SLTFind(plist, 3);
//指定位置之前插入
SLTInsert(&plist, find, 10);
SLTPrint(plist);
}
效果如下:
八、指定位置之后插入
- 创建新的节点
- 指定位置pos的next赋值给新节点的next。
- 再将新节点的地址赋值给指定位置pos的next。
指定位置之后插入函数的实现:
// 在指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newNode = SLTBuyNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
测试指定位置之后插入函数如下:
void SLTNodetest09()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
// 找到3的位置
SLTNode* find = SLTFind(plist, 3);
// 指定位置之后插入
SLTInsertAfter(find, 50);
SLTPrint(plist);
}
效果如下:
九、删除pos节点
- 先得到指定位置pos之前的节点prev。
- pos的next 赋值给 prev的next。
- free 释放掉 pos节点。
删除pos节点函数的实现:
// 删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
- 若pos指向头节点,则直接调用头删函数。
测试删除pos节点函数如下:
void SLTNodetest10()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
// 找到3的位置
SLTNode* find = SLTFind(plist, 3);
// 删除pos节点
SLTErase(&plist, find);
SLTPrint(plist);
}
效果如下:
十、删除pos之后的节点
- 创建临时变量del,存储指定位置(pos)的下一个节点的地址。
- 指定位置pos的next指向pos->next->next。
- free 临时变量del所指向的空间。
- del置为NULL。
删除pos之后节点函数的实现:
// 删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
SLTNode* del = pos->next;
pos->next = pos->next->next;
free(del);
del = NULL;
}
测试删除pos之后的节点函数如下:
void SLTNodetest11()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
// 找到3的位置
SLTNode* find = SLTFind(plist, 3);
// 删除pos节点之后的节点
SLTEraseAfter(find);
SLTPrint(plist);
}
效果如下:
十一、销毁链表
- 使用临时变量存储头节点的next地址
- 释放头节点的空间
- 再将临时变量赋值给头节点地址
- 最后,将头节点置为空
- 循环,知道链表结束
销毁链表函数的实现:
// 销毁链表
void SLTDestory(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
测试销毁链表函数如下:
void SLTNodetest12()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPushBack(&plist, 5);
SLTPrint(plist);
// 销毁链表
SLTDestory(&plist);
SLTPrint(plist);
}
效果如下:
总结
C语言单链表、单链表的创建、打印单链表、尾插、头插、尾删、头删、查找、指定位置之前和之后插入、删除pos节点、删除pos之后的节点、销毁链表等介绍