链表
作用
C语言中的链表是一种重要的数据结构,它在内存中不是顺序存储的,而是通过指针将一系列节点串联起来。链表的每个节点通常包含两个部分:数据域和指针域。数据域用于存储数据,指针域则存储指向下一个节点的地址。链表的主要用途包括:
-
动态内存分配:链表允许在运行时动态地添加或删除元素,而不需要重新分配整个数组的内存,这使得链表非常适合于需要频繁插入和删除操作的应用场景。
-
高效地处理大量数据:当处理的数据量非常大,以至于无法预先确定所需内存大小时,链表可以只分配当前需要的内存空间,从而有效利用内存。
-
实现复杂的数据结构:如栈、队列、哈希表、图等更高级的数据结构,都可以基于链表来实现,以满足特定的算法需求。
-
实现函数式数据结构:在某些情况下,为了保持数据的不可变性,可以通过创建新的链表节点来修改链表内容,而不是直接修改原有节点,这在函数式编程中尤为重要。
举例
#include <stdio.h>
#include <stdlib.h>
// 链表节点定义
typedef struct Node {
int data; // 数据域
struct Node* next; // 指针域,指向下一个节点
} Node;
// 创建新节点
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (!newNode) {
printf("Memory error\n");
exit(0);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 在链表头部插入节点
void insertAtHead(Node** head, int data) {
Node* newNode = createNode(data);
newNode->next = *head;
*head = newNode;
}
// 在链表中间指定位置插入节点(假设位置从1开始计数)
void insertInMiddle(Node** head, int data, int position) {
if (position <= 0) {
printf("Invalid position\n");
return;
}
Node* newNode = createNode(data);
if (position == 1) {
insertAtHead(&(*head), data);
return;
}
Node* current = *head;
for (int i = 1; current != NULL && i < position - 1; i++) {
current = current->next;
}
if (current == NULL) {
printf("Position exceeds the length of the list.\n");
free(newNode); // 防止内存泄漏
return;
}
newNode->next = current->next;
current->next = newNode;
}
// 在链表尾部插入节点
void insertAtTail(Node** head, int data) {
Node* newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
Node* current = *head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
}
// 打印链表
void printList(Node* node) {
while (node != NULL) {
printf("%d -> ", node->data);
node = node->next;
}
printf("NULL\n");
}
int main() {
Node* head = NULL;
// 在头部插入
insertAtHead(&head, 5);
printList(head);
// 在中间插入(例如位置2)
insertInMiddle(&head, 10, 2);
printList(head);
// 在尾部插入
insertAtTail(&head, 15);
printList(head);
return 0;
}
二级指针
链表的插入操作不一定都需要使用二级指针,但在某些特定情境下使用二级指针是有其优势的,特别是当需要在函数内部修改链表的头指针时。以下是使用二级指针的一些原因:
-
修改头节点: 当向一个空链表插入第一个节点或在链表头部插入节点时,需要直接修改头指针指向新节点。如果仅使用一级指针传递链表头节点的地址到函数中,虽然可以访问和修改头节点的数据,但无法改变头指针本身的值(即无法使头指针指向新分配的节点)。使用二级指针(即指针的指针),可以确保在函数内部对头指针本身的修改能反映到函数外部。
-
避免复制指针: 当将指针作为参数传递给函数时,默认情况下传递的是指针的副本。如果直接修改这个副本,不会影响到原始指针。使用二级指针作为参数,实际上是传递了指针的地址,因此在函数内部修改这个地址所指向的指针值就能直接影响到原始的指针。
-
初始化链表: 在链表初始化时,通常需要通过动态内存分配创建头节点,然后将头指针指向新分配的内存地址。这时使用二级指针可以确保新分配的内存地址能够被正确赋值给主调函数中的头指针变量。
综上所述,虽然在某些情况下(如向已知位置的链表中间或尾部插入节点,且头节点已经存在且不会变化时),使用一级指针就足够了,但在涉及到修改链表的头指针时,使用二级指针是一种有效的做法,它可以确保对链表结构的修改在函数调用结束后仍然有效。