链表分类
单向或双向
![![[Pasted image 20240918102853.png]]](https://i-blog.csdnimg.cn/direct/c45f130888e6428f86e6dd4635cc9189.png)
带头或不带头
![![[Pasted image 20240918102902.png]]](https://i-blog.csdnimg.cn/direct/f5663bfe822044fb87a9208fbc4d0a99.png)
循环或非循环
![![[Pasted image 20240918102912.png]]](https://i-blog.csdnimg.cn/direct/035666389cfc4c099dec0ebe831ca7ec.png)
合计有八种
带头双向循环链表
![![[Pasted image 20240918103005.png]]](https://i-blog.csdnimg.cn/direct/2b8d7cf82238410a91efde71d38750b4.png)
结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。使用代码实现,结构会带来很多优势,实现反而简单了。
![![[Pasted image 20240918222332.png]]](https://i-blog.csdnimg.cn/direct/8bc7444d9964417b8cfc2536e8a42244.png)
创建新节点
LTNode* BuyNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
初始化
LTNode* LTInit()
{
LTNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
初始化会修改结构体的内容,所以需要结构体指针,也可以用返回值来解决
- 通过BuyLTNode创建一个新节点
- 将phead节点的next指向自己
- prev指向自己
![![[Pasted image 20240918222347.png]]](https://i-blog.csdnimg.cn/direct/80ba381462a44a6bb5f425f8d5bfc2b4.png)
打印链表
void LTPrint(LTNode* phead)
{
assert(phead);
printf("phead<=>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
通过assert断言,判断phead是否合法
-
创建一个cur结构体指针,把phead->next赋给cur,也就是cur指向第一个节点
![![[Pasted image 20240918223548.png]]](https://i-blog.csdnimg.cn/direct/979e7f0113b9416c9bfed1f02c85f086.png)
-
把cur->next赋给cur,也就是cur指向cur的下一个节点,循环遍历直到cur等于头节点停止
![![[Pasted image 20240918223700.png]]](https://i-blog.csdnimg.cn/direct/f2cbd06452cd4256a5936d25e324c5fd.png)
只有头节点就不进入循环
尾插
尾插的时候不需要找尾节点,双向循环链表,头节点的prev指向尾节点,尾节点的next指向头节点
tail的next指向newnode,newnode的prev指向tail,newnode的next指向phead,phead的prev指向newnode
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = BuyLTNode(x);
newndode->prev = tail;
tail->next = newnode;
newnode->next = phead;
phead->prev = newnode;
}
通过assert判断phead指针是否为空
-
创建tail指针,通过phead的prev指针,找到最后一个节点
![![[Pasted image 20240918224853.png]]](https://i-blog.csdnimg.cn/direct/885af51784224ba69dd4814d8e30bb43.png)
-
通过BuyLTNode,malloc一个新的链表节点
![![[Pasted image 20240918224946.png]]](https://i-blog.csdnimg.cn/direct/91c46b538f064754abace52212ed3fa2.png)
-
将newnode节点与tail节点连接,将newnode的prev指向tail,tail的next指向newnode
![![[Pasted image 20240918225816.png]]](https://i-blog.csdnimg.cn/direct/02d14638c90e449f87284fdee46a8ec7.png)
-
将newnode节点与phead节点连接,将newnode的next指向phead,phead的prev指向newnode
![![[Pasted image 20240918225944.png]]](https://i-blog.csdnimg.cn/direct/15e33704847648bab13cf82813ca8d4a.png)
只有一个头节点的情况:
-
创建tail指针,malloc新节点
![![[Pasted image 20240919082022.png]]](https://i-blog.csdnimg.cn/direct/16b512d3d229418b8690e0a646eb17ac.png)
-
tail和phead是同一个节点,直接连接
![![[Pasted image 20240919082247.png]]](https://i-blog.csdnimg.cn/direct/2047e1ab214041738b8e80cfc27b18da.png)
尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
通过assert判断phead是否为空,是否只有phead一个节点,是个空链表
-
创建tail节点,通过phead->prev,找到最后一个节点
![![[Pasted image 20240919082749.png]]](https://i-blog.csdnimg.cn/direct/0ba2feebf02041de9504cd7f63c50ea5.png)
-
创建tailPrev节点,通过tail->prev,找到倒数第二个节点
![![[Pasted image 20240919082824.png]]](https://i-blog.csdnimg.cn/direct/44a57676fb9447bd8abf27d29da3cddb.png)
-
释放tial节点
![![[Pasted image 20240919082850.png]]](https://i-blog.csdnimg.cn/direct/ff5c323f55af44d99a3fb6bfb66e53d1.png)
-
连接tailPrev节点和phead
![![[Pasted image 20240919082931.png]]](https://i-blog.csdnimg.cn/direct/a8e654e15b9644a1a46617a2bfc9f54a.png)
头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyLTNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
先将newnode连接到第一个节点,再连接头节点和新节点newnode,不能更改顺序,否则会丢掉后面的链表
头插是在第一个节点的前面插入,不是在哨兵位的前面插入
通过assert判断phead是否为空
-
创建一个newnode节点
![![[Pasted image 20240919083214.png]]](https://i-blog.csdnimg.cn/direct/59d7cc78d9fe4397928b06b03d56821d.png)
-
将newnode与第一个节点连接
![![[Pasted image 20240919083421.png]]](https://i-blog.csdnimg.cn/direct/d9202e974f674ce5b5d014bc74496e4d.png)
-
将newnode与phead
![![[Pasted image 20240919083455.png]]](https://i-blog.csdnimg.cn/direct/fa98e8f225d8434686832a4dcc845f85.png)
只有一个phead节点的情况
由于phead和phead->prev是同一个节点,所以直接连接phead和newnode节点
-
创建一个newnode节点
![![[Pasted image 20240919083832.png]]](https://i-blog.csdnimg.cn/direct/a3336e7d2f3d48d5b1b36d32f1edef2e.png)
-
newnode的next指向phead,phead的prev指向newnode
![![[Pasted image 20240919084009.png]]](https://i-blog.csdnimg.cn/direct/1fde71ca37cf4ea7b3df6a7bb09fb128.png)
-
phead的next指向newnode,newnode的prev指向phead
![![[Pasted image 20240919084133.png]]](https://i-blog.csdnimg.cn/direct/8880d446d6e942ac821c0dc50ea2bf05.png)
通过创建一个first节点来代表第一个节点,这样连接就不用在意连接顺序,不怕丢掉后续链表
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyLTNode(x);
LTNode* first = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
}
-
创建newnode和first,将phead->next赋给first,first指向第一个节点
![![[Pasted image 20240919084316.png]]](https://i-blog.csdnimg.cn/direct/c5667b0b06764b2fb8b11de8d26498b3.png)
-
再继续连接phead和newnode,newnode和first,同上
头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* first = phead->next;
LTNode* second = first->next;
free(first);
phead->next = second;
second->prev = phead;
}
通过assert判断phead是否为空,是否为空链表
-
创建first和second指针,分别指向第一个和第二个节点
![![[Pasted image 20240919084535.png]]](https://i-blog.csdnimg.cn/direct/10732d2491ce4a81b7cb51f9f5d8062d.png)
-
释放掉first,连接phead和second
![![[Pasted image 20240919084635.png]]](https://i-blog.csdnimg.cn/direct/313aff21fa4848a39512c08b26da8b34.png)
返回大小
int LTSize(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}
- assert判断phead是否合法
- 通过cur指针遍历链表
- 每遍历一个节点,size++,直到cur为phead
- 最后返回size
可以在哨兵位节点的data里存放size,插入时data++,删除时data–,求大小时,直接返回phead的data
但是只有确定链表的data数据类型是int才可以使用,其他数据类型无法实现
如果真要用size来记录,应该在函数外定义
查找x
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
STNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
- 通过assert判断phead是否为空
- 通过cur指针遍历链表
- 当cur指向节点的data等于x,返回cur指针
- 如果没找到,返回NULL
pos之前插入x
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newnode = BuyLTNode(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
-
assert判断pos指针是否合法
-
将pos->prev赋给posPrev指针,找到pos前一个节点
-
newnode一个节点
![![[Pasted image 20240919085232.png]]](https://i-blog.csdnimg.cn/direct/ed56aa4b485644e6add72834453492e6.png)
-
连接newnode与posPrev节点
![![[Pasted image 20240919085315.png]]](https://i-blog.csdnimg.cn/direct/3692ec7b192b4d0dbdcb67a71fa91930.png)
-
连接pos和newnode
![![[Pasted image 20240919085356.png]]](https://i-blog.csdnimg.cn/direct/070ccf6fe80e4cccbb2e7fb07b4feedb.png)
之前的头插尾插都可以用Insert复用
LTInsert(phead->next, x); //头插
LTInsert(phead, x); //尾插
删除pos位置
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
free(pos);
posPrev->next = posNext;
posNext->prev = posPrev;
}
-
assert判断pos是否为空
-
创建posPrev和posNext找到pos前一个和后一个节点
![![[Pasted image 20240919085552.png]]](https://i-blog.csdnimg.cn/direct/512fbd1c74004ef2a2b52a2b76e7ce9d.png)
-
释放掉pos
![![[Pasted image 20240919085609.png]]](https://i-blog.csdnimg.cn/direct/03426c21689443a5849633db7b2500e0.png)
-
连接posPrev和posNext
![![[Pasted image 20240919085627.png]]](https://i-blog.csdnimg.cn/direct/602ed60be4da4a2aa4f964fbe99a8f1b.png)
LTErase(phead->next); //头删
LTErase(phead->prev); //尾删
销毁
void LTDestroy(LTNode* phead)
{
assert(phead);
STNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
- assert判断phead是否为空
- 通过cur指针遍历整个链表
- 每次循环,创建一个next指针,指向cur节点的下一个,释放掉cur节点,将cur指针指向next,当cur为phead时结束
- 最后释放掉phead节点
函数内没有用到二级指针,形参不能改变实参,可以在函数外调用后再phead置空
顺序表和链表的区别
![![[Pasted image 20240918215558.png]]](https://i-blog.csdnimg.cn/direct/af375e11b82e46d881cc5750c2dde5e2.png)
![![[Pasted image 20240918220042.png]]](https://i-blog.csdnimg.cn/direct/7c44d3a2898b4141a40c168189aae19a.png)
存储器层次结构
声明定义分离实现
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
LTNode* BuyNode(LTDataType x);
LTNode* LTInit();
void LTPrint(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);
int LTSize(LTNode* phead);
//pos之前插入xLTNode* LTFind(LTNode* phead, LTDataType x);
void LTInsert(LTNode* pos, LTDataType x);
//删除pos位置
void LTErase(LTNode* pos);
void LTDestroy(LTNode* phead);
include "List.h"
LTNode* BuyNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
LTNode* LTInit()
{
LTNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
void LTPrint(LTNode* phead)
{
assert(phead);
printf("phead<=>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = BuyLTNode(x);
newndode->prev = tail;
tail->next = newnode;
newnode->next = phead;
phead->prev = newnode;
}
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;
}
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyLTNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
LTNode* first = phead->next;
LTNode* second = first->next;
free(first);
phead->next = second;
second->prev = phead;
}
int LTSize(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
++size;
cur = cur->next;
}
return size;
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
STNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newnode = BuyLTNode(x);
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
free(pos);
posPrev->next = posNext;
posNext->prev = posPrev;
}
void LTDestroy(LTNode* phead)
{
assert(phead);
STNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
}
1554

被折叠的 条评论
为什么被折叠?



