C语言中的双链表详细解释与实现
双链表(Doubly Linked List)是一种常见的数据结构,它比单链表更灵活,因为每个节点都包含指向前驱和后继节点的指针。
双链表的基本结构
节点定义
c
复制
下载
typedef struct Node {
int data; // 数据域
struct Node* prev; // 前驱指针
struct Node* next; // 后继指针
} Node;
双链表结构
c
复制
下载
typedef struct {
Node* head; // 头指针
Node* tail; // 尾指针
int size; // 链表长度
} DoublyLinkedList;
基本操作实现
1. 初始化双链表
c
复制
下载
void initList(DoublyLinkedList* list) {
list->head = NULL;
list->tail = NULL;
list->size = 0;
}
2. 创建新节点
c
复制
下载
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败!\n");
exit(1);
}
newNode->data = data;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}
3. 在头部插入节点
c
复制
下载
void insertAtHead(DoublyLinkedList* list, int data) {
Node* newNode = createNode(data);
if (list->head == NULL) { // 空链表
list->head = list->tail = newNode;
} else {
newNode->next = list->head;
list->head->prev = newNode;
list->head = newNode;
}
list->size++;
}
4. 在尾部插入节点
c
复制
下载
void insertAtTail(DoublyLinkedList* list, int data) {
Node* newNode = createNode(data);
if (list->tail == NULL) { // 空链表
list->head = list->tail = newNode;
} else {
newNode->prev = list->tail;
list->tail->next = newNode;
list->tail = newNode;
}
list->size++;
}
5. 在指定位置插入节点
c
复制
下载
void insertAtPosition(DoublyLinkedList* list, int data, int position) {
if (position < 0 || position > list->size) {
printf("无效的位置!\n");
return;
}
if (position == 0) {
insertAtHead(list, data);
} else if (position == list->size) {
insertAtTail(list, data);
} else {
Node* newNode = createNode(data);
Node* current = list->head;
// 移动到要插入位置的前一个节点
for (int i = 0; i < position - 1; i++) {
current = current->next;
}
newNode->next = current->next;
newNode->prev = current;
current->next->prev = newNode;
current->next = newNode;
list->size++;
}
}
6. 删除头节点
c
复制
下载
void deleteHead(DoublyLinkedList* list) {
if (list->head == NULL) {
printf("链表为空,无法删除!\n");
return;
}
Node* temp = list->head;
list->head = list->head->next;
if (list->head == NULL) { // 删除后链表为空
list->tail = NULL;
} else {
list->head->prev = NULL;
}
free(temp);
list->size--;
}
7. 删除尾节点
c
复制
下载
void deleteTail(DoublyLinkedList* list) {
if (list->tail == NULL) {
printf("链表为空,无法删除!\n");
return;
}
Node* temp = list->tail;
list->tail = list->tail->prev;
if (list->tail == NULL) { // 删除后链表为空
list->head = NULL;
} else {
list->tail->next = NULL;
}
free(temp);
list->size--;
}
8. 删除指定位置的节点
c
复制
下载
void deleteAtPosition(DoublyLinkedList* list, int position) {
if (position < 0 || position >= list->size) {
printf("无效的位置!\n");
return;
}
if (position == 0) {
deleteHead(list);
} else if (position == list->size - 1) {
deleteTail(list);
} else {
Node* current = list->head;
// 移动到要删除的节点
for (int i = 0; i < position; i++) {
current = current->next;
}
current->prev->next = current->next;
current->next->prev = current->prev;
free(current);
list->size--;
}
}
9. 查找节点
c
复制
下载
Node* findNode(DoublyLinkedList* list, int data) {
Node* current = list->head;
while (current != NULL) {
if (current->data == data) {
return current;
}
current = current->next;
}
return NULL; // 未找到
}
10. 打印链表(从头到尾)
c
复制
下载
void printListForward(DoublyLinkedList* list) {
Node* current = list->head;
printf("链表(正向): ");
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
11. 打印链表(从尾到头)
c
复制
下载
void printListBackward(DoublyLinkedList* list) {
Node* current = list->tail;
printf("链表(反向): ");
while (current != NULL) {
printf("%d ", current->data);
current = current->prev;
}
printf("\n");
}
12. 释放链表内存
c
复制
下载
void freeList(DoublyLinkedList* list) {
Node* current = list->head;
while (current != NULL) {
Node* temp = current;
current = current->next;
free(temp);
}
list->head = list->tail = NULL;
list->size = 0;
}
完整示例代码
c
复制
下载
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node* prev;
struct Node* next;
} Node;
typedef struct {
Node* head;
Node* tail;
int size;
} DoublyLinkedList;
// 前面定义的所有函数在这里实现
int main() {
DoublyLinkedList list;
initList(&list);
// 插入操作
insertAtHead(&list, 10);
insertAtHead(&list, 20);
insertAtTail(&list, 30);
insertAtPosition(&list, 15, 1);
insertAtPosition(&list, 25, 3);
// 打印链表
printListForward(&list); // 输出: 链表(正向): 20 15 10 25 30
printListBackward(&list); // 输出: 链表(反向): 30 25 10 15 20
// 查找操作
Node* found = findNode(&list, 10);
if (found != NULL) {
printf("找到节点: %d\n", found->data);
} else {
printf("未找到节点\n");
}
// 删除操作
deleteHead(&list);
deleteTail(&list);
deleteAtPosition(&list, 1);
printListForward(&list); // 输出: 链表(正向): 15 25
// 释放内存
freeList(&list);
return 0;
}
双链表的应用场景
-
浏览器历史记录:前进和后退功能
-
文本编辑器:撤销和重做操作
-
音乐播放器:上一曲和下一曲功能
-
实现双端队列(Deque)
-
更复杂的数据结构基础:如平衡树等
双链表的优缺点
优点:
-
可以双向遍历
-
删除操作更高效(已知节点时O(1))
-
插入操作更灵活
缺点:
-
每个节点需要额外空间存储前驱指针
-
操作比单链表稍复杂
通过这个完整的C语言实现,你应该对双链表有了深入的理解。在实际编程中,可以根据需要扩展这些基本操作,例如添加排序、合并等功能。
单链表如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 学生信息结构体
typedef struct Student {
int id;
char name[20];
int age;
float score;
struct Student *next;
} Student;
// 1. 创建链表
Student* createList() {
Student *head = (Student*)malloc(sizeof(Student));
if (head == NULL) {
printf("内存分配失败!\n");
exit(1);
}
head->next = NULL;
return head;
}
// 2. 插入节点 ( 头插法 )
void insertNode(Student *head) {
Student *newNode = (Student*)malloc(sizeof(Student));
if (newNode == NULL) {
printf("内存分配失败!\n");
return;
}
printf("请输入学号: ");
scanf("%d", &newNode->id);
printf("请输入姓名: ");
scanf("%s", newNode->name);
printf("请输入年龄: ");
scanf("%d", &newNode->age);
printf("请输入成绩: ");
scanf("%f", &newNode->score);
newNode->next = head->next;
head->next = newNode;
printf("添加成功!\n");
}
// 3. 删除节点 ( 根据学号 )
void deleteNode(Student *head, int id) {
Student *p = head->next;
Student *prev = head;
while (p != NULL) {
if (p->id == id) {
prev->next = p->next;
free(p);
printf("删除成功!\n");
return;
}
prev = p;
p = p->next;
}
printf("未找到学号为%d的学生!\n", id);
}
// 4. 查找学生信息
void searchStudent(Student *head, int id) {
Student *p = head->next;
while (p != NULL) {
if (p->id == id) {
printf("学号: %d, 姓名: %s, 年龄: %d, 成绩: %.2f\n",
p->id, p->name, p->age, p->score);
return;
}
p = p->next;
}
printf("未找到学号为%d的学生!\n", id);
}
// 5. 打印整个链表
void printList(Student *head) {
Student *p = head->next;
if (p == NULL) {
printf("链表为空!\n");
return;
}
printf("学生信息如下:\n");
printf("学号\t姓名\t年龄\t成绩\n");
while (p != NULL) {
printf("%d\t%s\t%d\t%.2f\n", p->id, p->name, p->age, p->score);
p = p->next;
}
}
// 6. 计算链表长度
int listLength(Student *head) {
int count = 0;
Student *p = head->next;
while (p != NULL) {
count++;
p = p->next;
}
return count;
}
// 7. 清空链表
void clearList(Student *head) {
Student *p = head->next;
Student *temp;
while (p != NULL) {
temp = p;
p = p->next;
free(temp);
}
head->next = NULL;
printf("链表已清空!\n");
}
// 主函数
int main() {
Student *head = createList();
int choice, id;
while (1) {
printf("\n学生信息管理系统\n");
printf("1. 添加学生\n");
printf("2. 删除学生\n");
printf("3. 查找学生\n");
printf("4. 显示所有学生\n");
printf("5. 统计学生人数\n");
printf("6. 清空链表\n");
printf("0. 退出\n");
printf("请选择操作: ");
scanf("%d", &choice);
switch (choice) {
case 1:
insertNode(head);
break;
case 2:
printf("请输入要删除的学生学号: ");
scanf("%d", &id);
deleteNode(head, id);
break;
case 3:
printf("请输入要查找的学生学号: ");
scanf("%d", &id);
searchStudent(head, id);
break;
case 4:
printList(head);
break;
case 5:
printf("当前共有%d名学生\n", listLength(head));
break;
case 6:
clearList(head);
break;
case 0:
clearList(head);
free(head);
printf("程序已退出!\n");
return 0;
default:
printf("无效的选择!\n");
}
}
return 0;
}
算法说明
- 链表结构:使用带头节点的单链表结构,头节点不存储数据,仅作为链表起始标志。
- 插入操作:采用头插法,新节点总是插入到头节点之后,时间复杂度O(1)。
- 删除操作:根据学号查找并删除节点,需要遍历链表,时间复杂度O(n)。
- 查找操作:根据学号线性查找,时间复杂度O(n)。
- 内存管理:每个节点动态分配内存,清空链表时需要逐个释放。
- 用户交互:通过简单的菜单系统实现功能选择。