单链表
概念:链表是⼀种物理存储结构 上非连续、非顺序 的存储结构,数据元素的逻辑顺序 是通过链表中的指针链接次序实现的。
与顺序表不同的是,链表申请的每个空间称为节点, 结点的组成主要有两个部分:当前结点要保存的数据 和保存下⼀个结点的地址(指针变量)。
链表中每个结点都是独立申请的(即需要插入数据时才去申请⼀块结点的空间),我们需要通过指针变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。
当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数
据,也需要保存下⼀个结点的地址(当下⼀个结点为空时保存的地址为空)。
定义链表结构
cpp
//定义链表的结构---节点的结构
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;//存储的数据
struct SListNode* next;//指向下一个节点的指针,不能用简写,程序还未定义
}SLTNode;
打印链表
cpp
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d -> ", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
pcur=pcur->next:
1)pcur指针变量保存第一个节点的地址
2)对pcur解引用拿到next指针变量中的地址(下一个节点的地址)
3)赋值给pcur,此时pcur保存的地址为0x0012FFAO,即pcur"指向了下一个节点"

实现单链表
cpp
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//定义链表的结构---节点的结构
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;//存储的数据
struct SListNode* next;//指向下一个节点的指针,不能用简写,程序还未定义
}SLTNode;
//只有看不到&,就是传值操作
//
//链表的打印
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);

创建x节点
cpp
//根据x创建节点
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;
}
尾插
cpp
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = SLTbuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
头插
cs
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTbuyNode(x);
//newnode *pphead
newnode->next = *pphead;
*pphead = newnode;
}
尾删
cpp
//尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
//只有一个结点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else {
SLTNode* prev = NULL;
SLTNode* ptail = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
//prev ptail
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
头删
cpp
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
查找
cpp
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
指定位置前插入数据
cpp
//在指定位置pos之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && pos);
//当pos指向第一个节点,头插
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newnode = SLTbuyNode(x);
//找pos的前一个节点
SLTNode* prev = *pphead;//需要从头遍历
while (prev->next != pos)
{
prev = prev->next;
}
//prev newnode pos联系起来
prev->next = newnode;
newnode->next = pos;
/*newnode = prev->next;
pos = newnode->next;*/
}
}
*1.prev->next = newnode; newnode->next = pos;
是链表插入新节点的典型操作:
prev->next = newnode;
:让 prev
原本指向的节点(假设是 A
),改为指向新节点 newnode
,断开 prev
到 pos
的旧连接。
newnode->next = pos;
:让新节点 newnode
指向 pos
,把 pos
接在新节点后面,完成插入。
这一步的核心是先改变 prev
的指向,再用新节点连接原后继 pos
,最终链表结构会变成 ... -> prev -> newnode -> pos -> ...
。
*2.newnode = prev->next; pos = newnode->next;
则是单纯的指针赋值,没有修改链表结构:
newnode = prev->next;
:把 prev
原本指向的后继节点(比如 A
)的地址,赋给 newnode
,此时 newnode
和 prev->next
指向同一个节点。
pos = newnode->next;
:再把 newnode
原本的后继(即 prev
原来的后继的后继)赋给 pos
,本质是用指针 "抄" 了一遍链表的连接关系 ,但没改变链表实际结构。
指定位置后插入数据
cpp
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)//不需要找pos前面的指针,故只需要两个变量
{
assert(pos);
SLTNode* newnode = SLTbuyNode(x);
//pos newnode pos->next
newnode->next = pos->next;
pos->next = newnode;
}
删除指定位置节点
cpp
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && 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;
}
}
删除指定位置后一个节点
cpp
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
//pos del del->next
SLTNode* del = pos->next;
pos->next = del->next;
free(del);
del = NULL;
}
销毁链表
cpp
//销毁链表
void SListDestroy(SLTNode** pphead)
{
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}