在 C 语言编程中,链表是一种非常重要且基础的数据结构。与数组不同,链表的元素在内存中并非连续存储,而是通过指针将各个元素连接起来。这种数据结构具有动态分配内存、插入和删除元素效率高的特点,在很多场景下都有广泛的应用,比如实现栈、队列、图等更复杂的数据结构,或者用于动态管理数据。接下来,我们将详细探讨 C 语言中链表的相关知识。
链表由一系列节点组成,每个节点包含两部分:数据域和指针域。数据域用于存储实际的数据,指针域则存储指向下一个节点的指针(地址)。通过这种方式,节点之间形成了一个链式的结构。
常见的链表类型有单链表、双向链表和循环链表:
在 C 语言中,我们可以使用结构体来定义链表的节点。以下是一个简单的单链表节点的定义示例:
#include
#include
// 定义单链表节点结构体
typedef struct Node {
int data; // 数据域,这里以存储整数为例
struct Node* next; // 指针域,指向下一个节点
} Node;
在这个示例中,Node
结构体包含一个整数类型的数据域 data
和一个指向 Node
结构体的指针 next
,用于指向下一个节点。
为了向链表中添加元素,我们需要先创建新的节点。以下是创建新节点的函数:
// 创建新节点
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node)); // 分配内存
if (newNode == NULL) {
printf("内存分配失败!\n");
exit(1);
}
newNode->data = data; // 初始化数据域
newNode->next = NULL; // 初始化指针域
return newNode;
}
这个函数接受一个整数参数 data
,使用 malloc
函数为新节点分配内存,然后将 data
赋值给新节点的数据域,将指针域初始化为 NULL
,最后返回新节点的指针。
在链表头部插入节点是一种常见的操作。以下是实现该操作的函数:
// 在链表头部插入节点
Node* insertAtHead(Node* head, int data) {
Node* newNode = createNode(data); // 创建新节点
newNode->next = head; // 新节点的指针指向原链表头
return newNode; // 返回新的链表头
}
这个函数接受链表头指针 head
和一个整数 data
作为参数,创建一个新节点,将新节点的指针指向原链表头,然后返回新节点的指针作为新的链表头。
遍历链表是访问链表中每个节点的操作。以下是遍历链表并打印节点数据的函数:
// 遍历链表
void traverseList(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d ", current->data); // 打印节点数据
current = current->next; // 移动到下一个节点
}
printf("\n");
}
这个函数接受链表头指针 head
作为参数,使用一个指针 current
从链表头开始,依次访问每个节点,打印节点的数据,然后将 current
指针移动到下一个节点,直到链表结束(current
为 NULL
)。
删除链表节点也是常见的操作。以下是删除指定数据的节点的函数:
// 删除指定数据的节点
Node* deleteNode(Node* head, int data) {
Node* current = head;
Node* prev = NULL;
// 找到要删除的节点
while (current != NULL && current->data != data) {
prev = current;
current = current->next;
}
// 如果未找到要删除的节点
if (current == NULL) {
return head;
}
// 如果要删除的是头节点
if (prev == NULL) {
head = current->next;
} else {
prev->next = current->next;
}
free(current); // 释放节点内存
return head;
}
这个函数接受链表头指针 head
和一个整数 data
作为参数,使用两个指针 current
和 prev
遍历链表,找到要删除的节点。如果要删除的是头节点,则更新链表头指针;否则,将前一个节点的指针指向要删除节点的下一个节点。最后,释放要删除节点的内存,并返回更新后的链表头指针。
#include
#include
// 定义单链表节点结构体
typedef struct Node {
int data;
struct Node* next;
} Node;
// 创建新节点
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败!\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 在链表头部插入节点
Node* insertAtHead(Node* head, int data) {
Node* newNode = createNode(data);
newNode->next = head;
return newNode;
}
// 遍历链表
void traverseList(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 删除指定数据的节点
Node* deleteNode(Node* head, int data) {
Node* current = head;
Node* prev = NULL;
while (current != NULL && current->data != data) {
prev = current;
current = current->next;
}
if (current == NULL) {
return head;
}
if (prev == NULL) {
head = current->next;
} else {
prev->next = current->next;
}
free(current);
return head;
}
int main() {
Node* head = NULL; // 初始化链表头为 NULL
// 在链表头部插入节点
head = insertAtHead(head, 3);
head = insertAtHead(head, 2);
head = insertAtHead(head, 1);
// 遍历链表
printf("链表元素: ");
traverseList(head);
// 删除节点
head = deleteNode(head, 2);
// 再次遍历链表
printf("删除节点 2 后链表元素: ");
traverseList(head);
return 0;
}
在这个示例中,我们首先初始化链表头为 NULL
,然后使用 insertAtHead
函数在链表头部插入三个节点,接着使用 traverseList
函数遍历链表并打印节点数据,再使用 deleteNode
函数删除节点 2,最后再次遍历链表并打印节点数据。
双向链表的节点除了有一个指向下一个节点的指针外,还有一个指向前一个节点的指针。以下是双向链表节点的定义示例:
typedef struct DNode {
int data;
struct DNode* prev;
struct DNode* next;
} DNode;
双向链表的插入和删除操作相对复杂一些,需要同时更新前向指针和后向指针。具体操作见后文。
循环链表分为单循环链表和双循环链表。单循环链表的尾节点的指针指向头节点,双循环链表的头节点的前向指针指向尾节点,尾节点的后向指针指向头节点。以下是单循环链表的遍历示例:
void traverseCircularList(Node* head) {
Node* current = head;
if (head != NULL) {
do {
printf("%d ", current->data);
current = current->next;
} while (current != head);
}
printf("\n");
}
在实现双向链表的插入和删除操作之前,我们先定义双向链表的节点结构。双向链表的每个节点包含三个部分:数据域、指向前一个节点的指针和指向后一个节点的指针。以下是用 C 语言实现的节点结构定义:
#include
#include
// 定义双向链表节点结构体
typedef struct DNode {
int data; // 数据域
struct DNode* prev; // 指向前一个节点的指针
struct DNode* next; // 指向后一个节点的指针
} DNode;
// 在指定节点之后插入新节点
void insertAfter(DNode* prevNode, int newData) {
if (prevNode == NULL) {
printf("前一个节点不能为空\n");
return;
}
// 创建新节点
DNode* newNode = (DNode*)malloc(sizeof(DNode));
newNode->data = newData;
// 插入新节点
newNode->next = prevNode->next;
if (prevNode->next != NULL) {
prevNode->next->prev = newNode;
}
prevNode->next = newNode;
newNode->prev = prevNode;
}
操作步骤:
prevNode
是否为空,如果为空则输出错误信息并返回。next
指针指向 prevNode
的下一个节点。prevNode
的下一个节点不为空,将其 prev
指针指向新节点。prevNode
的 next
指针指向新节点。prev
指针指向 prevNode
。// 在链表头部插入新节点
DNode* insertAtHead(DNode* head, int newData) {
DNode* newNode = (DNode*)malloc(sizeof(DNode));
newNode->data = newData;
newNode->prev = NULL;
newNode->next = head;
if (head != NULL) {
head->prev = newNode;
}
return newNode;
}
操作步骤:
prev
指针置为 NULL
,next
指针指向原链表头节点。prev
指针指向新节点。// 删除指定节点
void deleteNode(DNode** headRef, DNode* delNode) {
if (*headRef == NULL || delNode == NULL) {
return;
}
if (*headRef == delNode) {
*headRef = delNode->next;
}
if (delNode->next != NULL) {
delNode->next->prev = delNode->prev;
}
if (delNode->prev != NULL) {
delNode->prev->next = delNode->next;
}
free(delNode);
}
操作步骤:
prev
指针指向要删除节点的前一个节点。next
指针指向要删除节点的下一个节点。循环链表的节点结构与单链表类似,只是尾节点的 next
指针指向头节点。以下是用 C 语言实现的节点结构定义:
#include
#include
// 定义循环链表节点结构体
typedef struct CNode {
int data;
struct CNode* next;
} CNode;
// 在循环链表头部插入新节点
CNode* insertAtHead(CNode* head, int newData) {
CNode* newNode = (CNode*)malloc(sizeof(CNode));
newNode->data = newData;
if (head == NULL) {
newNode->next = newNode;
return newNode;
}
CNode* last = head;
while (last->next != head) {
last = last->next;
}
newNode->next = head;
last->next = newNode;
return newNode;
}
操作步骤:
next
指针指向自身,返回新节点作为新的链表头节点。next
指针指向原链表头节点。next
指针指向新节点。// 删除循环链表中指定值的节点
CNode* deleteNode(CNode* head, int key) {
if (head == NULL) {
return NULL;
}
CNode* current = head;
CNode* prev = NULL;
do {
if (current->data == key) {
if (current == head) {
if (current->next == head) {
free(current);
return NULL;
}
CNode* last = head;
while (last->next != head) {
last = last->next;
}
head = current->next;
last->next = head;
} else {
prev->next = current->next;
}
free(current);
return head;
}
prev = current;
current = current->next;
} while (current != head);
return head;
}
操作步骤:
NULL
。current
指针指向头节点,prev
指针指向 NULL
。NULL
。next
指针指向新的头节点。prev
节点的 next
指针指向要删除节点的下一个节点。#include
#include
// 双向链表部分
typedef struct DNode {
int data;
struct DNode* prev;
struct DNode* next;
} DNode;
void insertAfter(DNode* prevNode, int newData) {
if (prevNode == NULL) {
printf("前一个节点不能为空\n");
return;
}
DNode* newNode = (DNode*)malloc(sizeof(DNode));
newNode->data = newData;
newNode->next = prevNode->next;
if (prevNode->next != NULL) {
prevNode->next->prev = newNode;
}
prevNode->next = newNode;
newNode->prev = prevNode;
}
DNode* insertAtHead(DNode* head, int newData) {
DNode* newNode = (DNode*)malloc(sizeof(DNode));
newNode->data = newData;
newNode->prev = NULL;
newNode->next = head;
if (head != NULL) {
head->prev = newNode;
}
return newNode;
}
void deleteNode(DNode** headRef, DNode* delNode) {
if (*headRef == NULL || delNode == NULL) {
return;
}
if (*headRef == delNode) {
*headRef = delNode->next;
}
if (delNode->next != NULL) {
delNode->next->prev = delNode->prev;
}
if (delNode->prev != NULL) {
delNode->prev->next = delNode->next;
}
free(delNode);
}
// 循环链表部分
typedef struct CNode {
int data;
struct CNode* next;
} CNode;
CNode* insertAtHead(CNode* head, int newData) {
CNode* newNode = (CNode*)malloc(sizeof(CNode));
newNode->data = newData;
if (head == NULL) {
newNode->next = newNode;
return newNode;
}
CNode* last = head;
while (last->next != head) {
last = last->next;
}
newNode->next = head;
last->next = newNode;
return newNode;
}
CNode* deleteNode(CNode* head, int key) {
if (head == NULL) {
return NULL;
}
CNode* current = head;
CNode* prev = NULL;
do {
if (current->data == key) {
if (current == head) {
if (current->next == head) {
free(current);
return NULL;
}
CNode* last = head;
while (last->next != head) {
last = last->next;
}
head = current->next;
last->next = head;
} else {
prev->next = current->next;
}
free(current);
return head;
}
prev = current;
current = current->next;
} while (current != head);
return head;
}
// 测试函数
int main() {
// 测试双向链表
DNode* dHead = NULL;
dHead = insertAtHead(dHead, 1);
insertAfter(dHead, 2);
deleteNode(&dHead, dHead->next);
// 测试循环链表
CNode* cHead = NULL;
cHead = insertAtHead(cHead, 3);
cHead = insertAtHead(cHead, 4);
cHead = deleteNode(cHead, 3);
return 0;
}
这个测试代码分别对双向链表和循环链表的插入和删除操作进行了简单的测试。可以根据需要添加更多的测试用例来验证这些操作的正确性。
链表作为一种基础且重要的数据结构,在计算机科学和软件开发的众多领域都有广泛的应用。以下将详细介绍链表在不同场景下的应用。
链表是 C 语言中一种重要的数据结构,具有动态分配内存、插入和删除效率高的特点。通过合理使用链表,我们可以解决很多实际编程中的问题。在使用链表时,要注意内存的分配和释放,避免内存泄漏。同时,要根据具体的应用场景选择合适的链表类型,如单链表、双向链表或循环链表。