一.链表的分类
先前我们已经讲了单链表,他的全名其实是不带头单向不循环链表,实际上链表有8种。
1.带头or不带头
带头比不带头多了一个头节点head,在头节点head中不存储数据,只是当一个坐标,从d1开始才存储数据。
2.单向or双向
可以明显发现,在一个节点中,双向链表比单向多了一个指针,这个指针指向前一个结点。
3.循环or不循环
循环链表像一个圆圈一样,首尾相连。
二.实现带头双向循环链表
在上一篇文章中,我详细介绍了单向链表(不带头单向不i循环链表),现在我再来讲解一下双向链表(带头双向循环链表)
2.1 头文件(包含要实现的功能与创建一个节点)
首先来创建一个新节点,这个节点有数据域和指针域,不同的是它包含两个指针,一个指向下一个节点(next),一个指向上一个节点(prev)
typedef int SLDataType;
typedef struct SLNode {
SLDataType data;
struct SLNode* next;
struct SLNode* prev;
}SLNode;
即将实现的功能
//要实现的功能!
//链表哨兵的初始化
//注意带头链表是有哨兵位的,所以插入数据之前必须要初始化一个哨兵位
SLNode* SLInit();
//哨兵位中没有数据,不需改变哨兵位,不用穿二级指针
//如果需要改变哨兵位,则传二级指针
void SLDestory(SLNode* phead);
//打印链表
void SLPrint(SLNode* phead);
//链表的头部插入和尾部插入
void SLPushFront(SLNode* phead,SLDataType x);
void SLPushBack(SLNode* phead,SLDataType x);
//链表的头删和尾删
void SLPopBack(SLNode* phead);
void SLPopFront(SLNode* pphead);
//链表的查找
SLNode* SLFind(SLNode* phead, SLDataType x);
//在pos位置之后插入数据
void SLInsert(SLNode* phead, SLNode* pos , SLDataType x);
//删除指定位置的数据
void SLErase(SLNode* phead, SLNode* pos);
//链表的销毁
void SLDestory(SLNode* phead);
2.2 实现文件
2.2.1 创建头节点
头节点是我们的带头双向循环链表的第一个节点,如何初始化他的next指针和prev指针呢?用NULL吗,显然不合理,因为它是循环的,所以目前他的prev指针与next指针指向的都是自己,基于此,我们对其进行初始化
void SLInit(LTNode* phead) {
phead = (LTNode*)malloc(sizeof(LTNode));
if (phead == NULL) {
perror("malloc fail!");
exit(1);
}
(phead)->data = -1;
(phead)->next = (phead)->prev = phead;
}
注意一点:我们这里传过来的不是二级指针,而是一级指针!
我们只需要更改结构体中next与prev的指向,不需改变头节点plist的指向,所以我们不需要传递二级指针。
2.2.2 创建一个空节点
因为要实现的插入新节点都需要创建一个空节点,为保持代码的简洁,我们可以封装一个函数
//创建一个新节点
SLNode* BuyNewnode(int x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("set newnode");
exit(1);
}
newnode->data = x;
newnode->next = newnode->prev = newnode;
return newnode;
}
2.2.3 头部插入与尾部插入
1.尾部插入
我们只需要一步一步的更改图中的箭头指向即可。但注意要按一定顺序,否则可能出现错误,建议画图。代码如下
void SLPushBack(SLNode* phead,SLDataType x)
{
assert(phead);
SLNode* newnode = BuyNewnode(x);
//不需要循环遍历找到尾部节点啦 phead->prev 就是尾节点
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
2.头部插入
还是一个一个的更改箭头指向即可
void SLPushFront(SLNode* phead,SLDataType x )
{
assert(phead);
SLNode* newnode = BuyNewnode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
2.2.4 链表的头删和尾删
1.头删
这里我们要搞清楚头删的定义------>是指删除头节点后的第一个存有数据的节点.
void SLPopFront(SLNode* phead)
{
assert(phead);
//不能只有头节点
assert(phead->next != phead);
SLNode* pdel = phead->next;
pdel->next->prev = phead;
phead->next = pdel->next;
free(pdel);
pdel = NULL;
}
2.尾部删除
代码如下:
void SLPopBack(SLNode* phead)
{
assert(phead);
//链表不能为空(这里为空的意思是没有数据,只有头节点)
assert(phead->next != phead);
SLNode* ptail = phead->prev;
ptail->prev->next = phead;
phead->prev = ptail->prev;
free(ptail);
ptail = NULL;
}
2.2.5 查找
注意了查找头节点没啥用,应该从第二个开始查找,所以链表是不能为空的
SLNode* SLFind(SLNode* phead, SLDataType x)
{
//遍历链表查找
assert(phead);
assert(phead->next);
//注意了查找头节点没啥用,应该从第二个开始查找,所以链表不能为空
SLNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
这里返回了被查找数据data的指针,方便对下面对指定位置增加/删除数据的代码的检验。
2.2.6 在指定位置之后插入数据
//在pos位置之后插入数据
void SLInsert(SLNode* phead, SLNode* pos, SLDataType x)
{
//pos只要存在,链表就不可能为空
assert(pos);
SLNode* pf = pos->next;
SLNode* newnode = BuyNewnode(x);
newnode->prev = pos;
newnode->next = pf;
pos->next = newnode;
pf->prev = newnode;
}
2.2.7 删除指定位置的数据
void SLErase(SLNode* phead, SLNode* pos)
{
assert(pos);
assert(pos != phead);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
这里注意了,不能删除头节点,头结点处没有数据。
2.2.7 销毁链表
遍历链表进行释放,分为两种情况,只有头节点/有其他节点
void SLDestory(SLNode* phead) {
assert(phead);
//遍历链表,一个一个释放】
SLNode* pcur = phead->next;
while (pcur != phead)//直接跳过了头节点,从第二个结点开始,如果只有头节点,此时pcur == phead;
{
SLNode* next = pcur->next;
free(pcur);
pcur = next;
}
//若是只有头节点
free(phead);
phead = NULL;
}
2.3 测试文件
#include "List.h"
void SLtest()
{
//初始化头节点
SLNode* plist = SLInit();//prev与next与plist地址相同
SLPushBack(plist, 1);
SLPushBack(plist, 2);
SLPushBack(plist, 3);
SLPushBack(plist, 4);
SLPrint(plist);
SLPushFront(plist, 5);
SLPushFront(plist, 6);
SLPrint(plist);
/*SLPopBack(plist);
SLPopBack(plist);*/
SLPopFront(plist);
SLPopFront(plist);
SLPrint(plist);
SLNode* pr = SLFind(plist, 4);
/*SLInsert(plist, pr, 6);
SLPrint(plist);*/
/*if (pr == NULL)
{
printf("没找到\n");
}
else
{
printf("找到了\n");
}*/
//SLErase(plist, plist);
//SLPrint(plist);
void SLDestory(plist);
SLPrint(plist);
}
int main()
{
SLtest();
return 0;
}
三.全部代码🐎🐎
//头文件List.h
#pragma once
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
//我们之前写的是不带头单向不循环链表,实际上链表分为8种
//1. 带头 不带头
//2. 单向 双向
//3. 循环 不循环
//今天我们来写一个带头!双向!循环链表!
//第一步--》先创建一个节点!
typedef int SLDataType;
typedef struct SLNode {
SLDataType data;
struct SLNode* next;
struct SLNode* prev;
}SLNode;
//要实现的功能!
//链表哨兵的初始化
//注意带头链表是有哨兵位的,所以插入数据之前必须要初始化一个哨兵位
SLNode* SLInit();
//哨兵位中没有数据,不需改变哨兵位,不用穿二级指针
//如果需要改变哨兵位,则传二级指针
void SLDestory(SLNode* phead);
//打印链表
void SLPrint(SLNode* phead);
//链表的头部插入和尾部插入
void SLPushFront(SLNode* phead,SLDataType x);
void SLPushBack(SLNode* phead,SLDataType x);
//链表的头删和尾删
void SLPopBack(SLNode* phead);
void SLPopFront(SLNode* pphead);
//链表的查找
SLNode* SLFind(SLNode* phead, SLDataType x);
//在pos位置之后插入数据
void SLInsert(SLNode* phead, SLNode* pos , SLDataType x);
//删除指定位置的数据
void SLErase(SLNode* phead, SLNode* pos);
//链表的销毁
void SLDestory(SLNode* phead);
//实现文件List.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
//创建一个新节点
SLNode* BuyNewnode(int x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("set newnode");
exit(1);
}
newnode->data = x;
newnode->next = newnode->prev = newnode;
return newnode;
}
//链表的打印
void SLPrint(SLNode* phead)
{
SLNode* pcur = phead;
while (pcur->next != phead)
{
pcur = pcur->next;
printf("%d->", pcur->data);
}
printf("\n");
}
//链表的初始化与销毁
//注意带头链表是有哨兵位的,所以插入数据之前必须要初始化一个哨兵位
SLNode* SLInit()
{
SLNode* phead = BuyNewnode(-1);
return phead;
}
//链表的头部插入和尾部插入
void SLPushFront(SLNode* phead,SLDataType x )
{
assert(phead);
SLNode* newnode = BuyNewnode(x);
newnode->next = phead->next;
newnode->prev = phead;
phead->next->prev = newnode;
phead->next = newnode;
}
void SLPushBack(SLNode* phead,SLDataType x)
{
assert(phead);
SLNode* newnode = BuyNewnode(x);
//不需要循环遍历找到尾部节点啦 phead->prev 就是尾节点
newnode->next = phead;
newnode->prev = phead->prev;
phead->prev->next = newnode;
phead->prev = newnode;
}
//链表的头删和尾删
//无需改变头节点!
void SLPopBack(SLNode* phead)
{
assert(phead);
//链表不能为空(这里为空的意思是没有数据,只有头节点)
assert(phead->next != phead);
SLNode* ptail = phead->prev;
ptail->prev->next = phead;
phead->prev = ptail->prev;
free(ptail);
ptail = NULL;
}
//这里我们要搞清楚头删的定义--》是指删除头节点后的第一个存有数据的节点
void SLPopFront(SLNode* phead)
{
assert(phead);
//不能只有头节点
assert(phead->next != phead);
SLNode* pdel = phead->next;
pdel->next->prev = phead;
phead->next = pdel->next;
free(pdel);
pdel = NULL;
}
//链表的查找
SLNode* SLFind(SLNode* phead, SLDataType x)
{
//遍历链表查找
assert(phead);
assert(phead->next);
//注意了查找头节点没啥用,应该从第二个开始查找,所以链表不能为空
SLNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//在pos位置之后插入数据
void SLInsert(SLNode* phead, SLNode* pos, SLDataType x)
{
//pos只要存在,链表就不可能为空
assert(pos);
SLNode* pf = pos->next;
SLNode* newnode = BuyNewnode(x);
newnode->prev = pos;
newnode->next = pf;
pos->next = newnode;
pf->prev = newnode;
}
//删除指定位置的数据
void SLErase(SLNode* phead, SLNode* pos)
{
assert(pos);
assert(pos != phead);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
//链表的销毁
void SLDestory(SLNode* phead) {
assert(phead);
//遍历链表,一个一个释放】
SLNode* pcur = phead->next;
while (pcur != phead)//直接跳过了头节点,从第二个结点开始,如果只有头节点,此时pcur == phead;
{
SLNode* next = pcur->next;
free(pcur);
pcur = next;
}
//若是只有头节点
free(phead);
phead = NULL;
}
//测试文件Test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
void SLtest()
{
//初始化头节点
SLNode* plist = SLInit();//prev与next与plist地址相同
SLPushBack(plist, 1);
SLPushBack(plist, 2);
SLPushBack(plist, 3);
SLPushBack(plist, 4);
SLPrint(plist);
SLPushFront(plist, 5);
SLPushFront(plist, 6);
SLPrint(plist);
/*SLPopBack(plist);
SLPopBack(plist);*/
SLPopFront(plist);
SLPopFront(plist);
SLPrint(plist);
SLNode* pr = SLFind(plist, 4);
/*SLInsert(plist, pr, 6);
SLPrint(plist);*/
/*if (pr == NULL)
{
printf("没找到\n");
}
else
{
printf("找到了\n");
}*/
//SLErase(plist, plist);
//SLPrint(plist);
void SLDestory(plist);
SLPrint(plist);
}
int main()
{
SLtest();
return 0;
}
四。结束啦!一起进步,越来越好!