注意:本文章主要实现的是单向无头不循环链表
一、链表的定义和结构
**1、链表的结构:**在结构上是线性的
2、链表的定义: 链表其实就像火车车厢一样,一个接一个的串起来。
所以链表在逻辑上是线性的,在物理上是非线性的,因为链表是一个一个的节点串起来,空间上不是连续的,所以在物理上是非线性的
链表的节点又分为两部分:
1、数据域:存储具体数据的成员
2、指针域:存储下一个节点的成员
二、链表的分类
链表可以分为三大类:
1、带头或不带头
不带头:链表的第一个节点就是首元节点
带头:链表的第一个节点是哨兵节点(头节点),头节点是没有有效的数据,但是头节点会指向首元节点

2、单向和双向
单向就是只有指向下一个节点的指针
双向不止有指向下一个节点的指针还有指向上一个节点的指针
如下图所示:

3、循环和不循环
不循环:尾节点不指向第一个存储有效元素的节点
循环:尾节点会指向第一个存储有效元素的节点,形成闭环

三、链表的核心功能
1、链表的创建、动态申请节点、销毁和打印
1.1链表结构的创建
cpp
typedef int DataType;
typedef struct SListNode
{
DataType date;//数据域
struct SListNode* next;//指针域
}SLTNode;
1.2、链表节点的动态申请并存储数据
cpp
SLTNode* StlBuyNode(DataType x)
{
SLTNode* pcur = (SLTNode*)malloc(sizeof(SLTNode));
if (pcur == NULL)
{
perror("malloc");
exit(1);
}
pcur->date = x;
pcur->next = NULL;
return pcur;
}
1.3、链表的打印
打印链表的原理:从头对链表进行遍历并打印它的数据域,当链表的当前节点为空了就说明链表已经遍历完了
cpp
void SltPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->",pcur->date);
pcur = pcur->next;
}
printf("NULL\n");
}
1.4、链表的销毁
链表的销毁其实和链表的打印是一个道理,都是通过对链表的遍历来实现的
cpp
void SLTDestory(SLTNode** pphead)
{
assert(pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* pnext = pcur->next;
free(pcur);
pcur = pnext;
}
*pphead = NULL;
}
2、链表的增加、删除和查找
2.1、链表的尾插
cpp
void StlPushback(SLTNode** pphead, DataType x)
{
assert(pphead);
SLTNode* plist = StlBuyNode(x);
if (*pphead == NULL)//当链表为空时,插入节点就是首元节点
{
*pphead = plist;
}
else
//当链表不为空时,要先找到最后一个节点(尾节点),再让最后一个节点的指针域指向需要插入节点的地址
{
//找尾
SLTNode* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = plist;
}
}
2.2、链表的头插
头插:让被插入的节点指向首元节点,被插入节点就成了新的首元节点
cpp
void StlPushFront(SLTNode** pphead, DataType x)
{
assert(pphead);
SLTNode* plist = StlBuyNode(x);
plist->next = *pphead;
*pphead = plist;
}
2.3、链表的尾删
cpp
void StlPopback(SLTNode** pphead)
{
assert(*pphead&&pphead);//链表不能为空
//有两种情况
//链表只有一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
//链表有多个节点
else
{
SLTNode* prev = *pphead;//被尾删的前一个节点
SLTNode* pcur = *pphead;//被尾删的节点
//找尾
while (pcur->next)
{
prev = pcur;//保存当前节点
pcur = pcur->next;//不满足条件则继续遍历链表
}
//满足条件时prev里面存储的就是被删节点的前节点
//而pcur就是被删除节点
free(pcur);
pcur = NULL;
prev->next = NULL;
//删除完了之后我们需要使被删除节点的前驱结点的指针域指向NULL保持链表的完整性
}
}
2.4、链表的头删
头删:删除首元节点,使首元节点的后继节点成为新的首元节点
cpp
void StlPopFront(SLTNode** pphead)
{
assert(pphead&&*pphead);//只要是删除,就都会判断被删除的容器是否为空
SLTNode* pcur = (*pphead) -> next;
free(*pphead);
*pphead = pcur;
}
2.5、链表的查找
cpp
SLTNode* SLTFind(SLTNode* phead, DataType x)
{
SLTNode* pcur = phead;
while ( pcur != NULL)
{
if (pcur ->date == x)
{
printf("找到了\n");
return pcur;
}
pcur = pcur->next;
}
printf("没找到\n");
return NULL;
}
3、链表在指定位置之后的增加和删除
3.1、在指定位置之后插入数据
cpp
void SLTInsertAfter(SLTNode* pos, DataType x)
{
assert(pos);//首先pos节点不能为空
SLTNode* pcur = StlBuyNode(x);//申请一个新节点
pcur->next = pos->next;//使新节点的指针域指向,pos节点的后继节点的地址
pos->next = pcur;//之后再时pos节点的指针域指向新节点的地址
}
3.2、在指定位置之后删除数据
cpp
void SLTEraseAfter(SLTNode* pos)
{
assert(pos&&pos->next);
SLTNode* pcur = pos->next;
pos->next = pcur->next;
free(pcur);
pcur = NULL;
}
四、链表的代码实现
首先我们要先创建好三个文件
SList.h:头文件
SList.c:链表功能实现文件
SList_test.c:链表测试文件
SList.h代码如下:
cpp
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;
typedef struct SListNode
{
DataType date;
struct SListNode* next;
}SLTNode;
//打印单链表
void SltPrint(SLTNode* phead);
//尾插
void StlPushback(SLTNode** pphead,DataType x);
//创建新的空间
SLTNode* StlBuyNode(DataType x);
//头插
void StlPushFront(SLTNode** pphead, DataType x);
//尾删
void StlPopback(SLTNode** pphead);
//头删
void StlPopFront(SLTNode** pphead);
//销毁链表
void SLTDestory(SLTNode** pphead);
//在指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, DataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插入
void SLTInsertAfter(SLTNode* pos, DataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//查找
SLTNode* SLTFind(SLTNode* phead, DataType x);
SList.c文件代码如下:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"
//打印链表
void SltPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->",pcur->date);
pcur = pcur->next;
}
printf("NULL\n");
}
//创建空间并赋值
SLTNode* StlBuyNode(DataType x)
{
SLTNode* pcur = (SLTNode*)malloc(sizeof(SLTNode));
if (pcur == NULL)
{
perror("malloc");
exit(1);
}
pcur->date = x;
pcur->next = NULL;
return pcur;
}
//尾插
void StlPushback(SLTNode** pphead, DataType x)
{
assert(pphead);
SLTNode* plist = StlBuyNode(x);
if (*pphead == NULL)
{
*pphead = plist;
}
else
{
//找尾
SLTNode* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = plist;
}
}
//头插
void StlPushFront(SLTNode** pphead, DataType x)
{
assert(pphead);
SLTNode* plist = StlBuyNode(x);
plist->next = *pphead;
*pphead = plist;
}
//尾删
void StlPopback(SLTNode** pphead)
{
assert(*pphead&&pphead);
//有两种情况
//链表只有一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
//链表有多个节点
else
{
SLTNode* prev = *pphead;
SLTNode* pcur = *pphead;
while (pcur->next)
{
prev = pcur;
pcur = pcur->next;
}
free(pcur);
pcur = NULL;
prev->next = NULL;
}
}
//头删
void StlPopFront(SLTNode** pphead)
{
assert(pphead&&*pphead);
SLTNode* pcur = (*pphead) -> next;
free(*pphead);
*pphead = pcur;
}
//指定位置插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, DataType x)
{
assert(pphead && *pphead);//当头节点为NULL时,无法插入,因为没有位置可插入
assert(pos);
if (pos == *pphead)//当插入位置为头结点时,说明是头插
{
StlPushFront(pphead, x);
}
else
{
SLTNode* plist = StlBuyNode(x);
SLTNode* pcur = *pphead;
while (pcur->next != pos)
{
pcur = pcur->next;
}
plist->next = pos;
pcur->next = plist;
}
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead&&pos&&*pphead);
if (*pphead == pos)
{
StlPopFront(pphead);
}
else
{
SLTNode* pcur = *pphead;
while (pcur->next != pos)
{
pcur = pcur->next;
}
pcur ->next = pos->next;
free(pos);
pos = NULL;
}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, DataType x)
{
assert(pos);
SLTNode* pcur = StlBuyNode(x);
pcur->next = pos->next;
pos->next = pcur;
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos&&pos->next);
SLTNode* pcur = pos->next;
pos->next = pcur->next;
free(pcur);
pcur = NULL;
}
//查找
SLTNode* SLTFind(SLTNode* phead, DataType x)
{
SLTNode* pcur = phead;
while ( pcur != NULL)
{
if (pcur ->date == x)
{
printf("找到了\n");
return pcur;
}
pcur = pcur->next;
}
printf("没找到\n");
return NULL;
}
//链表的销毁
void SLTDestory(SLTNode** pphead)
{
assert(pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* pnext = pcur->next;
free(pcur);
pcur = pnext;
}
*pphead = NULL;
}
SList_test.c文件代码如下:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include "SList.h"
void Test() {
SLTNode* pList = NULL;
printf("\n1. 尾插测试:插入 3、1\n");
StlPushback(&pList, 3);
StlPushback(&pList, 1);
printf("尾插后链表:");
SltPrint(pList);
printf("\n2. 头插测试:插入 4、2\n");
StlPushFront(&pList, 4);
StlPushFront(&pList, 2);
printf("头插后链表:");
SltPrint(pList);
printf("\n3. 查找测试:查找 4、5\n");
SLTNode* findNode1 = SLTFind(pList, 4);
SLTNode* findNode2 = SLTFind(pList, 5);
printf("\n4. 指定位置之后插入测试:在 4 之后插入 6\n");
SLTNode* findNode3 = SLTFind(pList, 4); // 重新查找 4
if (findNode3 != NULL) {
SLTInsertAfter(findNode3, 6);
}
printf("插入后链表:");
SltPrint(pList);
printf("\n5. 指定位置之后删除测试:删除 4 之后的 6\n");
if (findNode3 != NULL) {
SLTEraseAfter(findNode3);
}
printf("删除后链表:");
SltPrint(pList);
printf("\n6. 尾删测试:删除末尾节点 1\n");
StlPopback(&pList);
printf("尾删后链表:");
SltPrint(pList);
printf("\n7. 头删测试:删除头部节点 2\n");
StlPopFront(&pList);
printf("头删后链表:");
SltPrint(pList);
printf("\n8. 指定位置删除测试:删除节点 4\n");
SLTNode* findNode4 = SLTFind(pList, 4); // 查找 4
if (findNode4 != NULL) {
SLTErase(&pList, findNode4);
}
printf("删除后链表:");
SltPrint(pList);
printf("\n9. 销毁链表测试:\n");
SLTDestory(&pList);
SltPrint(pList);
}
int main() {
Test(); // 执行所有测试
return 0;
}