【数据结构】单链表-c语言
单链表基本概念
头结点(Dummy Node)
定义:头结点是位于链表第一个有效数据结点(开始结点)之前的一个辅助结点,其数据域通常无意义(或存储链表长度等元信息),指针域指向第一个实际存储数据的结点。
作用:
- 简化边界操作:插入/删除第一个实际结点时无需特殊处理头指针。
- 统一操作逻辑:无论链表是否为空,头指针始终指向头结点,避免空指针异常。
头指针(Head Pointer)
定义:头指针是一个指针变量,存储链表中第一个结点的地址。无论链表是否有头结点,头指针必须存在。
特性:
永远指向链表入口:
- 若链表有头结点,头指针指向头结点;
- 若链表无头结点,头指针直接指向第一个数据结点(开始结点)。
判断链表为空的条件:
- 带头结点:
head->next == NULL
; - 不带头结点:
head == NULL
。
开始结点(首元结点)
-
定义:
链表中第一个存储实际数据的结点,位于头结点(如果有)之后。 -
注意:
若链表不带头结点,头指针直接指向开始结点。
对比:带头结点 vs 不带头结点
特性 | 带头结点的链表 | 不带头结点的链表 |
---|---|---|
头指针指向 | 头结点(非数据结点) | 开始结点(第一个数据结点) |
空链表判断条件 | head->next == NULL |
head == NULL |
插入/删除首结点 | 无需修改头指针,逻辑统一 | 需修改头指针,需特殊处理 |
代码复杂度 | 较低(边界情况少) | 较高(需处理头指针变更) |
开始敲代码!
定义单链表结构体
// 定义链表结点结构
typedef struct Node {
int data;
struct Node* next;
}LNode;
初始化单链表操作
不带头结点
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
// 初始化空链表(不带头结点)
Node* initNoDummyList() {
return NULL; // 头指针初始化为 NULL
}
int main() {
Node* head = initNoDummyList(); // 空链表
if (head == NULL) {
printf("链表初始化成功!\n");
}
return 0;
}
带头结点
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
// 初始化空链表(带头结点)
Node *initDummyList() {
Node *dummyHead = (Node *) malloc(sizeof(Node)); // 创建头结点
if (dummyHead == NULL) {
exit(1);
};
dummyHead->next = NULL; // 初始为空链表
return dummyHead;
}
int main() {
Node *head = initDummyList(); // 空链表
if (head != NULL) {
printf("链表初始化成功!\n");
}
return 0;
}
尾插/头插
不带头结点
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* next;
} Node;
// 初始化空链表(不带头结点)
Node* initNoDummyList() {
return NULL; // 头指针初始化为 NULL
}
// 头插法:插入到链表头部
void insertAtHead(Node** head, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = *head; // 新节点指向原头节点
*head = newNode; // 更新头指针
}
// 尾插法:插入到链表尾部
void insertAtTail(Node** head, int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
if (*head == NULL) {
// 链表为空,新节点成为头节点
*head = newNode;
} else {
// 找到最后一个节点并链接新节点
Node* current = *head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
}
}
// 打印链表
void printList(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
int main() {
// 测试头插法
Node* headInsert = initNoDummyList();
insertAtHead(&headInsert, 1); // 头部插入 1
insertAtHead(&headInsert, 2); // 头部插入 2
insertAtHead(&headInsert, 3); // 头部插入 3
printf("头插法结果:");
printList(headInsert); // 输出:3 -> 2 -> 1 -> NULL
// 测试尾插法
Node* tailInsert = initNoDummyList();
insertAtTail(&tailInsert, 1); // 尾部插入 1
insertAtTail(&tailInsert, 2); // 尾部插入 2
insertAtTail(&tailInsert, 3); // 尾部插入 3
printf("尾插法结果:");
printList(tailInsert); // 输出:1 -> 2 -> 3 -> NULL
// 释放内存
free(headInsert);
free(tailInsert);
return 0;
}
带头结点
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构体
typedef struct Node {
int data; // 节点存储的数据
struct Node* next; // 指向下一个节点的指针
} Node;
/**
* 初始化一个带头结点的空链表
* @return 返回指向头结点的指针
*/
Node* initDummyList() {
Node* dummyHead = (Node*)malloc(sizeof(Node)); // 动态分配头结点的内存
if (dummyHead == NULL) {
printf("内存分配失败\n"); // 如果分配失败,打印错误信息
exit(1); // 退出程序
}
dummyHead->next = NULL; // 头结点的 next 初始化为 NULL
return dummyHead; // 返回头结点指针
}
/**
* 头插法:在头结点后插入新节点
* @param dummyHead 指向头结点的指针
* @param data 要插入的数据
*/
void insertAtDummyHead(Node* dummyHead, int data) {
Node* newNode = (Node*)malloc(sizeof(Node)); // 动态分配新节点的内存
if (newNode == NULL) {
printf("内存分配失败\n"); // 如果分配失败,打印错误信息
exit(1); // 退出程序
}
newNode->data = data; // 设置新节点的数据
newNode->next = dummyHead->next; // 新节点指向原第一个节点
dummyHead->next = newNode; // 头结点指向新节点
}
/**
* 尾插法:在链表尾部插入新节点
* @param dummyHead 指向头结点的指针
* @param data 要插入的数据
*/
void insertAtDummyTail(Node* dummyHead, int data) {
Node* newNode = (Node*)malloc(sizeof(Node)); // 动态分配新节点的内存
if (newNode == NULL) {
printf("内存分配失败\n"); // 如果分配失败,打印错误信息
exit(1); // 退出程序
}
newNode->data = data; // 设置新节点的数据
newNode->next = NULL; // 新节点指向 NULL,表示链表的末尾
// 从头结点出发找到最后一个节点
Node* current = dummyHead;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode; // 将新节点链接到尾部
}
/**
* 打印链表中的所有节点数据(跳过头结点)
* @param dummyHead 指向头结点的指针
*/
void printDummyList(Node* dummyHead) {
Node* current = dummyHead->next; // 从第一个节点开始遍历
while (current != NULL) {
printf("%d -> ", current->data); // 打印当前节点的数据
current = current->next; // 移动到下一个节点
}
printf("NULL\n"); // 打印链表结束标志
}
/**
* 释放链表占用的内存(包含头结点)
* @param dummyHead 指向头结点的指针
*/
void freeDummyList(Node* dummyHead) {
Node* current = dummyHead; // 从头结点开始遍历
while (current != NULL) {
Node* temp = current; // 临时保存当前节点
current = current->next; // 移动到下一个节点
free(temp); // 释放当前节点的内存
}
}
int main() {
// 测试头插法
Node* headInsertList = initDummyList(); // 初始化带头结点的空链表
insertAtDummyHead(headInsertList, 1); // 头插 1
insertAtDummyHead(headInsertList, 2); // 头插 2
insertAtDummyHead(headInsertList, 3); // 头插 3
printf("头插法结果:");
printDummyList(headInsertList); // 输出:3 -> 2 -> 1 -> NULL
// 测试尾插法
Node* tailInsertList = initDummyList(); // 初始化带头结点的空链表
insertAtDummyTail(tailInsertList, 1); // 尾插 1
insertAtDummyTail(tailInsertList, 2); // 尾插 2
insertAtDummyTail(tailInsertList, 3); // 尾插 3
printf("尾插法结果:");
printDummyList(tailInsertList); // 输出:1 -> 2 -> 3 -> NULL
// 释放内存
freeDummyList(headInsertList); // 释放头插法链表的内存
freeDummyList(tailInsertList); // 释放尾插法链表的内存
return 0; // 程序正常结束
}
尾删/头删
不带头结点
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构体
typedef struct Node {
int data; // 节点存储的数据
struct Node* next; // 指向下一个节点的指针
} Node;
/**
* 初始化一个空链表
* @return 返回指向链表头节点的指针,初始为NULL
*/
Node* initNoDummyList() { return NULL; } // 返回NULL表示链表为空
/**
* 在链表尾部插入一个新节点
* @param head 指向链表头节点指针的指针
* @param data 要插入的数据
*/
void insertTail(Node** head, int data) {
Node* newNode = (Node*)malloc(sizeof(Node)); // 动态分配新节点的内存
newNode->data = data; // 设置新节点的数据
newNode->next = NULL; // 新节点的下一个节点初始化为NULL
// 如果链表为空,新节点即为头节点
if (*head == NULL) {
*head = newNode;
} else {
// 遍历链表,找到最后一个节点
Node* current = *head;
while (current->next != NULL) {
current = current->next;
}
// 将新节点插入到链表尾部
current->next = newNode;
}
}
/**
* 删除链表的头节点
* @param head 指向链表头节点指针的指针
*/
void deleteHead(Node** head) {
if (*head == NULL) { // 如果链表为空,输出提示信息并返回
printf("链表为空,无法删除\n");
return;
}
Node* temp = *head; // 保存原头节点
*head = (*head)->next; // 头指针指向下一个节点
free(temp); // 释放原头节点内存
}
/**
* 删除链表的尾节点
* @param head 指向链表头节点指针的指针
*/
void deleteTail(Node** head) {
if (*head == NULL) { // 如果链表为空,输出提示信息并返回
printf("链表为空,无法删除\n");
return;
}
// 如果链表只有一个节点,直接删除并置头指针为NULL
if ((*head)->next == NULL) {
free(*head);
*head = NULL;
return;
}
// 遍历链表,找到倒数第二个节点
Node* current = *head;
while (current->next->next != NULL) {
current = current->next;
}
// 删除尾节点
free(current->next);
current->next = NULL;
}
/**
* 打印链表中的所有节点数据
* @param head 指向链表头节点的指针
*/
void printList(Node* head) {
Node* current = head; // 从头节点开始遍历
while (current != NULL) {
printf("%d -> ", current->data); // 打印当前节点的数据
current = current->next; // 移动到下一个节点
}
printf("NULL\n"); // 打印链表结束标志
}
/**
* 释放链表中的所有节点内存
* @param head 指向链表头节点的指针
*/
// 释放链表内存
void freeList(Node* head) {
Node* current = head;
while (current != NULL) {
Node* temp = current;
current = current->next;
free(temp);
}
}
int main() {
Node* head = initNoDummyList(); // 初始化一个空链表
insertTail(&head, 1); // 在链表尾部插入数据1
insertTail(&head, 2); // 在链表尾部插入数据2
insertTail(&head, 3); // 在链表尾部插入数据3
printf("删除前:");
printList(head); // 打印链表:1 -> 2 -> 3 -> NULL
deleteHead(&head); // 删除链表头节点
printf("头删后:");
printList(head); // 打印链表:2 -> 3 -> NULL
deleteTail(&head); // 删除链表尾节点
printf("尾删后:");
printList(head); // 打印链表:2 -> NULL
freeList(head); // 释放链表内存
return 0; // 程序正常结束
}
带头结点
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构