带头双向循环链表
带头双向循环链表是一种结合了双向链表和循环链表特性的数据结构。其主要特点如下:
-
双向性:链表中的每个节点都有两个指针,一个指向下一个节点(
next
),另一个指向前一个节点(prev
)。这种双向性使得链表中的任何节点都可以方便地访问其前驱节点和后继节点,从而支持向前和向后的遍历操作。 -
循环性:在带头双向循环链表中,最后一个节点的
next
指针指向链表的第一个节点,而第一个节点的prev
指针指向最后一个节点,形成一个闭环。这种循环结构使得链表没有明确的开始和结束,可以从任何节点开始遍历整个链表。 -
简化操作:由于链表的循环性,无论是从头部还是尾部开始遍历,都可以无缝地连接到链表的另一端。这简化了在链表两端进行插入和删除节点的操作。例如,在链表头部插入一个新节点时,只需将新节点的
prev
指针指向最后一个节点,next
指针指向原链表的第一个节点,并更新最后一个节点和原第一个节点的指针即可。 -
内存使用:带头双向循环链表相对于普通链表需要额外的空间来存储指针,但这也带来了操作的便利性和灵活性。在实际应用中,根据具体需求权衡空间复杂度和时间复杂度是很重要的。
-
适用场景:带头双向循环链表适用于需要频繁在链表头部和尾部进行插入和删除操作的场景,如实现循环队列、循环双向链表等数据结构。其循环性和双向性使得这些操作变得简单和高效。
需要注意的是,虽然带头双向循环链表具有许多优点,但它并不适用于所有场景。在选择数据结构时,需要根据具体的应用需求和性能要求进行权衡。
定义结点
// 定义一个新的数据类型LTDataType,它实际上就是int类型的一个别名。
typedef int LTDataType;
// 定义一个名为ListNode的结构体,用于表示双向链表的一个节点。
typedef struct ListNode
{
// 指向下一个节点的指针
struct ListNode* next;
// 指向前一个节点的指针
struct ListNode* prev;
// 存储数据的成员,其类型为之前定义的LTDataType,即int类型
LTDataType data;
} LTNode;
链表的基本操作
LTNode* BuyListNode(LTDataType x);
//void LTInit(LTNode** pphead);
LTNode* LTInit();
void LTDestroy(LTNode* phead);
void LTPrint(LTNode* phead);
bool LTEmpty(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);
// posλ֮ǰһֵ
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);
创建新结点
// 定义一个函数BuyListNode,它接受一个LTDataType类型的参数x,返回一个LTNode类型的指针。
LTNode* BuyListNode(LTDataType x)
{
// 使用malloc为LTNode结构体分配内存空间,并将返回的void*指针强制转换为LTNode*类型。
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
// 检查malloc是否成功分配了内存。
if (node == NULL)
{
// 如果内存分配失败,使用perror函数输出错误信息。
perror("malloc fail");
// 直接退出程序,因为内存分配失败通常意味着严重的问题。
// 注意:通常这里应该返回NULL,让调用者处理错误,但这里选择了直接退出。
exit(-1);
}
// 初始化新节点的next和prev指针为NULL,表示这是链表中的一个孤立节点。
node->next = NULL;
node->prev = NULL;
// 将参数x赋值给新节点的data成员。
node->data = x;
// 返回新创建的节点指针。
return node;
}
初始化双向链表
// 定义一个函数LTInit,它没有参数,返回一个LTNode类型的指针。
LTNode* LTInit()
{
// 调用BuyListNode函数,创建一个新的LTNode节点,并用-1初始化其data成员。
LTNode* phead = BuyListNode(-1);
// 将新节点的next指针指向其自身,表示这是一个循环链表。
phead->next = phead;
// 将新节点的prev指针也指向其自身,这是双向循环链表的特点。
phead->prev = phead;
// 返回头节点的指针。
return phead;
}
销毁链表
void LTDestroy(LTNode* phead)
{
LTNode* current = phead;
LTNode* next;
// 遍历链表,释放每个节点的内存
do {
next = current->next; // 保存下一个节点的指针
free(current); // 释放当前节点的内存
current = next; // 移动到下一个节点
} while (current != phead); // 遍历直到回到头节点
// 此时current指向头节点,但头节点已经在循环中被释放了
// 因此不需要再次释放phead
}
打印双向链表
void LTPrint(LTNode* phead)
{
assert(phead);
printf("<=head=>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
链表判空
#include <assert.h> // 引入assert.h头文件以使用assert宏
#include <stdbool.h> // 引入stdbool.h头文件以使用bool类型
bool LTEmpty(LTNode* phead)
{
// 使用assert宏来确保phead不为NULL,如果为NULL则终止程序
assert(phead != NULL);
// 判断链表是否为空,即头节点的next是否指向自身
// 如果是,说明链表为空(只有头节点),返回true
// 否则,返回false
//也就是下面这个代码
/*if (phead->next == phead)
{
return true;
}
else
{
return false;
}*/
//为了简化,改为下面这个
return phead->next == phead;
}
插入结点
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos); // 确保pos不为NULL
LTNode* prev = pos->prev; // 获取pos节点的前一个节点
LTNode* newnode = BuyListNode(x); // 创建一个新的节点,并用x初始化其data成员
// 将newnode插入到prev和pos之间
prev->next = newnode; // prev的下一个节点指向newnode
newnode->prev = prev; // newnode的前一个节点指向prev
newnode->next = pos; // newnode的下一个节点指向pos
pos->prev = newnode; // pos的前一个节点指向newnode
}
后插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
//LTNode* newnode = BuyListNode(x);
//LTNode* tail = phead->prev;
phead tail newnode
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = phead;
//phead->prev = newnode;
LTInsert(phead, x);
}
尾删
void LTPopBack(LTNode* phead)
{
assert(phead); // 确保phead不为NULL
assert(!LTEmpty(phead)); // 确保链表不为空
LTNode* tail = phead->prev; // 获取链表的尾节点
LTNode* tailPrev = tail->prev; // 获取尾节点的前一个节点
// 更新tailPrev的next指针,使其指向头节点
tailPrev->next = phead;
// 更新头节点的prev指针,使其指向tailPrev
phead->prev = tailPrev;
// 释放尾节点占用的内存
free(tail);
// 将tail指针置为NULL,避免悬挂指针
tail = NULL;
}
头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
//LTNode* newnode = BuyListNode(x);
//LTNode* first = phead->next;
//phead->next = newnode;
//newnode->prev = phead;
//newnode->next = first;
//first->prev = newnode;
// 㻻˳
//newnode->next = phead->next;
//phead->next->prev = newnode;
//phead->next = newnode;
//newnode->prev = phead;
LTInsert(phead->next, x);
}
头删
void LTPopFront(LTNode* phead)
{
assert(phead); // 确保phead不为NULL
assert(!LTEmpty(phead)); // 确保链表不为空
LTNode* first = phead->next; // 获取链表的头节点(非哨兵节点)
LTNode* second = first->next; // 获取头节点的下一个节点
// 更新头节点的next指针,使其指向second
phead->next = second;
// 更新second的prev指针,使其指向头节点
second->prev = phead;
// 释放头节点占用的内存
free(first);
// 特别注意:如果链表中只有一个节点(包括头节点自身),则second此时就是头节点
// 在这种情况下,我们需要将头节点的prev和next指针都指向自身,以维持循环结构
if (second == phead) {
phead->prev = phead;
phead->next = phead;
}
}
完整代码
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
} LTNode;
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
//return NULL;
exit(-1);
}
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
LTNode* LTInit()
{
LTNode* phead = BuyListNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
void LTDestroy(LTNode* phead);
void LTPrint(LTNode* phead)
{
assert(phead);
printf("<=head=>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
bool LTEmpty(LTNode* phead)
{
assert(phead);
/*if (phead->next == phead)
{
return true;
}
else
{
return false;
}*/
return phead->next == phead;
}
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
//LTNode* newnode = BuyListNode(x);
//LTNode* tail = phead->prev;
phead tail newnode
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = phead;
//phead->prev = newnode;
LTInsert(phead, x);
}
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
tailPrev->next = phead;
phead->prev = tailPrev;
free(tail);
tail = NULL;
}
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
//LTNode* newnode = BuyListNode(x);
//LTNode* first = phead->next;
//phead->next = newnode;
//newnode->prev = phead;
//newnode->next = first;
//first->prev = newnode;
// 㻻˳
//newnode->next = phead->next;
//phead->next->prev = newnode;
//phead->next = newnode;
//newnode->prev = phead;
LTInsert(phead->next, x);
}
void LTPopFront(LTNode* phead)
{
assert(phead); // 确保phead不为NULL
assert(!LTEmpty(phead)); // 确保链表不为空
LTNode* first = phead->next; // 获取链表的头节点(非哨兵节点)
LTNode* second = first->next; // 获取头节点的下一个节点
// 更新头节点的next指针,使其指向second
phead->next = second;
// 更新second的prev指针,使其指向头节点
second->prev = phead;
// 释放头节点占用的内存
free(first);
// 特别注意:如果链表中只有一个节点(包括头节点自身),则second此时就是头节点
// 在这种情况下,我们需要将头节点的prev和next指针都指向自身,以维持循环结构
if (second == phead) {
phead->prev = phead;
phead->next = phead;
}
}
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
// prev newnode pos
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}