数据结构——带头节点的双向循环列表

带头节点的双向循环链表是一种特殊的双向链表,它与普通的双向链表相比,最大的区别是链表头结点的 next 指针不再指向第一个实际节点,而是指向链表中的第一个节点。同时,链表尾结点的 prev 指针也不再指向 NULL,而是指向链表中的最后一个节点。

另外,带头节点的双向循环链表相对于普通的双向链表来说,在插入和删除节点时,需要特别注意头结点和尾结点的指针处理,以保证链表的循环性。
在这里插入图片描述

typedef int LTDatatype;
typedef struct ListNode
{
    
    
	struct ListNode* next;
	struct ListNode* prev;
	LTDatatype data;
}LTNode;

这个代码片段定义了一个名为 ListNode 的结构体,包含三个成员:

next:指向链表中下一个节点的指针。
prev:指向链表中上一个节点的指针。
data:保存节点数据的 LTDatatype 类型变量。
typedef int LTDatatype; 语句创建了一个名为 LTDatatype 的 int 数据类型的别名。这使得程序员可以通过更改 typedef 语句而不是修改整个代码,轻松修改链表中使用的数据类型。

总体来说,这段代码简单地定义了一个用于 C 语言双向链表节点的结构体。

//双向列表的初始化
LTNode* LTInit()
{
    
    
	LTNode* plist= BuyListNode(-1);
	plist->next = plist;
	plist->prev = plist;
	return plist;
}

这是一个 C 语言函数,用于初始化一个双向链表。函数的返回值是指向链表头结点的指针,函数名为 LTInit,意味着“初始化链表”。

// 双向链表销毁
void ListDestory(LTNode* plist) {
    
    
	LTNode* p = plist; // 从头节点开始遍历
	while (p != NULL) {
    
    
		LTNode* tmp = p; // 保存当前节点指针
		p = p->next; // 移动到下一个节点
		free(tmp); // 释放当前节点的内存
	}
	plist = NULL; // 将头结点指针置为 NULL
}

这是一个 C 语言函数,用于销毁一个带头节点的双向循环链表。函数的参数是指向链表头结点的指针 plist,函数没有返回值。函数名为 ListDestory,意味着“销毁链表”。

注意,在带头节点的双向循环链表中,头结点的 next 指针不再指向第一个实际节点,而是指向链表中的第一个节点。因此,在销毁链表时,需要从头结点的 next 指针指向的节点开始遍历,直到回到头结点为止。同时,在释放头结点的内存之前,需要先释放链表中的所有节点的内存。

