一、代码解析
1. 节点结构、双链表结构定义
a. Node
结构体:
-
每个节点包含一个数据域(
data
)和两个指针域(perv
和next
)。 -
perv
指向当前节点的前一个节点,next
指向当前节点的后一个节点。 -
这种双向指针结构使得双链表可以同时向前和向后遍历。
b .DoubleLinkedList
结构体:
-
head
:用于快速访问链表的开头。 -
tail
:用于快速访问链表的末尾。 -
通过维护head和tail指针,可以快速访问链表头尾,并且很大程度上简化了插入、删除等操作。
2.初始化
a.函数定义
DoubleLinkedList* list
是指向双链表结构体的指针, 返回类型为void。
b. 函数逻辑功能
①检查传递的指针为空指针,防止程序崩溃,并且将错误信息返回,提示初始化失败。
②初始化链表,将链表的head
和tail
指针都设置为NULL
,表示链表为空,即初始化状态没有节点。
③将初始化成功信息打印输出。
3.后插插入操作
a. 函数定义
参数:
list:指向双链表结构体的指针。
data:用户输入的数据。
b. 函数逻辑功能
①分配内存
使用malloc函数向内存申请了大小为结构体Node的一块空间,并且在返回类型void*强制转换成Node*,赋值给新的结构体指针变量newNode。
②检查内存分配是否成功
用fi()语句判断,newNode是否被赋予NULL,如果newNode
为NULL
,说明内存分配失败,打印错误信息并返回。
③初始化新节点
-
将新节点的
data(用户输入的数据)
设置为传入的data
参数。 -
初始化
perv
和next
指针为NULL
,因为新节点暂时没有前驱和后继。可理解为第一个节点即最后一个节点。
④插入新节点
-
如果链表为空(
head
为NULL
),将新节点同时设置为头节点和尾节点。 -
如果链表不为空: 👉 当前尾节点的
next
指针指向新节点。
👉 新节点的perv
指针指向当前尾节点。
👉 更新tail
指针为新节点。
图形解释:切记语句顺序不可颠倒!!!
⑤打印成功信息
打印一条信息,提示用户数据已成功插入到链表尾部。
4.删除操作
a. 函数定义
功能:👉 从双链表中删除包含特定数据的节点。
-
参数:
-
list
:指向双链表结构体的指针。 -
data
:要删除的节点数据。
-
b. 函数逻辑功能
①检查此前链表是否为空
很有可能用户并没有输入插入数据,链表为空,程序执行空语句是切记不运行的。如果链表为空(head
为NULL
),打印错误信息并返回。
②遍历链表查找目标节点
从头节点开始,利用while循环,只要链表不为NULL就遍历链表直到找到包含data
的节点。如果遍历完整个链表都没有找到目标节点,打印删除失败的信息。
③删除节点
根据目标节点的位置(头节点、尾节点或中间节点),执行不同的删除操作:
条件1:❀删除唯一节点
如果节点的前驱指针和后驱指针同样都是指向NULL,则说经既是头节点又是尾节点(即链表只有一个节点),随后将head
和tail
都设为NULL
。即完成删除操作。
条件2:❀❀ 删除头结点
如果节点的前驱节点指向为NULL,则说明是头节点,随后更新head将其设置为
当前节点a的下一个节点b,并将新头节点的perv
指针设为NULL
。
条件3:❀❀❀删除尾节点
如果节点的后驱指针指向为NULL,则说明是尾节点,更新tail将其设置
为当前节点b的前驱节点a,并将新尾节点的next
指针设为NULL
。
图示:即上图删除next。
条件4:❀❀❀❀删除中间节点
如果节点是中间节点,调整前驱节点的next
指针和后继节点的perv
指针,绕过当前节点。
④释放内存并打印成功信息
如果遍历完整个链表都没有找到目标节点,打印删除失败的信息。
5. 释放链表内存
a.函数定义
-
功能:释放双链表占用的内存。
-
参数:
-
list
:指向双链表结构体的指针。
-
b.函数逻辑功能
①初始化指针
current 指针初始化为链表的头节点。
temp 指针用于临时保存当前节点,以便释放内存。
②遍历链表并释放内存
使用while
循环遍历链表,直到current
为NULL
在每次循环中:
将current
的值赋给临时创建的结构体指针空间temp
,保存当前节点的引用。
将current
移动到下一个节点。
使用free
释放temp
指向的节点内存。
③ 清理链表头尾指针
将链表的head
和tail
指针都设为NULL
,表示链表已清空。
④最后打印释放成功信息。
6.菜单函数
利用printf()函数打印简单的菜单功能即可。
7. main函数
①变量的定义
-
list
:定义一个双链表结构体变量,用于存储链表。 -
choice
:存储用户选择的菜单选项。 -
data
:存储用户输入的数据。 -
initialized
:标志变量,用于判断链表是否已初始化。
②主循环
-
使用
while (1)
创建一个无限循环,持续显示菜单并处理用户输入。 -
调用
Menu()
函数显示操作菜单。 -
使用
scanf_s
读取用户输入的菜单选项。
③使用switch
语句调用相应的函数
选项1:初始化链表
-
调用
initList
函数初始化链表。 -
将
initialized
标志设为1,表示链表已初始化。
选项2:后插插入数据
-
检查链表是否已初始化,未初始化则提示用户。
-
提示用户输入要插入的数据。
-
调用
insertAtEnd
函数将数据插入链表尾部。 -
调用
traverseList
函数遍历并打印链表内容。
选项3:删除数据
-
检查链表是否已初始化,未初始化则提示用户。
-
提示用户输入要删除的数据。
-
调用
deleteNode
函数删除链表中的数据。 -
调用
traverseList
函数遍历并打印链表内容。
选项4:遍历链表
-
检查链表是否已初始化,未初始化则提示用户。
-
调用
traverseList
函数遍历并打印链表内容。
选项5:退出程序
-
如果链表已初始化,调用
freeList
函数释放链表内存。 -
打印退出信息。
-
使用
exit(0)
退出程序。
默认选项:无效选择
二、完整代码演示
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
// 定义双链表--节点结构
typedef struct Node
{
int data; // 节点存储的数据
struct Node* perv; // 指向前驱节点的指针
struct Node* next; // 指向后继节点的指针
}Node;
// 定义双链表---结构
typedef struct
{
Node* head; // 指向链表的头节点
Node* tail; // 指向链表的尾节点
}DoubleLinkedList;
// 初始化双链表
void initList(DoubleLinkedList* list)
{
if (list == NULL) // 检查是否为空指针
{
printf("初始化双链表失败\n");
return;
}
list->head = NULL; // 初始化为空指针,即无节点
list->tail = NULL;
printf("双链表已初始化。\n");
}
// 后插插入
void insertAtEnd(DoubleLinkedList* list, int data)
{
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL)
{
printf("内存分配失败!\n");
return;
}
newNode->data = data;
newNode->perv = NULL;
newNode->next = NULL;
if (list->head == NULL) // 如果链表为空
{
list->head = newNode; // 新节点成为头节点
list->tail = newNode; // 新节点成为尾节点
}
else // 将新节点插入到链表尾部
{
list->tail->next = newNode; // 当前尾节点的next指针指向新节点
newNode->perv = list->tail; // 新节点的perv指针指向当前尾节点
list->tail = newNode; // 更新尾节点为新节点
}
printf("数据 %d 已插入到链表尾部。\n", data);
}
// 遍历链表
void traverseList(DoubleLinkedList* list)
{
if (list->head == NULL)
{
printf("链表为空!\n");
return;
}
Node* current = list->head;
printf("链表内容:>");
while (current != NULL)
{
printf("%d", current->data);
current = current->next;
}
printf("\n");
}
// 删除节点
void deleteNode(DoubleLinkedList* list, int data)
{
if (list->head == NULL) // 如果链表为空(head为NULL),打印错误信息并返回
{
printf("链表为空,无法删除!\n");
return;
}
Node* current = list->head; // 将头节点赋值给current
while (current != NULL) // 从头节点开始,遍历链表直到找到包含data的节点
{ // 如果遍历完整个链表都没有找到目标节点,打印删除失败的信息
if (current->data == data) // 将用户输入的数据在current链表中
{
if (current->perv == NULL && current->next == NULL) // 如果节点既是头节点又是尾节点(即链表只有一个节点),将head和tail都设为NULL
{
list->head = NULL;
list->tail = NULL;
}
else if (current->perv == NULL) // 如果节点是头节点,更新head为当前节点的下一个节点,并将新头节点的perv指针设为NULL
{
list->head = current->next;
list->head->perv = NULL;
}
else if (current->next == NULL) // 如果节点是尾节点,更新tail为当前节点的前驱节点,并将新尾节点的next指针设为NULL
{
list->tail = current->perv;
list->tail->next = NULL;
}
else // 如果节点是中间节点,调整前驱节点的next指针和后继节点的perv指针,绕过当前节点
{
current->perv->next = current->next;
current->next->perv = current->perv;
}
free(current); // 释放被删除节点的内存
printf("数据 %d 已从链表中删除。\n", data);
return;
}
current = current->next;
}
printf("未找到数据 %d,删除失败!\n", data);
}
// 释放链表内存
void freeList(DoubleLinkedList* list)
{
Node* current = list->head; // 指针初始化为链表的头节点。
Node* temp; // temp指针用于临时保存当前节点,以便释放内存
while (current != NULL)
{
temp = current; // current的值赋给temp,保存当前节点的引用
current = current->next; // 将current移动到下一个节点
free(temp); // 释放空间
}
list->head = NULL; // 设为NULL,表示链表已空
list->tail = NULL;
printf("链表已释放\n");
}
void Menu()
{
printf("---------------------------------------------------------\n");
printf("---------1.初始化双链表-------------2.后插插入数据-------\n");
printf("---------3.删除数据-----------------4.遍历数表-----------\n");
printf("---------5.退出程序--------------------------------------\n");
printf("---------------------------------------------------------\n");
printf("请输入你的选择:>");
}
int main()
{
DoubleLinkedList list;
int choice = 0;
int data = 0;
int initialized = 0;
printf("欢迎使用双链表操作程序!\n");
while (1)
{
Menu();
scanf_s("%d", &choice);
switch (choice)
{
case 1:
initList(&list);
initialized = 1;
break;
case 2:
if (!initialized) // 检查链表是否已初始化,未初始化则提示用户
{
printf("请先初始化链表!\n");
break;
}
printf("请输入要插入的数据: >");
scanf_s("%d", &data);
insertAtEnd(&list, data); // 调用insertAtEnd函数将数据插入链表尾部
traverseList(&list); // 调用traverseList函数遍历并打印链表内容
break;
case 3:
if (!initialized)
{
printf("请先初始化链表!\n");
break;
}
printf("请输入要删除的数据: ");
scanf_s("%d", &data);
deleteNode(&list, data); // 调用deleteNode函数删除链表中的数据
traverseList(&list);
break;
case 4:
if (!initialized)
{
printf("请先初始化链表!\n");
break;
}
traverseList(&list);
break;
case 5:
if (initialized) // 如果链表已初始化,调用freeList函数释放链表内存
{
freeList(&list);
}
printf("感谢使用双链表操作程序!再见!\n");
exit(0);
default:
printf("无效的选择,请重新输入!\n");
}
}
return 0;
}
这段代码提供了一个完整的双链表操作示例,涵盖了基本的链表操作,适用于学习和理解双链表的实现和使用。❀❀❀