数据结构与算法:带头双向循环链表(c语言实现,带详细注释)
最实用链表,完美结构,具体实现如下所示:
//Dlist.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DLDataType;
typedef struct DListNode
{
struct DListNode *pNext;
struct DListNode *pPrev;
DLDataType data;
}DLNode, *pDLNode;
pDLNode Built_ListNode(DLDataType value);
pDLNode Init_DList();
void DList_PushBack(pDLNode pHead, DLDataType value);
void DList_PushFront(pDLNode pHead, DLDataType value);
void DList_PopBack(pDLNode pHead);
void DList_PopFront(pDLNode pHead);
void DList_Replace(pDLNode pos, DLDataType value);
void DList_Insert(pDLNode pos, DLDataType value);
void DList_Erase(pDLNode pos);
pDLNode DList_Find(pDLNode pHead, DLDataType value);
void DList_Print(pDLNode pHead);
void DList_Destory(pDLNode pHead);
//Dlist.c
#include "Dlist.h"
// 双向链表节点结构体定义
typedef int DLDataType; // 假设数据类型为int
typedef struct DLNode
{
DLDataType data; // 数据域
struct DLNode* pNext; // 指向下一个节点的指针
struct DLNode* pPrev; // 指向前一个节点的指针
} DLNode, *pDLNode; // 定义节点和节点指针类型
// 创建一个新的双向链表节点并返回其指针
pDLNode Built_ListNode(DLDataType value)
{
pDLNode newnode = (pDLNode)malloc(sizeof(DLNode)); // 分配内存来存储新节点
newnode->data = value; // 初始化节点的数据
newnode->pNext = NULL; // 将pNext指针初始化为NULL
newnode->pPrev = NULL; // 将pPrev指针初始化为NULL
return newnode; // 返回新节点的指针
}
// 初始化一个空的双向链表并返回虚拟头尾节点的指针
pDLNode Init_DList()
{
pDLNode pHead = Built_ListNode(0); // 创建虚拟头尾节点,并将其数据值初始化为0
pHead->pNext = pHead; // 将pNext指针指向自身,形成循环链表
pHead->pPrev = pHead; // 将pPrev指针指向自身,形成循环链表
return pHead; // 返回虚拟头尾节点的指针
}
// 在双向链表末尾添加一个新节点,其数据值为value
void DList_PushBack(pDLNode pHead, DLDataType value)
{
DList_Insert(pHead->pPrev, value); // 调用DList_Insert在虚拟头尾节点之前插入新节点
}
// 在双向链表开头添加一个新节点,其数据值为value
void DList_PushFront(pDLNode pHead, DLDataType value)
{
DList_Insert(pHead->pNext, value); // 调用DList_Insert在虚拟头尾节点之后插入新节点
}
// 从双向链表末尾移除最后一个节点
void DList_PopBack(pDLNode pHead)
{
DList_Erase(pHead->pPrev); // 调用DList_Erase移除虚拟头尾节点之前的节点
}
// 从双向链表开头移除第一个节点
void DList_PopFront(pDLNode pHead)
{
DList_Erase(pHead->pNext); // 调用DList_Erase移除虚拟头尾节点之后的节点
}
// 替换指定节点pos的数据值为value
void DList_Replace(pDLNode pos, DLDataType value)
{
assert(pos); // 断言指定的节点pos不为空
pDLNode newnode = Built_ListNode(value); // 创建一个新节点,数据值为value
pDLNode pos_prev = pos->pPrev; // 获取指定节点pos的前一个节点
pDLNode pos_next = pos->pNext; // 获取指定节点pos的后一个节点
pos_prev->pNext = newnode; // 将前一个节点的pNext指针指向新节点
newnode->pPrev = pos_prev; // 将新节点的pPrev指针指向前一个节点
pos_next->pPrev = newnode; // 将后一个节点的pPrev指针指向新节点
newnode->pNext = pos_next; // 将新节点的pNext指针指向后一个节点
}
// 在指定节点pos之后插入一个新节点,其数据值为value
void DList_Insert(pDLNode pos, DLDataType value)
{
assert(pos); // 断言指定的节点pos不为空
pDLNode pos_prev = pos->pPrev; // 获取指定节点pos的前一个节点
pDLNode newnode = Built_ListNode(value); // 创建一个新节点,数据值为value
pos_prev->pNext = newnode; // 将前一个节点的pNext指针指向新节点
newnode->pPrev = pos_prev; // 将新节点的pPrev指针指向前一个节点
newnode->pNext = pos; // 将新节点的pNext指针指向指定节点pos
pos->pPrev = newnode; // 将指定节点pos的pPrev指针指向新节点
}
// 移除指定节点pos
void DList_Erase(pDLNode pos)
{
assert(pos); // 断言指定的节点pos不为空
pDLNode pos_prev = pos->pPrev; // 获取指定节点pos的前一个节点
pDLNode pos_next = pos->pNext; // 获取指定节点pos的后一个节点
pos_prev->pNext = pos_next; // 将前一个节点的pNext指针指向后一个节点
pos_next->pPrev = pos_prev; // 将后一个节点的pPrev指针指向前一个节点
free(pos); // 释放指定节点pos的内存
pos = NULL; // 将指定节点pos指向NULL,防止悬挂指针
}
// 在双向链表中查找具有指定数据值的节点,并返回该节点的指针,若未找到返回NULL
pDLNode DList_Find(pDLNode pHead, DLDataType value)
{
pDLNode cur = pHead->pNext; // 从第一个真实节点开始查找
while (cur != pHead) // 遍历整个链表,直到回到虚拟头尾节点
{
if (cur->data == value) // 若找到具有指定数据值的节点
{
return cur; // 返回该节点的指针
}
cur = cur->pNext; // 继续向后遍历
}
return NULL; // 若未找到,返回NULL
}
// 打印双向链表中所有节点的数据值
void DList_Print(pDLNode pHead)
{
pDLNode cur = pHead->pNext; // 从第一个真实节点开始打印
while (cur !=
pHead) // 遍历整个链表,直到回到虚拟头尾节点
{
printf("%d ", cur->data); // 打印当前节点的数据值
cur = cur->pNext; // 继续向后遍历
}
printf("\n"); // 换行,输出完整链表后换行
}
// 释放双向链表的内存,包括所有节点
void DList_Destroy(pDLNode pHead)
{
assert(pHead); // 断言虚拟头尾节点不为空
pDLNode cur = pHead->pNext; // 从第一个真实节点开始释放
while (cur != pHead) // 遍历整个链表,直到回到虚拟头尾节点
{
pDLNode cur_next = cur->pNext; // 保存当前节点的下一个节点
free(cur); // 释放当前节点的内存
cur = cur_next; // 继续向后遍历
}
free(pHead); // 释放虚拟头尾节点的内存
}
// 测试函数1:演示基本操作,如尾部和头部插入节点以及尾部和头部移除节点
void test01()
{
pDLNode dlist = Init_DList(); // 初始化双向链表
DList_PushBack(dlist, 1); // 尾部插入节点,数据值为1
DList_PushBack(dlist, 2); // 尾部插入节点,数据值为2
DList_PushBack(dlist, 3); // 尾部插入节点,数据值为3
DList_PushBack(dlist, 4); // 尾部插入节点,数据值为4
DList_Print(dlist); // 打印链表的数据值:1 2 3 4
DList_PushFront(dlist, 0); // 头部插入节点,数据值为0
DList_PushFront(dlist, -1); // 头部插入节点,数据值为-1
DList_PushFront(dlist, -2); // 头部插入节点,数据值为-2
DList_Print(dlist); // 打印链表的数据值:-2 -1 0 1 2 3 4
DList_PopBack(dlist); // 尾部移除节点
DList_Print(dlist); // 打印链表的数据值:-2 -1 0 1 2 3
DList_PopFront(dlist); // 头部移除节点
DList_Print(dlist); // 打印链表的数据值:-1 0 1 2 3
DList_PopBack(dlist); // 尾部移除节点
DList_Print(dlist); // 打印链表的数据值:-1 0 1 2
DList_PopFront(dlist); // 头部移除节点
DList_Print(dlist); // 打印链表的数据值:0 1 2
}
int main()
{
test01(); // 调用测试函数1
DList_Destroy(dlist); // 释放双向链表的内存
return 0;
}