// 双向链表打印
void ListPrint(LTNode* plist)
{
    
    
	assert(plist);
	printf("<=head=>");
	LTNode* cur = plist->next;
	while (cur != plist)
	{
    
    
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

这是一个 C 语言函数,用于打印一个带头节点的双向循环链表中所有节点的数据。函数的参数是指向链表头结点的指针 plist,函数没有返回值。函数名为 ListPrint,意味着“打印链表”。

注意,在带头节点的双向循环链表中,头结点的 next 指针不再指向第一个实际节点,而是指向链表中的第一个节点。因此,在打印链表时,需要从头结点的 next 指针指向的节点开始遍历,直到回到头结点为止。同时,在输出节点数据时,需要注意区分头结点和实际节点,以免混淆。

bool LTEmpty(LTNode* plist)
{
    
    
    assert(plist != NULL); // 确保链表头结点不为空
    return plist->next == plist; // 如果头结点的 next 指针指向自身,说明链表为空,返回 true,否则返回 false
}

这是一个 C 语言函数,用于判断一个带头节点的双向循环链表是否为空。函数的参数是指向链表头结点的指针 plist,函数返回一个布尔值。函数名为 LTEmpty,意味着“链表是否为空”。

// 双向链表尾删
void ListPopBack(LTNode* plist)
{
    
    
	assert(plist);
	assert(!LTEmpty(plist));
	LTNode* tail = plist->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = plist;
	plist->prev = tailPrev;
	free(tail);
	tail = NULL;
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中删除最后一个节点。函数的参数是指向链表头结点的指针 plist,函数没有返回值。函数名为 ListPopBack,意味着“尾删链表”。

函数的实现通常涉及以下步骤:
检查链表头结点和链表是否为空,如果为空,抛出异常。
找到链表中的最后一个节点,即尾结点。
找到尾结点的前驱节点,并将其 next 指针指向头结点,将头结点的 prev 指针指向尾结点的前驱节点。
释放尾结点的内存,并将其指针置为 NULL。

// 双向链表头插
void ListPushFront(LTNode* plist, LTDataType x)
{
    
    
    assert(plist != NULL); // 确保链表头结点不为空
    LTNode* newnode = BuyListNode(x); // 创建新节点
    newnode->next = plist->next; // 将新节点的 next 指针指向头结点的下一个节点
    plist->next->prev = newnode; // 将头结点的下一个节点的 prev 指针指向新节点
    plist->next = newnode; // 将头结点的 next 指针指向新节点
    newnode->prev = plist; // 将新节点的 prev 指针指向头结点
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中在头部插入一个新节点。函数的参数是指向链表头结点的指针 plist 和要插入的节点数据 x,函数没有返回值。函数名为 ListPushFront,意味着“头插链表”。

// 双向链表尾插
void ListPushBack(LTNode* plist, LTDataType x)
{
    
    
	assert(plist);
	/*LTNode* newnode = BuyListNode(x);
	LTNode* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;*/
	ListInsert(plist, x);
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中在尾部插入一个新节点。函数的参数是指向链表头结点的指针 plist 和要插入的节点数据 x,函数没有返回值。函数名为 ListPushBack,意味着“尾插链表”。

函数的实现通常涉及以下步骤:

检查链表头结点是否为空,如果为空,抛出异常。
创建一个新节点,并将其 next 指针指向头结点,将头结点的前驱节点的 next 指针指向新节点。
将新节点的 prev 指针指向头结点的前驱节点,将头结点的 prev 指针指向新节点。

// 双向链表头删
void ListPopFront(LTNode* plist) {
    
    
	if (plist == NULL) {
    
    
		return; // 如果链表为空,直接返回
	}

	LTNode* p = plist; // 保存头节点指针
	plist = plist->next; // 将下一个节点设置为新的头节点
	if (plist != NULL) {
    
    
		plist->prev = NULL; // 如果新的头节点不为空,则将其前驱节点的指针设置为 NULL
	}
	free(p); // 释放原始头节点的内存
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中删除第一个节点。函数的参数是指向链表头结点的指针 plist,函数没有返回值。函数名为 ListPopFront,意味着“头删链表”。

函数的实现通常涉及以下步骤:

检查链表头结点是否为空,如果为空,直接返回。
将头结点的下一个节点设置为新的头节点,并保存原始头节点的指针。
如果新的头节点不为空,则将其前驱节点的指针设置为 NULL。
释放原始头节点的内存,并将其指针置为 NULL。

// 双向链表查找
LTNode* ListFind(LTNode* plist, LTDataType x) 
{
    
    
	LTNode* p = plist->next; // 从第一个节点开始遍历
	while (p != NULL && p->data != x) {
    
     // 遍历链表,直到找到指定数据或到达链表结尾
		p = p->next; // 移动到下一个节点
	}
	return p; // 返回找到的节点的指针,如果未找到则返回 NULL
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中查找指定数据的节点。函数的参数是指向链表头结点的指针 plist 和要查找的数据 x,函数返回找到的节点的指针,如果未找到则返回 NULL。函数名为 ListFind,意味着“查找链表”。

函数的实现通常涉及以下步骤:

从第一个节点开始遍历链表。
在遍历链表时,如果找到了数据值等于 x 的节点,则返回该节点的指针。
如果遍历到链表的结尾都没有找到符合条件的节点,则返回 NULL。

// 双向链表在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x)
{
    
    
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中在指定节点的前面插入一个新节点。函数的参数是指向指定节点的指针 pos 和要插入的节点数据 x,函数没有返回值。函数名为 ListInsert,意味着“插入链表”。

函数的实现通常涉及以下步骤:

检查指定节点是否为空,如果为空,抛出异常。
创建一个新节点,并将其 next 指针指向指定节点,将指定节点的前驱节点的 next 指针指向新节点。
将新节点的 prev 指针指向指定节点的前驱节点,将指定节点的 prev 指针指向新节点。

// 双向链表删除pos位置的结点
void ListErase(LTNode* pos) 
{
    
    
	if (pos == NULL || pos->prev == NULL) {
    
    
		return; // 如果待删除节点为 NULL 或者是头结点,则直接返回
	}

	LTNode* prev_node = pos->prev; // 保存待删除节点的前驱节点
	prev_node->next = pos->next; // 将前驱节点的 next 指针指向待删除节点的后继节点
	if (pos->next != NULL) {
    
    
		pos->next->prev = prev_node; // 如果待删除节点有后继节点,则将后继节点的 prev 指针指向前驱节点
	}
	free(pos); // 释放待删除节点的内存
}

这是一个 C 语言函数,用于在带头节点的双向循环链表中删除指定位置的节点。函数的参数是指向指定节点的指针 pos,函数没有返回值。函数名为 ListErase,意味着“删除链表”。

函数的实现通常涉及以下步骤:

检查待删除节点是否为空,如果为空或者是头结点,则直接返回。
保存待删除节点的前驱节点指针。
将前驱节点的 next 指针指向待删除节点的后继节点。
如果待删除节点有后继节点,则将后继节点的 prev 指针指向前驱节点。
释放待删除节点的内存。

LTNode* BuyListNode(LTDataType x)
{
    
    
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
    
    
		perror("malloc fail");
		return NULL;
	}
	node->next = NULL;
	node->prev = NULL;
	node->data = x;
	return node;
}

这是一个 C 语言函数,用于创建一个新的双向链表节点并返回其指针。函数的参数是节点中存储的数据 x,函数返回新节点的指针。函数名为 BuyListNode,意味着“购买链表节点”,通常也可以称为 CreateListNode 或者 NewListNode。

函数的实现通常涉及以下步骤:

分配一个新的节点结构体的内存空间。
将节点的 data 成员赋值为参数 x。
将节点的 next 和 prev 成员指针初始化为 NULL。
返回新节点的指针。

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{
    
    
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;


//双向列表的初始化
LTNode* LTInit();
// 双向链表销毁
void ListDestory(LTNode* plist);
// 双向链表打印
void ListPrint(LTNode* plist);
// 双向链表尾插
void ListPushBack(LTNode* plist, LTDataType x);
// 双向链表尾删
void ListPopBack(LTNode* plist);
// 双向链表头插
void ListPushFront(LTNode* plist, LTDataType x);
// 双向链表头删
void ListPopFront(LTNode* plist);
// 双向链表查找
LTNode* ListFind(LTNode* plist, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x);
// 双向链表删除pos位置的结点
void ListErase(LTNode* pos);

LTNode* BuyListNode(LTDataType x)
{
    
    
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
    
    
		perror("malloc fail");
		return NULL;
	}
	node->next = NULL;
	node->prev = NULL;
	node->data = x;
	return node;
}

//双向列表的初始化
LTNode* LTInit()
{
    
    
	LTNode* plist= BuyListNode(-1);
	plist->next = plist;
	plist->prev = plist;
	return plist;
}
// 双向链表销毁
void ListDestory(LTNode* plist) {
    
    
	LTNode* p = plist; // 从头节点开始遍历
	while (p != NULL) {
    
    
		LTNode* tmp = p; // 保存当前节点指针
		p = p->next; // 移动到下一个节点
		free(tmp); // 释放当前节点的内存
	}
	plist = NULL; // 将头结点指针置为 NULL
}

// 双向链表打印
void ListPrint(LTNode* plist)
{
    
    
	assert(plist);
	printf("<=head=>");
	LTNode* cur = plist->next;
	while (cur != plist)
	{
    
    
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}


//判断链表是否为空
bool LTEmpty(LTNode* plist)
{
    
    
	assert(plist);
	return plist->next == plist;
}

// 双向链表尾删
void ListPopBack(LTNode* plist)
{
    
    
	assert(plist);
	assert(!LTEmpty(plist));
	LTNode* tail = plist->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = plist;
	plist->prev = tailPrev;
	free(tail);
	tail = NULL;
}

// 双向链表头插
void ListPushFront(LTNode* plist, LTDataType x)
{
    
    
	assert(plist);
	/*LTNode* newnode = BuyListNode(x);
	newnode->next = plist->next;
	plist->next->prev = newnode;
	plist->next = newnode;
	newnode->prev = plist;*/
	ListInsert(plist->next, x);
}

// 双向链表尾插
void ListPushBack(LTNode* plist, LTDataType x)
{
    
    
	assert(plist);
	/*LTNode* newnode = BuyListNode(x);
	LTNode* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;*/
	ListInsert(plist, x);
}
// 双向链表头删
void ListPopFront(LTNode* plist) {
    
    
	if (plist == NULL) {
    
    
		return; // 如果链表为空,直接返回
	}

	LTNode* p = plist; // 保存头节点指针
	plist = plist->next; // 将下一个节点设置为新的头节点
	if (plist != NULL) {
    
    
		plist->prev = NULL; // 如果新的头节点不为空,则将其前驱节点的指针设置为 NULL
	}
	free(p); // 释放原始头节点的内存
}
// 双向链表查找
LTNode* ListFind(LTNode* plist, LTDataType x) 
{
    
    
	LTNode* p = plist->next; // 从第一个节点开始遍历
	while (p != NULL && p->data != x) {
    
     // 遍历链表,直到找到指定数据或到达链表结尾
		p = p->next; // 移动到下一个节点
	}
	return p; // 返回找到的节点的指针,如果未找到则返回 NULL
}
// 双向链表在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x)
{
    
    
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}
// 双向链表删除pos位置的结点
void ListErase(LTNode* pos) 
{
    
    
	if (pos == NULL || pos->prev == NULL) {
    
    
		return; // 如果待删除节点为 NULL 或者是头结点,则直接返回
	}

	LTNode* prev_node = pos->prev; // 保存待删除节点的前驱节点
	prev_node->next = pos->next; // 将前驱节点的 next 指针指向待删除节点的后继节点
	if (pos->next != NULL) {
    
    
		pos->next->prev = prev_node; // 如果待删除节点有后继节点,则将后继节点的 prev 指针指向前驱节点
	}
	free(pos); // 释放待删除节点的内存
}

猜你喜欢

转载自blog.csdn.net/weixin_51799303/article/details/131344295