什么是双链表?
双链表,也称作“双向链表”或“双重链表”,它由节点构成,每个节点既包含数据(或值),又包含两个指针,一个指向前一个节点,另一个指向后一个节点。
双链表的应用场景是什么?
双链表常用于需要频繁插入和删除节点的场景。
例如文本编辑器中的撤销和恢复功能、图形界面框架、浏览器历史记录、数据库管理系统等。
双链表的优点是什么?
双链表的缺点是什么?
双链表如何改进?
双链表的实现难度如何?
双链表的基本实现相对较简单,但在处理复杂的操作时,需要谨慎处理指针的连接和断开。在面对涉及多个节点的复杂操作时,复杂的边界情况和内存管理会很让人头疼。
第三、四、五部分附赠1000行代码(自定义头文件、实现代码,以及调试主文件)
双链表的核心思想是通过一个节点,既能访问到上一个节点(前驱),也能访问下一个节点(后继)。
为了定义一个双链表节点,通常遵循以下结构:
struct Node
{
// 可以根据需求添加多个元素
elementType element1;
elementType element2;
// ...
elementType elementn;
struct Node *prev; // 指向前一个节点
struct Node *next; // 指向下一个节点
};
在上述代码中,elementType
可以是任何数据类型,根据节点需要存储的数据而定。你可以自行修改并扩展节点的成员。
在本文中,我们采用如下的节点定义,以便于示例和代码的理解:
typedef struct linkedListNode linkedList;
typedef linkedList Node;
typedef int elementType;
struct linkedListNode
{
elementType value; // 存储节点的值
linkedList *next; // 指向下一个节点
linkedList *prev; // 指向前一个节点
};
这种节点定义旨在清晰地表达了节点的基本结构,包括值以及与前一个和后一个节点的关系。
需要注意的是,代码中使用了 typedef
来重新定义 linkedList
和 Node
,这使得代码更易于阅读和理解,因为我们可以使用短一些的名称。
在这个节点创建函数中,我们接收两个参数:值和后继指针(next
)。新创建的节点将使用传递的值来填充元素域(value
),并使用传递的指针来指定后继指针域(next
)。
与我之前在《掌握C语言单链表编程:从零到高手,实用示例全解析!》中实现的节点创建函数不同,这次我们接受了新参数 next
。这种设计允许在执行插入、删除等链表操作时更容易隐藏细节。
同时,因为没有接收 prev
(前驱指针),这避免了过度封装。如果将节点的前驱指针也封装在函数中,将对这个函数的灵活性产生不利影响。
// 创建一个新节点并返回其指针
Node *createNode(elementType value, Node *next)
{
Node *node = (Node *)malloc(sizeof(Node));
if (node == NULL)
{
// 处理内存分配失败(例如,打印错误消息)
printf("错误:在 createNode 中内存分配失败。\n");
exit(1); // 或返回 NULL 或执行其他错误处理
}
node->value = value; // 设置节点的值
node->next = next; // 设置节点的下一个节点指针
node->prev = NULL; // 设置节点的前一个节点指针为 NULL
return node; // 返回新节点的指针
}
遍历链表进行查找,同时需要注意边界情况。
// 根据节点位置查找链表中的节点
Node *findNodeByLocation(linkedList *head, int location)
{
while (--location)
{
if (!head)
{
return head; // 如果位置超出链表长度,返回NULL
}
head = head->next; // 移动到下一个节点
}
return head; // 返回找到的节点
}
遍历链表查找匹配的值,找到匹配的节点后返回,找不到就返回 NULL
。
// 根据节点值查找链表中的节点
Node *findNodeByValue(linkedList *head, elementType value)
{
Node *node = head;
while (node)
{
if (node->value == value)
{
return node; // 返回找到的节点
}
node = node->next; // 移动到下一个节点
}
return NULL; // 如果未找到匹配的节点,则返回NULL
}
调用 findNodeByValue
函数并进行逻辑判断即可。
// 检查链表中是否存在特定的值
bool containsValueInLinkedList(linkedList *head, elementType value)
{
return findNodeByValue(head, value) != NULL; // 如果找到匹配的节点,返回true,否则返回false
}
双链表的插入操作本质上与单链表的插入差异不大,只是需要多注意前驱指针(在本文中为 prev
)。
通常来说,需要接收两个参数:链表头节点(如果不是全局变量)和值。
首先创建一个节点,使这个节点的 next
指针指向当前的头节点(如果存在),然后更新头指针以指向新建的节点即可。
// 在链表头部插入节点
void insertNodeAtHead(linkedList **head, elementType value)
{
Node *newNode = createNode(value, *head); // 创建新节点
if (*head)
{
(*head)->prev = newNode; // 更新原头节点的前一个节点指针
}
*head = newNode; // 更新链表的头节点指针
}
// 在链表头部插入节点,并返回新的头节点
Node *insertNodeAtHead(linkedList **head, elementType value)
{
Node *newNode = createNode(value, *head); // 创建新节点
if (*head)
{
(*head)->prev = newNode; // 更新原头节点的前一个节点指针
}
*head = newNode; // 更新链表的头节点指针
return *head; // 返回新的头节点
}
遍历链表直到尾节点,同时创建新节点(newNode
)。使当前的尾节点的 next
指针指向新建的节点,而 newNode
的 prev
指针指向尾节点。需要特别注意处理空链表和仅包含一个节点的链表情况。
// 在链表尾部插入节点
void insertNodeAtTail(linkedList **head, elementType value)
{
Node *newNode = createNode(value, NULL); // 创建新节点
Node *currentNode = *head;
if (!currentNode)
{
*head = newNode; // 如果链表为空,将新节点设置为头节点
}
else
{
while (currentNode->next)
{
currentNode = currentNode->next; // 移动到下一个节点,直到找到最后一个节点
}
currentNode->next = newNode; // 更新最后一个节点的下一个节点指针
newNode->prev = currentNode; // 更新新节点的前一个节点指针
}
}
// 在链表尾部插入节点,并返回新的尾节点
Node *insertNodeAtTail(linkedList **head, elementType value)
{
Node *newNode = createNode(value, NULL); // 创建新节点
Node *currentNode = *head;
if (!currentNode)
{
*head = newNode; // 如果链表为空,将新节点设置为头节点
}
else
{
while (currentNode->next)
{
currentNode = currentNode->next; // 移动到下一个节点,直到找到最后一个节点
}
currentNode->next = newNode; // 更新最后一个节点的下一个节点指针
newNode->prev = currentNode; // 更新新节点的前一个节点指针
}
return newNode; // 返回新的尾节点
}
执行插入操作时需要指定位置,因此我们假定链表的头节点位置为0。
这样,我们可以明确定位链表中每个节点的位置。
当要在指定位置(假设为 location
)插入节点时,我们必须确保它是合法的,即
1 ≤ location ≤ ( 1 + numberOfNodesIn ( list ) ) 1 \leq \text{location} \leq (1 + \text{numberOfNodesIn}(\text{list})) 1≤location≤(1+numberOfNodesIn(list))
如果 l o c a t i o n location location 不合法,需要进行异常处理。
在假定 l o c a t i o n location location 合法的前提下,我们找到位置 ( l o c a t i o n − 1 ) (location-1) (location−1) 和 ( l o c a t i o n + 1 ) (location+1) (location+1) 对应的节点(前驱节点 prevNode
和后继节点 nextNode
)。
此外,需要创建新节点(newNode
)。
接下来,只需合理更新 prevNode
、nextNode
和 newNode
的 prev
和 next
指针即可。
// 在链表的指定位置插入节点
void insertNodeAtLocation(linkedList **head, elementType value, int location)
{
if (location < 1)
{
printf("错误:insertNodeAtLocation(...int location):位置太小\n");
}
else if (location == 1)
{
insertNodeAtHead(head, value); // 在链表头部插入节点
}
else
{
Node *prevNode = findNodeByLocation(*head, location - 1);
if (prevNode)
{
Node *newNode = createNode(value, prevNode->next); // 创建新节点
if (prevNode->next)
{
prevNode->next->prev = newNode; // 更新下一个节点的前一个节点指针
}
prevNode->next = newNode; // 更新前一个节点的下一个节点指针
newNode->prev = prevNode; // 更新新节点的前一个节点指针
}
else
{
printf("错误:insertNodeAtLocation(...int location):位置太大\n");
}
}
}
// 在链表的指定位置插入节点,并返回新的头节点
Node *insertNodeAtLocation(linkedList **head, elementType value, int location)
{
if (location < 1)
{
printf("错误:insertNodeAtLocation(...int location):位置太小\n");
return *head;
}
else if (location == 1)
{
return insertNodeAtHead(head, value); // 在链表头部插入节点,并返回新的头节点
}
else
{
Node *prevNode = findNodeByLocation(*head, location - 1);
if (prevNode)
{
Node *newNode = createNode(value, prevNode->next); // 创建新节点
if (prevNode->next)
{
prevNode->next->prev = newNode; // 更新下一个节点的前一个节点指针
}
prevNode->next = newNode; // 更新前一个节点的下一个节点指针
newNode->prev = prevNode; // 更新新节点的前一个节点指针
return *head; // 返回新的头节点
}
else
{
printf("错误:insertNodeAtLocation(...int location):位置太大\n");
return *head;
}
}
}
需要考虑两种情况。
prev
指针指向空。// 删除链表头部的节点
void deleteNodeAtHead(linkedList **head)
{
if (*head)
{
Node *node = *head;
*head = (*head)->next; // 更新链表的头指针为下一个节点
if (*head)
{
(*head)->prev = NULL; // 如果新的头节点存在,更新其前一个节点指针为NULL
}
free(node); // 释放原头节点的内存
}
else
{
printf("错误:deleteNodeAtHead(linkedList **head):链表为空,无法执行删除头节点。\n");
}
}
// 删除链表头部的节点,并返回新的头节点
Node *deleteNodeAtHead(linkedList **head)
{
if (*head)
{
Node *node = *head;
*head = (*head)->next; // 更新链表的头指针为下一个节点
if (*head)
{
(*head)->prev = NULL; // 如果新的头节点存在,更新其前一个节点指针为NULL
}
free(node); // 释放原头节点的内存
return *head; // 返回新的头节点
}
else
{
printf("错误:deleteNodeAtHead(linkedList **head):链表为空,无法执行删除头节点。\n");
return NULL;
}
}
prev
指针判断是否原链表为单节点// 删除链表尾部的节点
void deleteNodeAtTail(linkedList **head)
{
if (*head)
{
Node *tailNode = *head;
while (tailNode->next)
{
tailNode = tailNode->next; // 移动到下一个节点,直到找到最后一个节点
}
if (tailNode->prev)
{
tailNode->prev->next = NULL; // 更新倒数第二个节点的下一个节点指针为NULL
}
else
{
*head = NULL; // 如果链表中只有一个节点,更新链表头指针为NULL
}
free(tailNode); // 释放尾节点的内存
}
}
// 删除链表尾部的节点,并返回新的尾节点
Node *deleteNodeAtTail(linkedList **head)
{
if (*head)
{
Node *tailNode = *head;
while (tailNode->next)
{
tailNode = tailNode->next; // 移动到下一个节点,直到找到最后一个节点
}
if (tailNode->prev)
{
tailNode->prev->next = NULL; // 更新倒数第二个节点的下一个节点指针为NULL
}
else
{
*head = NULL; // 如果链表中只有一个节点,更新链表头指针为NULL
}
free(tailNode); // 释放尾节点的内存
return *head; // 返回新的尾节点
}
else
{
printf("错误:deleteNodeAtTail(linkedList **head):链表为空,无法执行删除尾节点。\n");
return NULL;
}
}
首先确保 l o c a t i o n location location合法即
1 ≤ location ≤ numberOfNodesIn ( list ) 1 \leq \text{location} \leq \text{numberOfNodesIn}(\text{list}) 1≤location≤numberOfNodesIn(list)
标记 l o c a t i o n location location处的节点(如有),不妨记为currentNode
。另记currentNode
的前驱节点和后继节点为prevNdoe
和nextNode
.
只需断开currentNode
和prevNode
、nextNode
的联系,另将prevNode
和nextNode
直接关联即可,并释放currentNode
的内存即可。
特殊的,对于
l o c a t i o n = 1 location=1 location=1或 l o c a t i o n = n u m b e r O f N o d e s I n ( l i s t ) location=numberOfNodesIn(list) location=numberOfNodesIn(list)
即头节点或尾节点,需要做特殊处理,不妨借鉴前文所提到的deleteNodeAtHead
和deleteNodeAtTail
函数。
// 从链表的指定位置删除节点
void deleteNodeAtLocation(linkedList **head, int location)
{
if (location < 1)
{
printf("错误:deleteNodeAtLocation(...int location):位置太小\n");
}
else if (location == 1)
{
deleteNodeAtHead(head); // 在链表头部删除节点
}
else
{
Node *currentNode = findNodeByLocation(*head, location);
if (currentNode)
{
currentNode->prev->next = currentNode->next; // 更新前一个节点的下一个节点指针
if (currentNode->next)
{
currentNode->next->prev = currentNode->prev; // 更新下一个节点的前一个节点指针
}
free(currentNode); // 释放当前节点的内存
}
else
{
printf("错误:deleteNodeAtLocation(...int location):位置太大。\n");
}
}
}
// 从链表的指定位置删除节点,并返回新的头节点
Node *deleteNodeAtLocation(linkedList **head, int location)
{
if (location < 1)
{
printf("错误:deleteNodeAtLocation(...int location):位置太小\n");
return *head;
}
else if (location == 1)
{
return deleteNodeAtHead(head); // 在链表头部删除节点,并返回新的头节点
}
else
{
Node *currentNode = findNodeByLocation(*head, location);
if (currentNode)
{
currentNode->prev->next = currentNode->next; // 更新前一个节点的下一个节点指针
if (currentNode->next)
{
currentNode->next->prev = currentNode->prev; // 更新下一个节点的前一个节点指针
}
free(currentNode); // 释放当前节点的内存
return *head; // 返回新的头节点
}
else
{
printf("错误:deleteNodeAtLocation(...int location):位置太大。\n");
return *head;
}
}
}
此处我们希望通过 l o c a t i o n location location来修改指定位置的节点(如有)的 值。
我们仍然要确保 l o c a t i o n location location合法即
1 ≤ location ≤ numberOfNodesIn ( list ) 1 \leq \text{location} \leq \text{numberOfNodesIn}(\text{list}) 1≤location≤numberOfNodesIn(list)
// 修改链表中某个节点的值
void modifyNodeValue(linkedList **head, int location, elementType newValue)
{
Node *node = findNodeByLocation(*head, location);
if (node)
{
node->value = newValue; // 修改节点的值
}
}
遍历是链表中最基本的操作,在前文中多个函数中都多次出现。
不同的是,根据 DRY 原则,为了提高函数的灵活性,我们使 traverseLinkedList
函数允许接收 void
类型的函数指针。
这种方式提高了函数的通用性和可重用性,允许我们在不更改 traverseLinkedList
函数的情况下,根据需要执行不同的操作。这种设计方式常用于编写更具扩展性的代码。
DRY (Don’t repeat yourself),是敏捷开发的核心设计原则之一。
这个函数指针允许我们传递一个自定义的操作(函数)作为参数给 traverseLinkedList
。
这样,我们可以定义任何操作,将其应用到链表的每个节点上,而不必修改 traverseLinkedList
函数本身。
相信看到这里,聪明的你会发现前文中出现的插入、查找、删除等操作都可以在主文件中写个函数,应用到这个 traverseLinkedList
函数来实现,似乎违背了 DRY 原则,但是我们不能过度的追求灵活性。
在自定义头文件中实现需求量大的操作如插入、删除等,可以减少程序的 bug
量,让别人或以后的自己的程序更鲁棒!
traverseLinkedList
函数如下:
// 遍历链表并执行操作
void traverseLinkedList(linkedList *head, void (*operation)(Node *))
{
while (head)
{
operation(head); // 执行传入的操作函数
head = head->next; // 移动到下一个节点
}
}
如果你想要遍历链表并打印每个节点的值,创建一个打印函数,将其传递给 traverseLinkedList
,它将在遍历链表时对每个节点调用这个函数来执行打印操作。
打印函数见下面:
void print(linkedList *node)
{
if (node)
{
int value = node->value;
if (!node->next && !node->prev)
{
printf("null <- %d -> null", value);
}
else if (!node->prev)
{
printf("null <- %d <-> ", value);
}
else if (node->next && node->prev)
{
printf("%d <-> ", value);
}
else
{
printf("%d -> null\n", value);
}
}
}
在主函数主应用
traverseLinkedList(list, print);
运行结果大抵如下:
双链表的翻转明显要比单链表的翻转要复杂一些。在递归和迭代两种方法中,需要考虑的细节较多。建议不必急于迅速掌握,而应当深刻理解这些代码的原理。
// 递归方式反转链表
Node *reverseLinkedListRecursively(linkedList *head)
{
if (!head || !head->next)
{
return head; // 如果链表为空或只有一个节点,直接返回
}
Node *newHead = reverseLinkedListRecursively(head->next); // 递归反转后续链表
// 更新节点指针
head->next->prev = head->next->next;
head->next->next = head;
// 如果是头节点,更新前一个节点的指针
if (!head->prev)
{
head->prev = head->next;
head->next = NULL; // 将头节点的下一个节点指针设为NULL
}
return newHead; // 返回反转后的链表
}
// 迭代方式反转链表
Node *reverseLinkedListIteratively(linkedList *head)
{
Node *prevNode = NULL;
Node *currentNode = head;
Node *nextNode;
while (currentNode)
{
nextNode = currentNode->next; // 保存下一个节点的指针
currentNode->next = prevNode; // 反转节点指针
// 更新前一个节点和当前节点
prevNode = currentNode;
currentNode = nextNode;
}
return prevNode; // 返回反转后的链表
}
清空链表无论是双链表还是单链表,实现的逻辑大都一般无二。
如果你对递归有浅显的理解,相信不难理解下面的代码。
如果读者感兴趣,不妨试着用迭代实现。
// 清空链表并释放内存
void clearLinkedList(linkedList **head)
{
if (!(*head))
{
return; // 如果链表为空,直接返回
}
clearLinkedList(&(*head)->next); // 递归清空下一个节点
*head = NULL; // 更新当前节点为NULL
free(*head); // 释放当前节点的内存
}
根据双链表的定义实现即可,不难。
下面是迭代实现的一个实例。
如果读者感兴趣,不妨试着再用递归实现下。
// 检查双向链表是否有效
bool isDoublyLinkedListValid(linkedList *head)
{
if (!head)
{
return true; // 空链表被认为是有效的
}
linkedList *current = head;
linkedList *prev = NULL;
while (current)
{
if (current->prev != prev)
{
return false; // 前一个节点指针不正确
}
prev = current;
current = current->next;
}
// 确保最后一个节点的下一个节点指针是NULL
return (prev->next == NULL);
}
// D:\A_Shun_Exclusive\life_ofuniversity\code_collection\C\VSCODE\include\DataStructure\my_double_basic_linklist.h
#pragma once
#include
#include
#include
typedef struct linkedListNode linkedList;
typedef linkedList Node;
typedef int elementType;
struct linkedListNode
{
elementType value;
linkedList *next;
linkedList *prev;
};
// 创造节点
Node *createNode(elementType value, Node *next);
// 根据节点值查找链表中的节点
Node *findNodeByValue(linkedList *head, elementType value);
// 根据节点位置查找链表中的节点
Node *findNodeByLocation(linkedList *head, int location);
// 检查链表中是否存在特定的值
bool containsValueInLinkedList(linkedList *head, elementType value);
// 获取链表的长度
int getLengthOfLinkedList(linkedList *head);
// 在链表的指定位置插入节点
void insertNodeAtLocation(linkedList **head, elementType value, int location);
// 在链表头部插入节点
void insertNodeAtHead(linkedList **head, elementType value);
// 在链表尾部插入节点
void insertNodeAtTail(linkedList **head, elementType value);
// 从链表的指定位置删除节点
void deleteNodeAtLocation(linkedList **head, int location);
// 删除链表头部的节点
void deleteNodeAtHead(linkedList **head);
// 删除链表尾部的节点
void deleteNodeAtTail(linkedList **head);
// 修改链表中某个节点的值
void modifyNodeValue(linkedList **head, int location, elementType newValue);
// 遍历链表并执行操作
void traverseLinkedList(linkedList *head, void (*operation)(Node *));
// 反转链表,递归实现
Node *reverseLinkedList(linkedList *head);
// 清空链表并释放内存
void clearLinkedList(linkedList **head);
// 检查是否合理双链表
bool isDoublyLinkedListValid(linkedList *head);
//D:\A_Shun_Exclusive\life_ofuniversity\code_collection\C\VSCODE\include\DataStructure\my_double_basic_linklist.c
#include "my_double_basic_linklist.h"
// 创建一个新节点并返回其指针
Node *createNode(elementType value, Node *next)
{
Node *node = (Node *)malloc(sizeof(Node));
if (node == NULL)
{
// 处理内存分配失败(例如,打印错误消息)
printf("错误:在 createNode 中内存分配失败。\n");
exit(1); // 或返回 NULL 或执行其他错误处理
}
node->value = value; // 设置节点的值
node->next = next; // 设置节点的下一个节点指针
node->prev = NULL; // 设置节点的前一个节点指针为NULL
return node; // 返回新节点的指针
}
// 根据节点值查找链表中的节点
Node *findNodeByValue(linkedList *head, elementType value)
{
Node *node = head;
while (node)
{
if (node->value == value)
{
return node; // 返回找到的节点
}
node = node->next; // 移动到下一个节点
}
return NULL; // 如果未找到匹配的节点,则返回NULL
}
// 根据节点位置查找链表中的节点
Node *findNodeByLocation(linkedList *head, int location)
{
while (--location)
{
if (!head)
{
return head; // 如果位置超出链表长度,返回NULL
}
head = head->next; // 移动到下一个节点
}
return head; // 返回找到的节点
}
// 检查链表中是否存在特定的值
bool containsValueInLinkedList(linkedList *head, elementType value)
{
return findNodeByValue(head, value) != NULL; // 如果找到匹配的节点,返回true,否则返回false
}
// 获取链表的长度
int getLengthOfLinkedList(linkedList *head)
{
int length = 0;
while (head)
{
head = head->next; // 移动到下一个节点
length++; // 增加长度计数
}
return length; // 返回链表的长度
}
// 在链表的指定位置插入节点
void insertNodeAtLocation(linkedList **head, elementType value, int location)
{
if (location < 1)
{
printf("错误:insertNodeAtLocation(...int location):位置太小\n");
}
else if (location == 1)
{
insertNodeAtHead(head, value); // 在链表头部插入节点
}
else
{
Node *prevNode = findNodeByLocation(*head, location - 1);
if (prevNode)
{
Node *newNode = createNode(value, prevNode->next); // 创建新节点
if (prevNode->next)
{
prevNode->next->prev = newNode; // 更新下一个节点的前一个节点指针
}
prevNode->next = newNode; // 更新前一个节点的下一个节点指针
newNode->prev = prevNode; // 更新新节点的前一个节点指针
}
else
{
printf("错误:insertNodeAtLocation(...int location):位置太大\n");
}
}
}
// 在链表头部插入节点
void insertNodeAtHead(linkedList **head, elementType value)
{
Node *newNode = createNode(value, *head); // 创建新节点
if (*head)
{
(*head)->prev = newNode; // 更新原头节点的前一个节点指针
}
*head = newNode; // 更新链表的头节点指针
}
// 在链表尾部插入节点
void insertNodeAtTail(linkedList **head, elementType value)
{
Node *newNode = createNode(value, NULL); // 创建新节点
Node *currentNode = *head;
if (!currentNode)
{
*head = newNode; // 如果链表为空,将新节点设置为头节点
}
else
{
while (currentNode->next)
{
currentNode = currentNode->next; // 移动到下一个节点,直到找到最后一个节点
}
currentNode->next = newNode; // 更新最后一个节点的下一个节点指针
newNode->prev = currentNode; // 更新新节点的前一个节点指针
}
}
// 从链表的指定位置删除节点
void deleteNodeAtLocation(linkedList **head, int location)
{
if (location < 1)
{
printf("错误:deleteNodeAtLocation(...int location):位置太小\n");
}
else if (location == 1)
{
deleteNodeAtHead(head); // 在链表头部删除节点
}
else
{
Node *currentNode = findNodeByLocation(*head, location);
if (currentNode)
{
currentNode->prev->next = currentNode->next; // 更新前一个节点的下一个节点指针
if (currentNode->next)
{
currentNode->next->prev = currentNode->prev; // 更新下一个节点的前一个节点指针
}
free(currentNode); // 释放当前节点的内存
}
else
{
printf("错误:deleteNodeAtLocation(...int location):位置太大。\n");
}
}
}
// 删除链表头部的节点
void deleteNodeAtHead(linkedList **head)
{
if (*head)
{
Node *node = *head;
*head = (*head)->next; // 更新链表的头指针为下一个节点
if (*head)
{
(*head)->prev = NULL; // 如果新的头节点存在,更新其前一个节点指针为NULL
}
free(node); // 释放原头节点的内存
}
else
{
printf("错误:deleteNodeAtHead(linkedList **head):链表为空,无法执行删除头节点。\n");
}
}
// 删除链表尾部的节点
void deleteNodeAtTail(linkedList **head)
{
if (*head)
{
Node *tailNode = *head;
while (tailNode->next)
{
tailNode = tailNode->next; // 移动到下一个节点,直到找到最后一个节点
}
if (tailNode->prev)
{
tailNode->prev->next = NULL; // 更新倒数第二个节点的下一个节点指针为NULL
}
else
{
*head = NULL; // 如果链表中只有一个节点,更新链表头指针为NULL
}
free(tailNode); // 释放尾节点的内存
}
}
// 修改链表中某个节点的值
void modifyNodeValue(linkedList **head, int location, elementType newValue)
{
Node *node = findNodeByLocation(*head, location);
if (node)
{
node->value = newValue; // 修改节点的值
}
}
// 遍历链表并执行操作
void traverseLinkedList(linkedList *head, void (*operation)(Node *))
{
while (head)
{
operation(head); // 执行传入的操作函数
head = head->next; // 移动到下一个节点
}
}
// 反转链表,递归实现
Node *reverseLinkedList(linkedList *head)
{
if (!head || !head->next)
{
return head; // 如果链表为空或只有一个节点,直接返回
}
Node *node = reverseLinkedList(head->next); // 递归反转后续链表
head->next->prev = head->next->next; // 更新下一个节点的前一个节点指针
head->next->next = head; // 更新下一个节点的下一个节点指针
if (!head->prev)
{
head->prev = head->next; // 如果是头节点,更新前一个节点的指针
head->next = NULL; // 将头节点的下一个节点指针设为NULL
}
return node; // 返回反转后的链表
}
// 清空链表并释放内存
void clearLinkedList(linkedList **head)
{
if (!(*head))
{
return; // 如果链表为空,直接返回
}
clearLinkedList(&(*head)->next); // 递归清空下一个节点
*head = NULL; // 更新当前节点为NULL
free(*head); // 释放当前节点的内存
}
// 检查双向链表是否有效
bool isDoublyLinkedListValid(linkedList *head)
{
if (!head)
{
return true; // 空链表被认为是有效的
}
linkedList *current = head;
linkedList *prev = NULL;
while (current)
{
if (current->prev != prev)
{
return false; // 前一个节点指针不正确
}
prev = current;
current = current->next;
}
// 确保最后一个节点的下一个节点指针是NULL
return (prev->next == NULL);
}
//D :\A_Shun_Exclusive\life_ofuniversity\code_collection\C\VSCODE\My_Code\C\test\Ten\T_10_first.c
#include
#include "DataStructure/my_double_basic_linklist.h"
// ANSI_COLOR_RED 是一个宏定义,用于设置控制台文本颜色为红色
#define ANSI_COLOR_RED "\x1b[31m"
// ANSI_COLOR_RESET 是一个宏定义,用于重置控制台文本颜色为默认值
#define ANSI_COLOR_RESET "\x1b[0m"
void
print(linkedList *node)
{
if (node)
{
int value = node->value;
if (!node->next && !node->prev)
{
printf("null<- %d ->null", value);
}
else if (!node->prev)
{
printf("null<- %d <-> ", value);
}
else if (node->next && node->prev)
{
printf("%d <-> ", value);
}
else
{
printf("%d ->null\n", value);
}
}
}
// Function to generate a random integer between min and max
int randomInt(int min, int max)
{
return min + rand() % (max - min + 1);
}
// Function to print a menu for user selection
void printMenu()
{
printf("\n╭────────────────────────────╮\n");
printf("│ Linked List Test Menu │\n");
printf("╰────────────────────────────╯\n");
printf("1. 插入节点至链表头部\n");
printf("2. 插入节点至链表尾部\n");
printf("3. 插入节点至指定位置\n");
printf("4. 删除链表头部的节点\n");
printf("5. 删除链表尾部的节点\n");
printf("6. 删除指定位置的节点\n");
printf("7. 修改节点的值\n");
printf("8. 根据值查找节点\n");
printf("9. 根据位置查找节点\n");
printf("10. 检查值是否存在\n");
printf("11. 获取链表长度\n");
printf("12. 反转链表\n");
printf("13. 清空链表\n");
printf("14. 查看链表\n");
printf("15. 随机测试\n");
printf("0. 退出\n");
printf("请选择操作: _ \b\b");
}
// 插入节点至链表头部
void insertNodeAtHead_case(linkedList **head)
{
int N;
printf("正在执行插头操作...\n");
do
{
printf("执行此操作的次数 (正整数): ");
scanf("%d", &N);
if (N <= 0)
{
printf("Error:执行操作的次数必须是正整数。\n");
}
} while (N <= 0);
while (N--)
{
int value;
printf("请输入要插入的值: __\b\b");
scanf("%d", &value);
insertNodeAtHead(head, value);
printf("节点%d已插入至链表头部。\n", (*head)->value);
if (!N)
{
printf("插头操作完成.\n链表如下:\n");
}
traverseLinkedList(*head, print);
}
}
void insertNodeAtTail_case(linkedList **head)
{
int N;
printf("正在执行插尾操作...\n");
do
{
printf("执行此操作的次数 (正整数): ");
scanf("%d", &N);
if (N <= 0)
{
printf("错误:插入次数必须是正整数。\n");
}
} while (N <= 0);
while (N--)
{
int value;
printf("请输入要插入的值: ");
scanf("%d", &value);
insertNodeAtTail(head, value);
printf("节点%d已插入至链表尾部。\n", (*head)->value);
if (!N)
{
printf("插尾操作完成.\n链表如下:\n");
}
traverseLinkedList(*head, print);
}
}
void insertNodeAtLocation_case(linkedList **head)
{
int N;
printf("正在执行一般插入操作...\n");
do
{
printf("执行此操作的次数 (正整数): ");
scanf("%d", &N);
if (N <= 0)
{
printf("错误:插入次数必须是正整数。\n");
}
} while (N <= 0);
while (N--)
{
int location, value;
printf("请输入要插入的位置: ");
scanf("%d", &location);
printf("请输入要插入的值: ");
scanf("%d", &value);
insertNodeAtLocation(head, value, location);
printf("节点已插入至指定位置。\n");
if (!N)
{
printf("操作完成:\n");
}
traverseLinkedList(*head, print);
}
}
void deleteNodeAtHead_case(linkedList **head)
{
int N;
printf("正在执行删除头操作...\n");
do
{
printf("执行此操作的次数 (正整数): ");
scanf("%d", &N);
if (N <= 0)
{
printf("错误:删除次数必须是正整数。\n");
}
} while (N <= 0);
while (N--)
{
deleteNodeAtHead(head);
printf("已删除链表头部的节点。\n");
if (!N)
{
printf("删除头操作完成.\n链表如下:\n");
}
traverseLinkedList(*head, print);
}
}
void deleteNodeAtTail_case(linkedList **head)
{
int N;
printf("正在执行删除尾操作...\n");
do
{
printf("执行此操作的次数 (正整数): ");
scanf("%d", &N);
if (N <= 0)
{
printf("错误:删除次数必须是正整数。\n");
}
} while (N <= 0);
while (N--)
{
deleteNodeAtTail(head);
printf("已删除链表尾部的节点。\n");
if (!N)
{
printf("删除尾操作完成.\n链表如下:\n");
}
traverseLinkedList(*head, print);
}
}
void deleteNodeAtLocation_case(linkedList **head)
{
int N;
printf("正在执行删除位置操作...\n");
do
{
printf("执行此操作的次数 (正整数): ");
scanf("%d", &N);
if (N <= 0)
{
printf("错误:删除次数必须是正整数。\n");
}
} while (N <= 0);
while (N--)
{
int location;
printf("请输入要删除的位置: ");
scanf("%d", &location);
deleteNodeAtLocation(head, location);
printf("已删除指定位置的节点。\n");
if (!N)
{
printf("删除位置操作完成.\n链表如下:\n");
}
traverseLinkedList(*head, print);
}
}
void modifyNodeValue_case(linkedList **head)
{
int N;
printf("正在执行修改值操作...\n");
do
{
printf("执行此操作的次数 (正整数): ");
scanf("%d", &N);
if (N <= 0)
{
printf("错误:修改次数必须是正整数。\n");
}
} while (N <= 0);
while (N--)
{
int location, value;
printf("请输入要修改的位置: ");
scanf("%d", &location);
printf("请输入新的值: ");
scanf("%d", &value);
modifyNodeValue(head, location, value);
printf("节点的值已修改。\n");
if (!N)
{
printf("修改值操作完成.\n");
}
traverseLinkedList(*head, print);
}
}
void findNodeByValue_case(linkedList *head)
{
int N;
printf("正在执行按值查找操作...\n");
do
{
printf("执行此操作的次数 (正整数): ");
scanf("%d", &N);
if (N <= 0)
{
printf("错误:查找次数必须是正整数。\n");
}
} while (N <= 0);
while (N--)
{
int value;
printf("请输入要查找的值: ");
scanf("%d", &value);
Node *foundNode = findNodeByValue(head, value);
if (foundNode)
{
printf("已找到节点,位置为:%d\n", foundNode->value);
}
else
{
printf("未找到节点。\n");
}
}
}
void findNodeByLocation_case(linkedList *head)
{
int N;
printf("正在执行按位置查找操作...\n");
do
{
printf("执行此操作的次数 (正整数): ");
scanf("%d", &N);
if (N <= 0)
{
printf("错误:查找次数必须是正整数。\n");
}
} while (N <= 0);
while (N--)
{
int location;
printf("请输入要查找的位置: ");
scanf("%d", &location);
Node *nodeAtLocation = findNodeByLocation(head, location);
if (nodeAtLocation)
{
printf("位置 %d 的值为:%d\n", location, nodeAtLocation->value);
}
else
{
printf("未找到位置。\n");
}
}
}
void containsValueInLinkedList_case(linkedList *head)
{
int N;
printf("正在执行值检查操作...\n");
do
{
printf("执行此操作的次数 (正整数): ");
scanf("%d", &N);
if (N <= 0)
{
printf("错误:操作次数必须是正整数。\n");
}
} while (N <= 0);
while (N--)
{
int value;
printf("请输入要检查的值: ");
scanf("%d", &value);
if (containsValueInLinkedList(head, value))
{
printf("值存在于链表中。\n");
}
else
{
printf("值不存在于链表中。\n");
}
}
}
// 获取链表长度
void getLengthOfLinkedList_case(linkedList *head)
{
printf("链表长度: %d\n", getLengthOfLinkedList(head));
}
// 反转链表
void reverseLinkedList_case(linkedList **head)
{
*head = reverseLinkedList(*head);
printf("链表已反转。\n");
printf("此时链表如下:\n");
traverseLinkedList(*head, print);
}
// 清空链表
void clearLinkedList_case(linkedList **head)
{
clearLinkedList(head);
printf("链表已清空。\n");
}
// 随机测试
void randomTest_case(linkedList **head)
{
int N;
do
{
printf("执行随机操作的次数 (正整数): ");
scanf("%d", &N);
if (N <= 0)
{
printf("错误:操作次数必须是正整数。\n");
}
} while (N <= 0);
while (N--)
{
int operation = randomInt(1, 12); // 生成一个随机操作码
switch (operation)
{
case 1:
{
int value = randomInt(1, 100);
insertNodeAtHead(head, value);
printf("节点 %d 已插入头部.\n", value);
}
break;
case 2:
{
int value = randomInt(1, 100);
insertNodeAtTail(head, value);
printf("节点 %d 已插入尾部.\n", value);
}
break;
case 3:
{
int value = randomInt(1, 100);
int location = randomInt(1, getLengthOfLinkedList(*head) + 1);
insertNodeAtLocation(head, value, location);
printf("节点 %d 已插入位置 %d.\n", value, location);
}
break;
case 4:
deleteNodeAtHead(head);
printf("已删除链表头部的节点.\n");
break;
case 5:
deleteNodeAtTail(head);
printf("已删除链表尾部的节点.\n");
break;
case 6:
{
int location = randomInt(1, getLengthOfLinkedList(*head) + 1);
deleteNodeAtLocation(head, location);
printf("已删除位置 %d 的节点.\n", location);
}
break;
case 7:
{
int location = randomInt(1, getLengthOfLinkedList(*head) + 1);
int value = randomInt(1, 100);
modifyNodeValue(head, location, value);
printf("位置 %d 的节点的值已修改为 %d.\n", location, value);
}
break;
case 8:
{
int value = randomInt(1, 100);
Node *foundNode = findNodeByValue(*head, value);
if (foundNode)
{
printf("已找到节点,位置为:%d\n", foundNode->value);
}
else
{
printf("未找到节点。\n");
}
}
break;
case 9:
{
int location = randomInt(1, getLengthOfLinkedList(*head) + 1);
Node *nodeAtLocation = findNodeByLocation(*head, location);
if (nodeAtLocation)
{
printf("位置 %d 的值为:%d\n", location, nodeAtLocation->value);
}
else
{
printf("未找到位置。\n");
}
}
break;
case 10:
{
int value = randomInt(1, 100);
if (containsValueInLinkedList(*head, value))
{
printf("值 %d 存在于链表中.\n", value);
}
else
{
printf("值 %d 不存在于链表中.\n", value);
}
}
break;
case 11:
printf("链表长度: %d\n", getLengthOfLinkedList(*head));
break;
case 12:
*head = reverseLinkedList(*head);
printf("链表已反转.\n");
break;
}
if (!isDoublyLinkedListValid(*head))
{
printf(ANSI_COLOR_RED "Error: 双向链表结构异常!\n!!\n!!!" ANSI_COLOR_RESET "\n");
printf("按任意键继续...");
getchar();
}
printf("链表如下:\n");
traverseLinkedList(*head, print);
}
}
int main(void)
{
linkedList *head = NULL;
srand(time(NULL));
int choice;
do
{
printMenu();
scanf("%d", &choice);
switch (choice)
{
case 1:
insertNodeAtHead_case(&head);
break;
case 2:
insertNodeAtTail_case(&head);
break;
case 3:
insertNodeAtLocation_case(&head);
break;
case 4:
deleteNodeAtHead_case(&head);
break;
case 5:
deleteNodeAtTail_case(&head);
break;
case 6:
deleteNodeAtLocation_case(&head);
break;
case 7:
modifyNodeValue_case(&head);
break;
case 8:
findNodeByValue_case(head);
break;
case 9:
findNodeByLocation_case(head);
break;
case 10:
containsValueInLinkedList_case(head);
break;
case 11:
getLengthOfLinkedList_case(head);
break;
case 12:
reverseLinkedList_case(&head);
break;
case 13:
clearLinkedList_case(&head);
break;
case 14:
traverseLinkedList(head, print);
break;
case 15:
randomTest_case(&head);
break;
case 0:
break;
default:
printf("无效的选择,请重试。\n");
}
if (!isDoublyLinkedListValid(head))
{
printf(ANSI_COLOR_RED "Error: 双向链表结构异常!\n!!\n!!!" ANSI_COLOR_RESET "\n");
}
getchar();
printf("请输入任意键继续...");
getchar();
} while (choice != 0);
return 0;
}
在探索C语言中的双链表编程之旅中,你已经学到了如何定义、插入、删除、修改和查找双链表中的元素。通过详实的示例和鲁棒的函数实现,你现在具备了编写强大代码的技能。另外,提供的自定义头文件和实现代码将帮助你更轻松地应用这些知识。无论是初学者还是有经验的开发者,都可以从这篇文章中受益匪浅。现在,你可以信心十足地使用双链表来解决各种编程挑战。继续前进,掌握更多强大的编程技巧吧!