链表是一种线性数据结构,与数组不同,它在内存中不是连续存储的。链表由一系列节点组成,每个节点包含数据和指向下一个节点的指针。这种链式存储方式使得链表在插入和删除操作上具有高效性,并且可以动态地分配内存,无需像数组那样预先确定大小。
链表主要分为单向链表、双向链表和循环链表:
在 C 语言中,通过结构体来定义链表节点。每个节点包含两部分:数据域和指针域。数据域用于存储实际的数据,指针域用于指向下一个节点。示例代码如下:
// 定义链表节点结构体
typedef struct Node {
int data; // 数据域,这里以整数为例,可根据需求修改
struct Node* next; // 指针域,指向下一个节点
} Node;
创建新节点的过程实际上是动态分配内存并初始化节点数据的过程。使用malloc函数在堆内存中分配空间,然后初始化节点的数据域和指针域。示例代码如下:
// 创建新节点
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败!\n");
return NULL;
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
链表的初始化通常是创建一个空链表,即头指针指向NULL。示例代码如下:
// 初始化链表,返回头指针
Node* initList() {
return NULL;
}
插入节点是链表的重要操作之一,根据插入位置的不同,可分为头插法、尾插法和指定位置插入。
// 头插法插入节点
Node* insertAtHead(Node* head, int data) {
Node* newNode = createNode(data);
newNode->next = head;
return newNode;
}
// 尾插法插入节点
Node* insertAtTail(Node* head, int data) {
Node* newNode = createNode(data);
if (head == NULL) {
return newNode;
}
Node* current = head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
return head;
}
// 在指定位置插入节点
Node* insertAtPosition(Node* head, int data, int position) {
if (position < 1) {
printf("位置无效!\n");
return head;
}
if (position == 1) {
return insertAtHead(head, data);
}
Node* newNode = createNode(data);
Node* current = head;
int count = 1;
while (current != NULL && count < position - 1) {
current = current->next;
count++;
}
if (current == NULL) {
printf("位置超出链表长度!\n");
free(newNode);
return head;
}
newNode->next = current->next;
current->next = newNode;
return head;
}
删除节点同样根据删除位置分为删除头节点、删除尾节点和删除指定位置节点。
// 删除头节点
Node* deleteHead(Node* head) {
if (head == NULL) {
printf("链表为空!\n");
return NULL;
}
Node* temp = head;
head = head->next;
free(temp);
return head;
}
// 删除尾节点
Node* deleteTail(Node* head) {
if (head == NULL) {
printf("链表为空!\n");
return NULL;
}
if (head->next == NULL) {
free(head);
return NULL;
}
Node* current = head;
while (current->next->next != NULL) {
current = current->next;
}
free(current->next);
current->next = NULL;
return head;
}
// 删除指定位置节点
Node* deleteAtPosition(Node* head, int position) {
if (position < 1) {
printf("位置无效!\n");
return head;
}
if (position == 1) {
return deleteHead(head);
}
Node* current = head;
int count = 1;
while (current != NULL && count < position - 1) {
current = current->next;
count++;
}
if (current == NULL || current->next == NULL) {
printf("位置超出链表长度!\n");
return head;
}
Node* temp = current->next;
current->next = current->next->next;
free(temp);
return head;
}
遍历链表是从表头开始,依次访问每个节点的数据。示例代码如下:
// 遍历链表
void traverseList(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
双向链表的节点比单向链表多一个指向前一个节点的指针。示例代码如下:
// 定义双向链表节点结构体
typedef struct DoublyNode {
int data;
struct DoublyNode* prev;
struct DoublyNode* next;
} DoublyNode;
与单向链表类似,使用malloc函数分配内存并初始化节点。示例代码如下:
// 创建双向链表新节点
DoublyNode* createDoublyNode(int data) {
DoublyNode* newNode = (DoublyNode*)malloc(sizeof(DoublyNode));
if (newNode == NULL) {
printf("内存分配失败!\n");
return NULL;
}
newNode->data = data;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}
双向链表的插入操作同样有头插法、尾插法和指定位置插入,不过需要同时处理前驱和后继指针。
// 双向链表头插法插入节点
DoublyNode* insertDoublyAtHead(DoublyNode* head, int data) {
DoublyNode* newNode = createDoublyNode(data);
if (head == NULL) {
return newNode;
}
newNode->next = head;
head->prev = newNode;
return newNode;
}
// 双向链表尾插法插入节点
DoublyNode* insertDoublyAtTail(DoublyNode* head, int data) {
DoublyNode* newNode = createDoublyNode(data);
if (head == NULL) {
return newNode;
}
DoublyNode* current = head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
newNode->prev = current;
return head;
}
删除操作也需要同时更新前驱和后继节点的指针。
// 双向链表删除头节点
DoublyNode* deleteDoublyHead(DoublyNode* head) {
if (head == NULL) {
printf("链表为空!\n");
return NULL;
}
if (head->next == NULL) {
free(head);
return NULL;
}
DoublyNode* temp = head;
head = head->next;
head->prev = NULL;
free(temp);
return head;
}
// 双向链表删除尾节点
DoublyNode* deleteDoublyTail(DoublyNode* head) {
if (head == NULL) {
printf("链表为空!\n");
return NULL;
}
if (head->next == NULL) {
free(head);
return NULL;
}
DoublyNode* current = head;
while (current->next->next != NULL) {
current = current->next;
}
free(current->next);
current->next = NULL;
return head;
}
双向链表支持正向和反向遍历。正向遍历与单向链表类似,反向遍历则从尾节点开始,通过前驱指针向前访问。示例代码如下:
// 正向遍历双向链表
void traverseDoublyListForward(DoublyNode* head) {
DoublyNode* current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 反向遍历双向链表
void traverseDoublyListBackward(DoublyNode* head) {
if (head == NULL) {
return;
}
DoublyNode* current = head;
while (current->next != NULL) {
current = current->next;
}
while (current != NULL) {
printf("%d ", current->data);
current = current->prev;
}
printf("\n");
}
// 初始化单向循环链表
Node* initCircularList() {
return NULL;
}
Node* createFirstNode(int data) {
Node* newNode = createNode(data);
newNode->next = newNode;
return newNode;
}
// 单向循环链表头插法
Node* insertCircularAtHead(Node* head, int data) {
Node* newNode = createNode(data);
if (head == NULL) {
newNode->next = newNode;
return newNode;
}
Node* current = head;
while (current->next != head) {
current = current->next;
}
newNode->next = head;
current->next = newNode;
return newNode;
}
// 单向循环链表尾插法
Node* insertCircularAtTail(Node* head, int data) {
Node* newNode = createNode(data);
if (head == NULL) {
newNode->next = newNode;
return newNode;
}
Node* current = head;
while (current->next != head) {
current = current->next;
}
current->next = newNode;
newNode->next = head;
return head;
}
// 单向循环链表删除头节点
Node* deleteCircularHead(Node* head) {
if (head == NULL) {
printf("链表为空!\n");
return NULL;
}
if (head->next == head) {
free(head);
return NULL;
}
Node* current = head;
while (current->next != head) {
current = current->next;
}
Node* temp = head;
head = head->next;
current->next = head;
free(temp);
return head;
}
双向循环链表结合了双向链表和循环链表的特点,节点有前驱和后继指针,且表头和表尾节点形成闭环。其实现与双向链表和单向循环链表类似,只是在初始化、插入和删除操作时需要同时处理前驱和后继指针,确保形成闭环 。
链表可以根据实际需求动态地分配和释放内存,适合存储数据量不确定的情况。例如,在处理用户输入的字符串序列时,使用链表可以方便地添加或删除字符串节点,而不需要预先分配固定大小的数组。
栈和队列是常用的数据结构,链表可以很好地实现它们。通过链表的头插法和删除头节点操作可以实现栈的 “后进先出” 特性;通过尾插法和删除头节点操作可以实现队列的 “先进先出” 特性 。
在操作系统中,进程的管理通常使用链表来实现。每个进程可以看作是链表中的一个节点,通过链表可以方便地对进程进行创建、删除、调度等操作。
哈希表在处理冲突时,常用的方法之一是链地址法。即将哈希值相同的元素存储在同一个链表中,通过链表来解决哈希冲突 。
以上详细介绍了 C 语言链表的各类知识。如果你还想深入了解链表的某部分内容,比如性能优化或具体应用案例,欢迎和我说说。