前言:内容包括:链表的分类,无头单向非循环链表的增删查改的实现,带头双向循环链表的增删查改的实现
目录
链表的分类
1. 单向或者双向
编辑
2. 带头或者不带头
3. 循环或者非循环
无头单向非循环链表:
编辑 带头双向循环链表:
链表的实现
1、无头+单向+非循环链表增删查改实现
单链表结构:
动态申请一个节点:
单链表打印:
单链表销毁:
单链表尾插:
单链表的头插:
单链表的尾删:
单链表头删 :
单链表查找:
在某个节点之前插入:
在某个节点之后插入:
删除pos位置的值:
删除pos之后的值:
2、带头+双向+循环链表增删查改实现
双向链表结构:
动态申请一个节点:
双向链表初始化(创建哨兵位的头结点):
双向链表销毁:
双向链表打印 :
双向链表尾插 :
双向链表头插:
双向链表尾删 :
双向链表头删 :
双向链表查找 :
双向链表在pos的前面进行插入:
双向链表删除pos位置的结点 :
链表的分类
组合起来共有8种结构:
1. 单向或者双向
双向 :
2. 带头或者不带头
不带头:
带头(哨兵位的头结点):
哨兵位的头结点不存储有效数据,站岗功能
3. 循环或者非循环
非循环:
循环:
虽然链表的组合有8种结构,但是最常用还是一下两种结构:
无头单向非循环链表:
一般不会单独用来存数据,更多是作为其他数据结构的子结构,如哈希桶、图的邻接表
带头双向循环链表:
一般用来单独存数据
链表的实现
1、无头+单向+非循环链表增删查改实现
在main函数种,plist指针作为头指针,负责在单链表创建后,指向单链表的第一个节点
SLTNode* plist = NULL;
单链表结构:
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
动态申请一个节点:
SLTNode* BuySLTNode(SLTDataType x);
malloc一块空间并存入数据后,此函数将会返回这块空间的地址
SLTNode* BuySLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail");//若malloc开辟失败,打印错误信息
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
单链表打印:
void SLTPrint(SLTNode* phead);
void SLTPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
单链表销毁:
void SLTDestroy(SLTNode** pphead);
当链表的每个节点销毁完成后,指向第一个节点的指针plist需要置空,所以传入plist指针的地址(二级指针接收),plist必须置空,否则成为野指针
void SLTDestroy(SLTNode** pphead)
{
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
在释放一个节点之前,需要找到下一个节点,所以先用next指针保存下一个节点的地址,然后才能放心销毁当前节点
单链表尾插:
void SLTPushBack(SLTNode** pphead, SLTDataType x);
尾插分为两种情况:
空链表尾插 和 非空链表尾插
空链表尾插:需要改变头指针plist,使得plist指向插入的第一个节点
所以存在对plist指针进行修改的情况,故而需要传入plist的指针,用二级指针pphead来接收
非空链表尾插: 遍历找到尾节点进行链接
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);//pphead是头指针plist的地址一定不为空,用assert进行断言,若是pphead为空会报错
SLTNode* newnode = BuySLNode(x);
if (*pphead == NULL)//空链表尾插
{
*pphead = newnode;//plist需要指向第一个节点
}
else//非空链表尾插
{
SLTNode* tail = *pphead;
while (tail->next)//找到尾节点,尾节点的next就是NULL
{
tail = tail->next;
}
tail->next = newnode;//链接
}
}
关于assert断言:
pphead一定不能为空,所以需要断言,当pphead为空时,报错
*pphead可以为空,这表明链表为空,空链表是可以插入数据的,打个比方:
银行卡里没钱了,存钱是可以的
单链表的头插:
void SLTPushFront(SLTNode** pphead, SLTDataType x);
头插也分两种:
空链表头插:需要对plist指针进行修改,使得它指向插入的第一个节点
非空链表头插:1 新节点链接头结点 2 plist指针指向新节点(新节点成为新的头结点)
但是这两种情况下的头插方法都是通用的,空链表头插代码可以与非空链表头插共用一份代码
原因:空链表时,plist==NULL,即*pphead==NULL
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuySLNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
单链表的尾删:
void SLTPopBack(SLTNode** pphead);
尾删分两种情况:
链表仅有1个节点:删除此节点后,plist指针需要置空
链表不只1个节点:找到尾节点的前一个结点,当尾结点删除后,它将成为新的尾结点,则其成员next指针需要置空
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);//链表为空不能删
if ((*pphead)->next == NULL)//链表仅有一个结点,它的next就是NULL
{
free(*pphead);
*pphead = NULL;//plist指针置空
}
else
{
SLTNode* tail = *pphead;
while (tail->next->next)//找到尾结点的前一个结点,它将会是新的tail,则tail->next就是原来的尾结点,原来的尾结点的next指针就是NULL
{
tail = tail->next;
}
free(tail->next);//删除尾结点
tail->next = NULL;//新的尾结点的next要置空
}
}
单链表头删 :
void SLTPopFront(SLTNode** pphead);
头删也分两种情况:
链表仅有1个节点:删除此节点后,plist指针要置空
链表不止1个节点:1 保存要删除的节点地址 2 头指针plist指向新的头结点 3 删除节点
但是这两种情况也共用一份代码
原因:当链表仅有1个节点,它没有下一个节点,所以它的next指针指向了NULL,
当删除这个节点后,plist会置成NULL
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);//链表为空不能删除
SLTNode* del = *pphead;//保存要删除的节点地址
*pphead = (*pphead)->next;//plist指向新的头结点
free(del);
}
单链表查找:
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
在某个节点之前插入:
1 使用单链表查找函数找到某个节点的地址pos
2 在pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
使用二级指针pphead:若是pos是第一个结点,则在pos之前插入就是头插逻辑,会对plist指针进行修改,所以需要传plist的地址
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (*pphead == pos)//pos是头结点
{
SLTPushFront(pphead,x);//在头结点之前插入即头插
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)//找到pos节点的前一个结点prev
{
prev = prev->next;
}
SLTNode* newnode = BuySLNode(x);
prev->next = newnode;//链接
newnode->next = pos;
}
}
在某个节点之后插入:
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
不需要传入plist,因为在pos之后插入,可以轻而易举得到pos之后所有结点的地址
传入plist,可以认为需要找到某一个结点的前一个结点才需要plist,因为通过遍历才可以找到某个结点的前一个结点
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySLNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
删除pos位置的值:
void SLTErase(SLTNode** pphead, SLTNode* pos)
存在删除的pos位置是头结点的情况,需要对plist进行修改,所以需要二级指针
无需assert(*pphead)
因为assert(pos)已经保证了pos位置的有效,也保证链表不为空,若是pos位置有效(即不为NULL),则说明链表不会为空
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (*pphead == pos)//头删
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)//找到pos的前一个结点
{
prev = prev->next;
}
prev->next = pos->next;//前一个结点的next指向pos的下一个结点
free(pos);
}
}
删除pos之后的值:
void SLTEraseAfter(SLTNode* pos);
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);//不能对NULL进行删除操作,若是pos是尾结点的地址,则pos->next为NULL
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
}
2、带头+双向+循环链表增删查改实现
双向链表结构:
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
动态申请一个节点:
LTNode* BuyLTNode(LTDataType x)
LTNode* BuyLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
双向链表初始化(创建哨兵位的头结点):
LTNode* LTInit();
开辟一个结点,不存储任何有效数据,作为哨兵位的头结点
当链表为空时,这个哨兵位的头结点需要自己构成一个循环,即自己的next指向自己,自己的prev指向自己
LTNode* LTInit()
{
LTNode* phead = BuyLTNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
双向链表销毁:
void LTDestroy(LTNode* phead);
释放完所有的有效节点后才能释放哨兵位的头结点
void LTDestroy(LTNode* phead)
{
assert(phead);//phead是指向哨兵位的头结点的指针,一定不能为空
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;//记录下一个要释放节点的地址
free(cur);
cur = next;
}
free(phead);//释放哨兵位的头结点
}
双向链表打印 :
void LTPrint(LTNode* phead);
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("guard<==>");
while (cur != phead)
{
printf("%d<==>", cur->data);
cur = cur->next;
}
printf("\n");
}
双向链表尾插 :
void LTPushBack(LTNode* phead, LTDataType x);
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* tail = phead->prev;
LTNode* newnode = BuyLTNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
//或者
//LTInsert(phead,x);
}
1 尾节点的next指向newnode
2 newnode的prev指向尾节点
3 newnode的next指向哨兵位的头结点
4 哨兵位头结点的prev指向newnode
双向链表头插:
void LTPushFront(LTNode* phead, LTDataType x);
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* first = phead->next;//哨兵位的头结点后的真正的头结点
LTNode* newnode = BuyLTNode(x);
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
//或者
//LTInsert(phead->next, x);
}
双向链表尾删 :
双向链表判空:链表若为空,则仅有一个哨兵位的头结点,它既是头,也是尾
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
void LTPopBack(LTNode* phead)
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));//链表为空不能删
LTNode* tail = phead->prev;
LTNode* tailprev = tail->prev;//找到尾节点的上一个节点,它将成为新的尾节点
free(tail);
tailprev->next = phead;//新的尾节点要连接哨兵位的头结点
phead->prev = tailprev;
//或者
//LTErase(phead->prev);
}
双向链表头删 :
void LTPopFront(LTNode* phead)
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));//链表为空不能删
LTNode* first = phead->next;
LTNode* second = first->next;//真正头结点的下一个节点second会成为新的真正头结点,链接哨兵位的头结点
phead->next = second;
second->prev = phead;
free(first);
//或者
//LTErase(phead->next);
}
双向链表查找 :
LTNode* LTFind(LTNode* phead, LTDataType x)
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
双向链表在pos的前面进行插入:
pos是某个节点的地址,可以通过查找函数得到
void LTInsert(LTNode* pos, LTDataType x)
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = BuyLTNode(x);
LTNode* prev = pos->prev;//找到pos的前一个结点
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
双向链表删除pos位置的结点 :
void LTErase(LTNode* pos)
void LTErase(LTNode* pos)
{
assert(pos);
LTNode* posPrev = pos->prev;//找到pos的前一个结点
LTNode* posNext = pos->next;//找到pos的后一个结点
posPrev->next = posNext;//两个链接
posNext->prev = posPrev;
free(pos);
}