数据结构—双向链表(超详细,收藏这一篇就够了)
本文全面介绍了双向链表的数据结构,包括其基本概念、节点结构定义、内存布局以及核心操作实现。双向链表相比单向链表具有双向遍历的优势,每个节点包含指向前驱和后继的指针,使得删除和插入操作更高效,但同时也增加了空间开销和实现复杂度。文章详细讲解了双向链表的初始化、插入(头部/尾部)、删除等基本操作,并提供了C语言实现代码。双向链表适用于需要频繁双向遍历的场景,如浏览器历史记录、文本编辑器撤销功能等。通过
数据结构之双向链表全面详解
1. 双向链表概述
1.1 什么是双向链表
双向链表(Doubly Linked List)是一种特殊的链表结构,它的每个节点包含两个指针,分别指向前一个节点和后一个节点。这种结构使得双向链表可以双向遍历,既可以从头节点开始遍历到尾节点,也可以从尾节点开始遍历到头节点。
与单向链表相比,双向链表在节点结构上多了一个指向前驱节点的指针,这使得双向链表在某些操作上更加高效,特别是在需要反向遍历或删除指定节点时。
// 双向链表节点的基本结构
struct DoublyListNode {
int data; // 数据域
struct DoublyListNode *prev; // 前驱指针,指向前一个节点
struct DoublyListNode *next; // 后继指针,指向后一个节点
};
1.2 双向链表的优势与劣势
优势:
- 双向遍历:可以从两个方向遍历链表
- 高效删除:删除指定节点时不需要查找前驱节点
- 灵活操作:在已知节点位置时,可以在前后快速插入新节点
劣势:
- 空间开销:每个节点需要额外的指针空间
- 实现复杂:操作需要维护两个指针,逻辑更复杂
- 内存占用:相比单向链表多占用约33%的内存空间
1.3 双向链表的应用场景
双向链表在以下场景中特别有用:
- 需要频繁双向遍历的数据集合
- 实现双向队列(Deque)
- 浏览器的前进后退功能
- 文本编辑器的撤销重做功能
- LRU缓存淘汰算法
2. 双向链表的基本结构
2.1 节点结构定义
让我们从最基本的节点结构开始,详细定义双向链表的节点:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 双向链表节点定义
typedef struct DoublyListNode {
int data; // 节点存储的数据
struct DoublyListNode *prev; // 指向前驱节点的指针
struct DoublyListNode *next; // 指向后继节点的指针
} DoublyListNode;
// 双向链表结构定义
typedef struct {
DoublyListNode *head; // 头指针,指向第一个节点
DoublyListNode *tail; // 尾指针,指向最后一个节点
int size; // 链表当前长度
} DoublyLinkedList;
节点结构详细说明:
data:存储节点的实际数据,可以是任意类型prev:指向前一个节点的指针,如果是头节点则为NULLnext:指向后一个节点的指针,如果是尾节点则为NULL
2.2 链表结构定义
双向链表的结构体封装了链表的整体信息:
// 更完整的双向链表结构定义
typedef struct {
DoublyListNode *head; // 链表头指针
DoublyListNode *tail; // 链表尾指针
int size; // 链表当前节点数量
int capacity; // 链表容量(如果有限制)
bool circular; // 是否为循环双向链表
} DoublyLinkedList;
2.3 内存布局可视化
为了更好地理解双向链表的内存布局,让我们创建一个可视化的示例:
// 可视化双向链表的内存布局
void visualizeMemoryLayout(DoublyLinkedList *list) {
if (list == NULL || list->head == NULL) {
printf("链表为空,无法可视化\n");
return;
}
printf("\n=== 双向链表内存布局可视化 ===\n");
printf("链表头指针: %p\n", (void*)list->head);
printf("链表尾指针: %p\n", (void*)list->tail);
printf("链表长度: %d\n", list->size);
DoublyListNode *current = list->head;
int position = 0;
while (current != NULL) {
printf("\n节点[%d]:\n", position);
printf(" 地址: %p\n", (void*)current);
printf(" 数据: %d\n", current->data);
printf(" 前驱: %p\n", (void*)current->prev);
printf(" 后继: %p\n", (void*)current->next);
// 检查指针一致性
if (current->prev != NULL && current->prev->next != current) {
printf(" ⚠️ 前驱指针不一致!\n");
}
if (current->next != NULL && current->next->prev != current) {
printf(" ⚠️ 后继指针不一致!\n");
}
current = current->next;
position++;
// 防止无限循环
if (position > list->size + 5) {
printf(" ⚠️ 检测到可能的循环链表问题!\n");
break;
}
}
printf("================================\n");
}
3. 双向链表的基本操作
3.1 初始化操作
初始化是创建双向链表的第一步,需要正确设置所有指针和状态:
// 创建新节点
DoublyListNode* createNode(int data) {
DoublyListNode *newNode = (DoublyListNode*)malloc(sizeof(DoublyListNode));
if (newNode == NULL) {
printf("内存分配失败!\n");
return NULL;
}
newNode->data = data;
newNode->prev = NULL;
newNode->next = NULL;
printf("创建新节点,数据: %d, 地址: %p\n", data, (void*)newNode);
return newNode;
}
// 初始化双向链表
bool initList(DoublyLinkedList *list) {
if (list == NULL) {
printf("链表指针为NULL\n");
return false;
}
list->head = NULL;
list->tail = NULL;
list->size = 0;
printf("双向链表初始化成功\n");
return true;
}
// 检查链表是否为空
bool isEmpty(DoublyLinkedList *list) {
return list == NULL || list->head == NULL;
}
// 获取链表长度
int getSize(DoublyLinkedList *list) {
if (list == NULL) return 0;
return list->size;
}
3.2 插入操作
双向链表的插入操作比单向链表复杂,需要维护前后指针的正确性:
3.2.1 头部插入
// 在链表头部插入节点
bool insertAtHead(DoublyLinkedList *list, int data) {
if (list == NULL) {
printf("链表指针为NULL\n");
return false;
}
DoublyListNode *newNode = createNode(data);
if (newNode == NULL) {
return false;
}
if (isEmpty(list)) {
// 空链表情况
list->head = newNode;
list->tail = newNode;
} else {
// 非空链表,在头部插入
newNode->next = list->head;
list->head->prev = newNode;
list->head = newNode;
}
list->size++;
printf("在头部插入节点: %d\n", data);
return true;
}
3.2.2 尾部插入
// 在链表尾部插入节点
bool insertAtTail(DoublyLinkedList *list, int data) {
if (list == NULL) {
printf("链表指针为NULL\n");
return false;
}
DoublyListNode *newNode = createNode(data);
if (newNode == NULL) {
return false;
}
if (isEmpty(list)) {
// 空链表情况
list->head = newNode;
list->tail = newNode;
} else {
// 非空链表,在尾部插入
newNode->prev = list->tail;
list->tail->next = newNode;
list->tail = newNode;
}
list->size++;
printf("在尾部插入节点: %d\n", data);
return true;
}
3.2.3 指定位置插入
// 在指定位置插入节点
bool insertAtPosition(DoublyLinkedList *list, int position, int data) {
if (list == NULL) {
printf("链表指针为NULL\n");
return false;
}
// 位置检查
if (position < 0 || position > list->size) {
printf("插入位置不合法: %d, 链表长度: %d\n", position, list->size);
return false;
}
// 特殊位置处理
if (position == 0) {
return insertAtHead(list, data);
}
if (position == list->size) {
return insertAtTail(list, data);
}
// 创建新节点
DoublyListNode *newNode = createNode(data);
if (newNode == NULL) {
return false;
}
// 找到插入位置的前一个节点
DoublyListNode *current = list->head;
for (int i = 0; i < position - 1; i++) {
current = current->next;
}
// 插入新节点
newNode->prev = current;
newNode->next = current->next;
current->next->prev = newNode;
current->next = newNode;
list->size++;
printf("在位置 %d 插入节点: %d\n", position, data);
return true;
}
3.2.4 在指定节点前后插入
// 在指定节点前插入新节点
bool insertBeforeNode(DoublyLinkedList *list, DoublyListNode *targetNode, int data) {
if (list == NULL || targetNode == NULL) {
printf("链表或目标节点为NULL\n");
return false;
}
DoublyListNode *newNode = createNode(data);
if (newNode == NULL) {
return false;
}
// 如果目标节点是头节点
if (targetNode == list->head) {
newNode->next = list->head;
list->head->prev = newNode;
list->head = newNode;
} else {
// 普通情况
newNode->prev = targetNode->prev;
newNode->next = targetNode;
targetNode->prev->next = newNode;
targetNode->prev = newNode;
}
list->size++;
printf("在节点 %d 前插入新节点: %d\n", targetNode->data, data);
return true;
}
// 在指定节点后插入新节点
bool insertAfterNode(DoublyLinkedList *list, DoublyListNode *targetNode, int data) {
if (list == NULL || targetNode == NULL) {
printf("链表或目标节点为NULL\n");
return false;
}
DoublyListNode *newNode = createNode(data);
if (newNode == NULL) {
return false;
}
// 如果目标节点是尾节点
if (targetNode == list->tail) {
newNode->prev = list->tail;
list->tail->next = newNode;
list->tail = newNode;
} else {
// 普通情况
newNode->prev = targetNode;
newNode->next = targetNode->next;
targetNode->next->prev = newNode;
targetNode->next = newNode;
}
list->size++;
printf("在节点 %d 后插入新节点: %d\n", targetNode->data, data);
return true;
}
3.3 删除操作
双向链表的删除操作同样需要仔细维护指针关系:
3.3.1 头部删除
// 删除头部节点
bool deleteAtHead(DoublyLinkedList *list) {
if (isEmpty(list)) {
printf("链表为空,无法删除头部节点\n");
return false;
}
DoublyListNode *temp = list->head;
int deletedData = temp->data;
if (list->head == list->tail) {
// 只有一个节点的情况
list->head = NULL;
list->tail = NULL;
} else {
// 多个节点的情况
list->head = list->head->next;
list->head->prev = NULL;
}
free(temp);
list->size--;
printf("删除头部节点: %d\n", deletedData);
return true;
}
3.3.2 尾部删除
// 删除尾部节点
bool deleteAtTail(DoublyLinkedList *list) {
if (isEmpty(list)) {
printf("链表为空,无法删除尾部节点\n");
return false;
}
DoublyListNode *temp = list->tail;
int deletedData = temp->data;
if (list->head == list->tail) {
// 只有一个节点的情况
list->head = NULL;
list->tail = NULL;
} else {
// 多个节点的情况
list->tail = list->tail->prev;
list->tail->next = NULL;
}
free(temp);
list->size--;
printf("删除尾部节点: %d\n", deletedData);
return true;
}
3.3.3 指定位置删除
// 删除指定位置的节点
bool deleteAtPosition(DoublyLinkedList *list, int position) {
if (isEmpty(list)) {
printf("链表为空,无法删除节点\n");
return false;
}
// 位置检查
if (position < 0 || position >= list->size) {
printf("删除位置不合法: %d, 链表长度: %d\n", position, list->size);
return false;
}
// 特殊位置处理
if (position == 0) {
return deleteAtHead(list);
}
if (position == list->size - 1) {
return deleteAtTail(list);
}
// 找到要删除的节点
DoublyListNode *current = list->head;
for (int i = 0; i < position; i++) {
current = current->next;
}
// 删除节点
current->prev->next = current->next;
current->next->prev = current->prev;
int deletedData = current->data;
free(current);
list->size--;
printf("删除位置 %d 的节点: %d\n", position, deletedData);
return true;
}
3.3.4 删除指定节点
// 删除指定节点
bool deleteNode(DoublyLinkedList *list, DoublyListNode *targetNode) {
if (isEmpty(list) || targetNode == NULL) {
printf("链表为空或目标节点为NULL\n");
return false;
}
// 检查节点是否在链表中
bool nodeFound = false;
DoublyListNode *current = list->head;
while (current != NULL) {
if (current == targetNode) {
nodeFound = true;
break;
}
current = current->next;
}
if (!nodeFound) {
printf("目标节点不在链表中\n");
return false;
}
// 处理边界情况
if (targetNode == list->head) {
return deleteAtHead(list);
}
if (targetNode == list->tail) {
return deleteAtTail(list);
}
// 普通情况
targetNode->prev->next = targetNode->next;
targetNode->next->prev = targetNode->prev;
int deletedData = targetNode->data;
free(targetNode);
list->size--;
printf("删除指定节点: %d\n", deletedData);
return true;
}
3.3.5 按值删除节点
// 删除第一个匹配值的节点
bool deleteByValue(DoublyLinkedList *list, int value) {
if (isEmpty(list)) {
printf("链表为空,无法删除节点\n");
return false;
}
DoublyListNode *current = list->head;
while (current != NULL) {
if (current->data == value) {
return deleteNode(list, current);
}
current = current->next;
}
printf("未找到值为 %d 的节点\n", value);
return false;
}
// 删除所有匹配值的节点
bool deleteAllByValue(DoublyLinkedList *list, int value) {
if (isEmpty(list)) {
printf("链表为空,无法删除节点\n");
return false;
}
int deleteCount = 0;
DoublyListNode *current = list->head;
DoublyListNode *nextNode = NULL;
while (current != NULL) {
nextNode = current->next;
if (current->data == value) {
deleteNode(list, current);
deleteCount++;
}
current = nextNode;
}
if (deleteCount > 0) {
printf("删除了 %d 个值为 %d 的节点\n", deleteCount, value);
return true;
} else {
printf("未找到值为 %d 的节点\n", value);
return false;
}
}
3.4 查找操作
双向链表支持双向查找,提供了灵活的查找方式:
3.4.1 按位置查找
// 按位置查找节点(从头开始)
DoublyListNode* getNodeAtPosition(DoublyLinkedList *list, int position) {
if (isEmpty(list)) {
printf("链表为空\n");
return NULL;
}
if (position < 0 || position >= list->size) {
printf("位置不合法: %d, 链表长度: %d\n", position, list->size);
return NULL;
}
// 根据位置选择查找方向(优化查找效率)
DoublyListNode *current = NULL;
if (position < list->size / 2) {
// 从前向后查找
current = list->head;
for (int i = 0; i < position; i++) {
current = current->next;
}
} else {
// 从后向前查找
current = list->tail;
for (int i = list->size - 1; i > position; i--) {
current = current->prev;
}
}
printf("位置 %d 的节点数据: %d\n", position, current->data);
return current;
}
3.4.2 按值查找
// 按值查找节点(从头开始)
DoublyListNode* findNodeByValue(DoublyLinkedList *list, int value) {
if (isEmpty(list)) {
printf("链表为空\n");
return NULL;
}
DoublyListNode *current = list->head;
int position = 0;
while (current != NULL) {
if (current->data == value) {
printf("找到值为 %d 的节点,位置: %d\n", value, position);
return current;
}
current = current->next;
position++;
}
printf("未找到值为 %d 的节点\n", value);
return NULL;
}
// 按值查找节点(从尾开始)
DoublyListNode* findNodeByValueReverse(DoublyLinkedList *list, int value) {
if (isEmpty(list)) {
printf("链表为空\n");
return NULL;
}
DoublyListNode *current = list->tail;
int position = list->size - 1;
while (current != NULL) {
if (current->data == value) {
printf("找到值为 %d 的节点,位置: %d\n", value, position);
return current;
}
current = current->prev;
position--;
}
printf("未找到值为 %d 的节点\n", value);
return NULL;
}
3.4.3 查找所有匹配值的节点
// 查找所有匹配值的节点位置
void findAllByValue(DoublyLinkedList *list, int value) {
if (isEmpty(list)) {
printf("链表为空\n");
return;
}
DoublyListNode *current = list->head;
int position = 0;
int foundCount = 0;
printf("值为 %d 的节点位置: ", value);
while (current != NULL) {
if (current->data == value) {
printf("%d ", position);
foundCount++;
}
current = current->next;
position++;
}
if (foundCount == 0) {
printf("无");
}
printf("\n共找到 %d 个节点\n", foundCount);
}
3.5 遍历操作
双向链表支持两种方向的遍历:
3.5.1 正向遍历
// 正向遍历链表(从头到尾)
void traverseForward(DoublyLinkedList *list) {
if (isEmpty(list)) {
printf("链表为空\n");
return;
}
printf("正向遍历: ");
DoublyListNode *current = list->head;
while (current != NULL) {
printf("%d", current->data);
if (current->next != NULL) {
printf(" <-> ");
}
current = current->next;
}
printf(" -> NULL\n");
}
// 带详细信息的正向遍历
void traverseForwardDetailed(DoublyLinkedList *list) {
if (isEmpty(list)) {
printf("链表为空\n");
return;
}
printf("\n=== 正向遍历详细信息 ===\n");
DoublyListNode *current = list->head;
int position = 0;
while (current != NULL) {
printf("位置 %d: 数据=%d", position, current->data);
printf(", 前驱=%s", current->prev ? "有" : "NULL");
printf(", 后继=%s", current->next ? "有" : "NULL");
printf("\n");
current = current->next;
position++;
}
printf("========================\n");
}
3.5.2 反向遍历
// 反向遍历链表(从尾到头)
void traverseBackward(DoublyLinkedList *list) {
if (isEmpty(list)) {
printf("链表为空\n");
return;
}
printf("反向遍历: ");
DoublyListNode *current = list->tail;
while (current != NULL) {
printf("%d", current->data);
if (current->prev != NULL) {
printf(" <-> ");
}
current = current->prev;
}
printf(" -> NULL\n");
}
// 带详细信息的反向遍历
void traverseBackwardDetailed(DoublyLinkedList *list) {
if (isEmpty(list)) {
printf("链表为空\n");
return;
}
printf("\n=== 反向遍历详细信息 ===\n");
DoublyListNode *current = list->tail;
int position = list->size - 1;
while (current != NULL) {
printf("位置 %d: 数据=%d", position, current->data);
printf(", 前驱=%s", current->prev ? "有" : "NULL");
printf(", 后继=%s", current->next ? "有" : "NULL");
printf("\n");
current = current->prev;
position--;
}
printf("========================\n");
}
3.5.3 双向遍历验证
// 验证双向链表的完整性
bool verifyListIntegrity(DoublyLinkedList *list) {
if (list == NULL) {
printf("链表指针为NULL\n");
return false;
}
if (isEmpty(list)) {
printf("空链表,完整性验证通过\n");
return true;
}
printf("\n=== 双向链表完整性验证 ===\n");
// 正向验证
DoublyListNode *current = list->head;
int count = 0;
bool integrityOK = true;
while (current != NULL) {
count++;
// 检查头节点
if (current == list->head && current->prev != NULL) {
printf("❌ 头节点的前驱指针不为NULL\n");
integrityOK = false;
}
// 检查尾节点
if (current == list->tail && current->next != NULL) {
printf("❌ 尾节点的后继指针不为NULL\n");
integrityOK = false;
}
// 检查前驱指针一致性
if (current->prev != NULL && current->prev->next != current) {
printf("❌ 位置 %d: 前驱指针不一致\n", count - 1);
integrityOK = false;
}
// 检查后继指针一致性
if (current->next != NULL && current->next->prev != current) {
printf("❌ 位置 %d: 后继指针不一致\n", count - 1);
integrityOK = false;
}
current = current->next;
// 防止无限循环
if (count > list->size + 5) {
printf("❌ 检测到可能的循环链表\n");
integrityOK = false;
break;
}
}
// 检查长度一致性
if (count != list->size) {
printf("❌ 链表长度不一致: 实际=%d, 记录=%d\n", count, list->size);
integrityOK = false;
}
if (integrityOK) {
printf("✅ 双向链表完整性验证通过\n");
} else {
printf("❌ 双向链表完整性验证失败\n");
}
return integrityOK;
}
4. 高级操作与算法
4.1 链表反转
双向链表的反转比单向链表更复杂,需要同时调整prev和next指针:
// 反转双向链表
bool reverseList(DoublyLinkedList *list) {
if (isEmpty(list)) {
printf("链表为空,无法反转\n");
return false;
}
if (list->size == 1) {
printf("链表只有一个节点,无需反转\n");
return true;
}
printf("开始反转双向链表...\n");
DoublyListNode *current = list->head;
DoublyListNode *temp = NULL;
// 交换头尾指针
list->head = list->tail;
list->tail = current;
// 遍历链表,交换每个节点的prev和next指针
while (current != NULL) {
// 交换当前节点的prev和next指针
temp = current->prev;
current->prev = current->next;
current->next = temp;
// 移动到原链表中的前一个节点(因为指针已经交换)
current = current->prev;
}
printf("双向链表反转完成\n");
return true;
}
// 反转链表的指定区间
bool reverseListRange(DoublyLinkedList *list, int start, int end) {
if (isEmpty(list)) {
printf("链表为空,无法反转\n");
return false;
}
if (start < 0 || end >= list->size || start >= end) {
printf("反转区间不合法: [%d, %d]\n", start, end);
return false;
}
printf("反转区间 [%d, %d] 的链表...\n", start, end);
// 找到区间开始的前一个节点和结束的后一个节点
DoublyListNode *beforeStart = (start == 0) ? NULL : getNodeAtPosition(list, start - 1);
DoublyListNode *afterEnd = (end == list->size - 1) ? NULL : getNodeAtPosition(list, end + 1);
// 找到区间内的节点
DoublyListNode *startNode = (start == 0) ? list->head : beforeStart->next;
DoublyListNode *endNode = getNodeAtPosition(list, end);
// 反转区间内的链表
DoublyListNode *current = startNode;
DoublyListNode *prev = beforeStart;
DoublyListNode *next = NULL;
while (current != afterEnd) {
next = current->next;
current->next = prev;
current->prev = next;
prev = current;
current = next;
}
// 连接反转后的区间与前后部分
if (beforeStart != NULL) {
beforeStart->next = endNode;
} else {
list->head = endNode;
}
if (afterEnd != NULL) {
afterEnd->prev = startNode;
} else {
list->tail = startNode;
}
startNode->next = afterEnd;
endNode->prev = beforeStart;
printf("区间反转完成\n");
return true;
}
4.2 链表排序
双向链表支持多种排序算法,这里实现插入排序和归并排序:
4.2.1 插入排序
// 对双向链表进行插入排序
bool insertionSort(DoublyLinkedList *list) {
if (isEmpty(list) || list->size == 1) {
printf("链表无需排序\n");
return true;
}
printf("开始插入排序...\n");
DoublyListNode *sorted = NULL; // 已排序部分的头节点
DoublyListNode *current = list->head;
while (current != NULL) {
DoublyListNode *next = current->next; // 保存下一个节点
// 从已排序链表中移除当前节点
if (current->prev != NULL) {
current->prev->next = current->next;
}
if (current->next != NULL) {
current->next->prev = current->prev;
}
// 将当前节点插入到已排序链表的正确位置
if (sorted == NULL) {
// 第一个节点
sorted = current;
current->prev = NULL;
current->next = NULL;
} else if (current->data <= sorted->data) {
// 插入到已排序链表的头部
current->next = sorted;
current->prev = NULL;
sorted->prev = current;
sorted = current;
} else {
// 在已排序链表中找到插入位置
DoublyListNode *temp = sorted;
while (temp->next != NULL && temp->next->data < current->data) {
temp = temp->next;
}
// 插入节点
current->next = temp->next;
current->prev = temp;
if (temp->next != NULL) {
temp->next->prev = current;
}
temp->next = current;
}
current = next;
}
// 更新链表的头尾指针
list->head = sorted;
// 找到新的尾节点
DoublyListNode *tail = sorted;
while (tail->next != NULL) {
tail = tail->next;
}
list->tail = tail;
printf("插入排序完成\n");
return true;
}
4.2.2 归并排序
// 分割双向链表(用于归并排序)
DoublyListNode* splitList(DoublyListNode *head) {
if (head == NULL || head->next == NULL) {
return NULL;
}
// 使用快慢指针找到中间节点
DoublyListNode *slow = head;
DoublyListNode *fast = head->next;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
DoublyListNode *mid = slow->next;
slow->next = NULL;
if (mid != NULL) {
mid->prev = NULL;
}
return mid;
}
// 合并两个有序双向链表
DoublyListNode* mergeSortedLists(DoublyListNode *left, DoublyListNode *right) {
if (left == NULL) return right;
if (right == NULL) return left;
DoublyListNode *result = NULL;
DoublyListNode **tail = &result;
DoublyListNode *last = NULL;
while (left != NULL && right != NULL) {
DoublyListNode *temp = NULL;
if (left->data <= right->data) {
temp = left;
left = left->next;
} else {
temp = right;
right = right->next;
}
temp->prev = last;
*tail = temp;
last = temp;
tail = &(temp->next);
}
// 连接剩余部分
if (left != NULL) {
*tail = left;
left->prev = last;
} else {
*tail = right;
if (right != NULL) {
right->prev = last;
}
}
return result;
}
// 对双向链表进行归并排序
DoublyListNode* mergeSort(DoublyListNode *head) {
if (head == NULL || head->next == NULL) {
return head;
}
// 分割链表
DoublyListNode *mid = splitList(head);
// 递归排序
head = mergeSort(head);
mid = mergeSort(mid);
// 合并排序后的链表
return mergeSortedLists(head, mid);
}
// 对双向链表进行归并排序(外部接口)
bool mergeSortList(DoublyLinkedList *list) {
if (isEmpty(list) || list->size == 1) {
printf("链表无需排序\n");
return true;
}
printf("开始归并排序...\n");
// 进行归并排序
list->head = mergeSort(list->head);
// 更新尾指针
DoublyListNode *tail = list->head;
while (tail != NULL && tail->next != NULL) {
tail = tail->next;
}
list->tail = tail;
printf("归并排序完成\n");
return true;
}
4.3 去重操作
// 删除链表中的重复元素(已排序链表)
bool removeDuplicatesFromSorted(DoublyLinkedList *list) {
if (isEmpty(list)) {
printf("链表为空\n");
return false;
}
printf("开始删除已排序链表中的重复元素...\n");
DoublyListNode *current = list->head;
int removedCount = 0;
while (current != NULL && current->next != NULL) {
if (current->data == current->next->data) {
// 删除重复节点
DoublyListNode *duplicate = current->next;
current->next = duplicate->next;
if (duplicate->next != NULL) {
duplicate->next->prev = current;
} else {
// 如果删除的是尾节点,更新尾指针
list->tail = current;
}
free(duplicate);
list->size--;
removedCount++;
} else {
current = current->next;
}
}
printf("删除了 %d 个重复元素\n", removedCount);
return true;
}
// 删除链表中的重复元素(未排序链表)
bool removeDuplicatesFromUnsorted(DoublyLinkedList *list) {
if (isEmpty(list)) {
printf("链表为空\n");
return false;
}
printf("开始删除未排序链表中的重复元素...\n");
DoublyListNode *current = list->head;
int removedCount = 0;
while (current != NULL) {
DoublyListNode *runner = current->next;
DoublyListNode *prevRunner = current;
while (runner != NULL) {
if (runner->data == current->data) {
// 删除重复节点
prevRunner->next = runner->next;
if (runner->next != NULL) {
runner->next->prev = prevRunner;
} else {
// 如果删除的是尾节点,更新尾指针
list->tail = prevRunner;
}
DoublyListNode *toDelete = runner;
runner = runner->next;
free(toDelete);
list->size--;
removedCount++;
} else {
prevRunner = runner;
runner = runner->next;
}
}
current = current->next;
}
printf("删除了 %d 个重复元素\n", removedCount);
return true;
}
5. 循环双向链表
5.1 循环双向链表的概念
循环双向链表是双向链表的变种,其尾节点的next指针指向头节点,头节点的prev指针指向尾节点,形成一个循环。
5.2 循环双向链表的实现
// 循环双向链表结构定义
typedef struct {
DoublyListNode *head; // 头指针
DoublyListNode *tail; // 尾指针
int size; // 链表长度
} CircularDoublyLinkedList;
// 初始化循环双向链表
bool initCircularList(CircularDoublyLinkedList *list) {
if (list == NULL) {
return false;
}
list->head = NULL;
list->tail = NULL;
list->size = 0;
printf("循环双向链表初始化成功\n");
return true;
}
// 检查循环双向链表是否为空
bool isCircularListEmpty(CircularDoublyLinkedList *list) {
return list == NULL || list->head == NULL;
}
// 在循环双向链表头部插入节点
bool insertAtHeadCircular(CircularDoublyLinkedList *list, int data) {
if (list == NULL) {
printf("链表指针为NULL\n");
return false;
}
DoublyListNode *newNode = createNode(data);
if (newNode == NULL) {
return false;
}
if (isCircularListEmpty(list)) {
// 空链表情况
newNode->next = newNode;
newNode->prev = newNode;
list->head = newNode;
list->tail = newNode;
} else {
// 非空链表,在头部插入
newNode->next = list->head;
newNode->prev = list->tail;
list->head->prev = newNode;
list->tail->next = newNode;
list->head = newNode;
}
list->size++;
printf("在循环链表头部插入节点: %d\n", data);
return true;
}
// 在循环双向链表尾部插入节点
bool insertAtTailCircular(CircularDoublyLinkedList *list, int data) {
if (list == NULL) {
printf("链表指针为NULL\n");
return false;
}
DoublyListNode *newNode = createNode(data);
if (newNode == NULL) {
return false;
}
if (isCircularListEmpty(list)) {
// 空链表情况
newNode->next = newNode;
newNode->prev = newNode;
list->head = newNode;
list->tail = newNode;
} else {
// 非空链表,在尾部插入
newNode->next = list->head;
newNode->prev = list->tail;
list->tail->next = newNode;
list->head->prev = newNode;
list->tail = newNode;
}
list->size++;
printf("在循环链表尾部插入节点: %d\n", data);
return true;
}
// 遍历循环双向链表
void traverseCircularList(CircularDoublyLinkedList *list) {
if (isCircularListEmpty(list)) {
printf("循环链表为空\n");
return;
}
printf("循环链表遍历: ");
DoublyListNode *current = list->head;
int count = 0;
do {
printf("%d", current->data);
if (count < list->size - 1) {
printf(" <-> ");
}
current = current->next;
count++;
} while (current != list->head && count < list->size + 5); // 防止无限循环
printf(" -> (回到头节点)\n");
}
// 验证循环双向链表的完整性
bool verifyCircularListIntegrity(CircularDoublyLinkedList *list) {
if (list == NULL) {
printf("链表指针为NULL\n");
return false;
}
if (isCircularListEmpty(list)) {
printf("空循环链表,完整性验证通过\n");
return true;
}
printf("\n=== 循环双向链表完整性验证 ===\n");
bool integrityOK = true;
DoublyListNode *current = list->head;
int count = 0;
do {
count++;
// 检查前驱指针
if (current->prev == NULL) {
printf("❌ 节点 %d 的前驱指针为NULL\n", count - 1);
integrityOK = false;
}
// 检查后继指针
if (current->next == NULL) {
printf("❌ 节点 %d 的后继指针为NULL\n", count - 1);
integrityOK = false;
}
// 检查循环完整性
if (current->next->prev != current) {
printf("❌ 节点 %d 的后继节点的前驱指针不一致\n", count - 1);
integrityOK = false;
}
if (current->prev->next != current) {
printf("❌ 节点 %d 的前驱节点的后继指针不一致\n", count - 1);
integrityOK = false;
}
current = current->next;
} while (current != list->head && count <= list->size);
// 检查长度一致性
if (count != list->size) {
printf("❌ 链表长度不一致: 实际遍历=%d, 记录=%d\n", count, list->size);
integrityOK = false;
}
// 检查头尾指针正确性
if (list->head->prev != list->tail) {
printf("❌ 头节点的前驱不是尾节点\n");
integrityOK = false;
}
if (list->tail->next != list->head) {
printf("❌ 尾节点的后继不是头节点\n");
integrityOK = false;
}
if (integrityOK) {
printf("✅ 循环双向链表完整性验证通过\n");
} else {
printf("❌ 循环双向链表完整性验证失败\n");
}
return integrityOK;
}
6. 实际应用案例
6.1 LRU缓存实现
LRU(Least Recently Used)缓存淘汰算法可以使用双向链表高效实现:
// LRU缓存节点
typedef struct LRUNode {
int key;
int value;
struct LRUNode *prev;
struct LRUNode *next;
} LRUNode;
// LRU缓存结构
typedef struct {
LRUNode *head; // 最近使用的节点
LRUNode *tail; // 最久未使用的节点
int capacity; // 缓存容量
int size; // 当前大小
} LRUCache;
// 创建LRU缓存
LRUCache* createLRUCache(int capacity) {
LRUCache *cache = (LRUCache*)malloc(sizeof(LRUCache));
cache->head = NULL;
cache->tail = NULL;
cache->capacity = capacity;
cache->size = 0;
printf("创建LRU缓存,容量: %d\n", capacity);
return cache;
}
// 将节点移到头部(表示最近使用)
void moveToHead(LRUCache *cache, LRUNode *node) {
if (node == cache->head) {
return; // 已经在头部
}
// 从当前位置移除节点
if (node->prev != NULL) {
node->prev->next = node->next;
}
if (node->next != NULL) {
node->next->prev = node->prev;
}
// 如果移动的是尾节点,更新尾指针
if (node == cache->tail) {
cache->tail = node->prev;
}
// 将节点插入头部
node->next = cache->head;
node->prev = NULL;
if (cache->head != NULL) {
cache->head->prev = node;
}
cache->head = node;
// 如果缓存为空,同时更新尾指针
if (cache->tail == NULL) {
cache->tail = node;
}
}
// 从LRU缓存获取数据
int getFromLRU(LRUCache *cache, int key) {
if (cache == NULL || cache->head == NULL) {
printf("缓存为空\n");
return -1;
}
// 查找键对应的节点
LRUNode *current = cache->head;
while (current != NULL) {
if (current->key == key) {
// 找到节点,移到头部表示最近使用
moveToHead(cache, current);
printf("获取键 %d 的值: %d\n", key, current->value);
return current->value;
}
current = current->next;
}
printf("键 %d 不存在于缓存中\n", key);
return -1;
}
// 向LRU缓存插入数据
void putToLRU(LRUCache *cache, int key, int value) {
if (cache == NULL) {
return;
}
// 检查键是否已存在
LRUNode *current = cache->head;
while (current != NULL) {
if (current->key == key) {
// 更新已存在节点的值并移到头部
current->value = value;
moveToHead(cache, current);
printf("更新键 %d 的值为: %d\n", key, value);
return;
}
current = current->next;
}
// 创建新节点
LRUNode *newNode = (LRUNode*)malloc(sizeof(LRUNode));
newNode->key = key;
newNode->value = value;
newNode->prev = NULL;
newNode->next = NULL;
// 如果缓存已满,移除最久未使用的节点
if (cache->size >= cache->capacity) {
LRUNode *oldTail = cache->tail;
if (oldTail != NULL) {
cache->tail = oldTail->prev;
if (cache->tail != NULL) {
cache->tail->next = NULL;
}
printf("移除最久未使用的键: %d\n", oldTail->key);
free(oldTail);
cache->size--;
}
}
// 插入新节点到头部
newNode->next = cache->head;
if (cache->head != NULL) {
cache->head->prev = newNode;
}
cache->head = newNode;
// 如果缓存为空,同时更新尾指针
if (cache->tail == NULL) {
cache->tail = newNode;
}
cache->size++;
printf("插入新键值对: %d -> %d\n", key, value);
}
// 打印LRU缓存状态
void printLRUCache(LRUCache *cache) {
if (cache == NULL) {
printf("缓存为NULL\n");
return;
}
printf("LRU缓存状态[大小=%d/%d]: ", cache->size, cache->capacity);
LRUNode *current = cache->head;
while (current != NULL) {
printf("(%d:%d)", current->key, current->value);
if (current->next != NULL) {
printf(" -> ");
}
current = current->next;
}
printf("\n");
}
6.2 浏览器历史记录
双向链表非常适合实现浏览器的前进后退功能:
// 浏览器历史记录节点
typedef struct HistoryNode {
char *url; // 网页URL
char *title; // 网页标题
struct HistoryNode *prev; // 前一个历史记录
struct HistoryNode *next; // 后一个历史记录
} HistoryNode;
// 浏览器历史记录管理器
typedef struct {
HistoryNode *current; // 当前访问的页面
int size; // 历史记录数量
int maxSize; // 最大历史记录数量
} BrowserHistory;
// 创建浏览器历史记录管理器
BrowserHistory* createBrowserHistory(int maxSize) {
BrowserHistory *history = (BrowserHistory*)malloc(sizeof(BrowserHistory));
history->current = NULL;
history->size = 0;
history->maxSize = maxSize;
printf("创建浏览器历史记录,最大容量: %d\n", maxSize);
return history;
}
// 访问新网页
void visitPage(BrowserHistory *history, const char *url, const char *title) {
if (history == NULL || url == NULL) {
return;
}
// 创建新历史记录节点
HistoryNode *newNode = (HistoryNode*)malloc(sizeof(HistoryNode));
newNode->url = strdup(url);
newNode->title = strdup(title);
newNode->prev = history->current;
newNode->next = NULL;
// 如果当前有页面,断开后续历史记录
if (history->current != NULL) {
// 清除当前页面之后的所有历史记录
HistoryNode *temp = history->current->next;
while (temp != NULL) {
HistoryNode *toDelete = temp;
temp = temp->next;
free(toDelete->url);
free(toDelete->title);
free(toDelete);
history->size--;
}
history->current->next = newNode;
}
history->current = newNode;
history->size++;
// 如果超过最大容量,删除最旧的历史记录
while (history->size > history->maxSize) {
HistoryNode *oldest = history->current;
while (oldest->prev != NULL) {
oldest = oldest->prev;
}
if (oldest->next != NULL) {
oldest->next->prev = NULL;
}
HistoryNode *toDelete = oldest;
oldest = oldest->next;
free(toDelete->url);
free(toDelete->title);
free(toDelete);
history->size--;
}
printf("访问新页面: %s (%s)\n", title, url);
}
// 后退到前一个页面
HistoryNode* goBack(BrowserHistory *history) {
if (history == NULL || history->current == NULL || history->current->prev == NULL) {
printf("无法后退\n");
return NULL;
}
history->current = history->current->prev;
printf("后退到: %s (%s)\n", history->current->title, history->current->url);
return history->current;
}
// 前进到后一个页面
HistoryNode* goForward(BrowserHistory *history) {
if (history == NULL || history->current == NULL || history->current->next == NULL) {
printf("无法前进\n");
return NULL;
}
history->current = history->current->next;
printf("前进到: %s (%s)\n", history->current->title, history->current->url);
return history->current;
}
// 打印浏览历史
void printBrowserHistory(BrowserHistory *history) {
if (history == NULL) {
printf("历史记录为NULL\n");
return;
}
printf("浏览历史[大小=%d/%d]:\n", history->size, history->maxSize);
// 找到最旧的历史记录
HistoryNode *oldest = history->current;
while (oldest != NULL && oldest->prev != NULL) {
oldest = oldest->prev;
}
// 打印所有历史记录
HistoryNode *current = oldest;
int position = 0;
while (current != NULL) {
if (current == history->current) {
printf("-> [%d] %s (%s) <-\n", position, current->title, current->url);
} else {
printf(" [%d] %s (%s)\n", position, current->title, current->url);
}
current = current->next;
position++;
}
}
7. 性能分析与优化
7.1 时间复杂度分析
| 操作 | 双向链表 | 单向链表 | 数组 |
|---|---|---|---|
| 访问头部 | O(1) | O(1) | O(1) |
| 访问尾部 | O(1) | O(n) | O(1) |
| 随机访问 | O(n) | O(n) | O(1) |
| 头部插入 | O(1) | O(1) | O(n) |
| 尾部插入 | O(1) | O(n) | O(1) |
| 随机插入 | O(n) | O(n) | O(n) |
| 头部删除 | O(1) | O(1) | O(n) |
| 尾部删除 | O(1) | O(n) | O(1) |
| 随机删除 | O(n) | O(n) | O(n) |
| 查找 | O(n) | O(n) | O(n) |
7.2 空间复杂度分析
- 双向链表:每个节点需要2个指针 + 数据,空间复杂度O(n)
- 单向链表:每个节点需要1个指针 + 数据,空间复杂度O(n)
- 数组:只需要数据空间,空间复杂度O(n)
内存开销对比:
假设指针大小为8字节,数据为4字节:
- 双向链表:每个节点20字节(8+8+4)
- 单向链表:每个节点12字节(8+4)
- 数组:每个元素4字节
7.3 缓存性能分析
// 缓存性能测试
void cachePerformanceTest() {
printf("\n=== 缓存性能分析 ===\n");
const int TEST_SIZE = 10000;
// 创建测试数据
DoublyLinkedList *list = (DoublyLinkedList*)malloc(sizeof(DoublyLinkedList));
initList(list);
// 顺序插入测试
clock_t start = clock();
for (int i = 0; i < TEST_SIZE; i++) {
insertAtTail(list, i);
}
clock_t end = clock();
double sequentialInsertTime = ((double)(end - start)) / CLOCKS_PER_SEC;
// 随机访问测试
start = clock();
for (int i = 0; i < TEST_SIZE; i++) {
getNodeAtPosition(list, rand() % TEST_SIZE);
}
end = clock();
double randomAccessTime = ((double)(end - start)) / CLOCKS_PER_SEC;
// 遍历测试
start = clock();
DoublyListNode *current = list->head;
while (current != NULL) {
int data = current->data; // 模拟访问操作
current = current->next;
}
end = clock();
double traverseTime = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("测试结果 (n=%d):\n", TEST_SIZE);
printf("顺序插入时间: %.6f 秒\n", sequentialInsertTime);
printf("随机访问时间: %.6f 秒\n", randomAccessTime);
printf("遍历时间: %.6f 秒\n", traverseTime);
// 清理内存
// 实际应用中需要释放链表所有节点
}
7.4 优化技巧
7.4.1 内存池优化
// 简单的内存池实现
#define MEMORY_POOL_SIZE 1000
typedef struct MemoryPool {
DoublyListNode nodes[MEMORY_POOL_SIZE];
int used;
struct MemoryPool *next;
} MemoryPool;
MemoryPool* createMemoryPool() {
MemoryPool *pool = (MemoryPool*)malloc(sizeof(MemoryPool));
pool->used = 0;
pool->next = NULL;
return pool;
}
DoublyListNode* allocateNodeFromPool(MemoryPool *pool, int data) {
if (pool->used >= MEMORY_POOL_SIZE) {
printf("内存池已满,创建新的内存池\n");
return NULL; // 简化实现,实际应该创建新池
}
DoublyListNode *node = &pool->nodes[pool->used];
node->data = data;
node->prev = NULL;
node->next = NULL;
pool->used++;
return node;
}
7.4.2 批量操作优化
// 批量插入优化
bool batchInsert(DoublyLinkedList *list, int data[], int count) {
if (list == NULL || data == NULL || count <= 0) {
return false;
}
printf("批量插入 %d 个元素\n", count);
// 批量创建节点
DoublyListNode **nodes = (DoublyListNode**)malloc(count * sizeof(DoublyListNode*));
for (int i = 0; i < count; i++) {
nodes[i] = createNode(data[i]);
if (nodes[i] == NULL) {
// 清理已分配的节点
for (int j = 0; j < i; j++) {
free(nodes[j]);
}
free(nodes);
return false;
}
}
// 批量连接节点
for (int i = 0; i < count - 1; i++) {
nodes[i]->next = nodes[i + 1];
nodes[i + 1]->prev = nodes[i];
}
// 连接到链表
if (isEmpty(list)) {
list->head = nodes[0];
list->tail = nodes[count - 1];
} else {
list->tail->next = nodes[0];
nodes[0]->prev = list->tail;
list->tail = nodes[count - 1];
}
list->size += count;
free(nodes);
return true;
}
8. 测试与验证
8.1 完整测试程序
// 完整的双向链表测试程序
void comprehensiveTest() {
printf("=== 双向链表全面测试 ===\n\n");
// 1. 初始化测试
DoublyLinkedList list;
initList(&list);
printf("1. 初始化测试完成\n");
// 2. 插入操作测试
printf("\n2. 插入操作测试:\n");
insertAtHead(&list, 10);
insertAtTail(&list, 30);
insertAtPosition(&list, 1, 20);
insertAtHead(&list, 5);
insertAtTail(&list, 40);
traverseForward(&list);
// 3. 查找操作测试
printf("\n3. 查找操作测试:\n");
DoublyListNode *node20 = findNodeByValue(&list, 20);
DoublyListNode *node30 = getNodeAtPosition(&list, 3);
printf("查找到的值20: %s\n", node20 ? "成功" : "失败");
printf("位置3的节点: %s\n", node30 ? "成功" : "失败");
// 4. 删除操作测试
printf("\n4. 删除操作测试:\n");
deleteAtHead(&list);
deleteAtTail(&list);
deleteAtPosition(&list, 1);
traverseForward(&list);
// 5. 边界情况测试
printf("\n5. 边界情况测试:\n");
// 空链表操作
DoublyLinkedList emptyList;
initList(&emptyList);
deleteAtHead(&emptyList); // 应该失败
findNodeByValue(&emptyList, 10); // 应该失败
// 单节点链表
insertAtHead(&emptyList, 100);
deleteAtHead(&emptyList);
deleteAtHead(&emptyList); // 应该失败
// 6. 性能测试
printf("\n6. 性能测试:\n");
DoublyLinkedList perfList;
initList(&perfList);
const int PERF_SIZE = 1000;
clock_t start = clock();
for (int i = 0; i < PERF_SIZE; i++) {
insertAtTail(&perfList, i);
}
clock_t end = clock();
double insertTime = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("插入 %d 个元素耗时: %.6f 秒\n", PERF_SIZE, insertTime);
// 7. 完整性验证
printf("\n7. 完整性验证:\n");
verifyListIntegrity(&list);
verifyListIntegrity(&perfList);
printf("\n=== 测试完成 ===\n");
}
// 内存泄漏检查
void memoryLeakCheck() {
printf("\n=== 内存泄漏检查 ===\n");
// 在Valgrind或类似工具下运行此函数检查内存泄漏
DoublyLinkedList *testList = (DoublyLinkedList*)malloc(sizeof(DoublyLinkedList));
initList(testList);
// 创建一些节点
for (int i = 0; i < 10; i++) {
insertAtTail(testList, i * 10);
}
// 删除一些节点
for (int i = 0; i < 5; i++) {
deleteAtHead(testList);
}
// 应该手动释放剩余节点,但这里故意不释放以测试内存泄漏
// 实际使用时应该调用完整的销毁函数
printf("内存泄漏检查完成(请使用Valgrind等工具验证)\n");
}
8.2 错误处理测试
// 错误处理测试
void errorHandlingTest() {
printf("\n=== 错误处理测试 ===\n");
DoublyLinkedList list;
initList(&list);
// 测试非法位置插入
printf("1. 测试非法位置插入:\n");
insertAtPosition(&list, -1, 100); // 应该失败
insertAtPosition(&list, 5, 100); // 应该失败
// 测试非法位置删除
printf("\n2. 测试非法位置删除:\n");
deleteAtPosition(&list, -1); // 应该失败
deleteAtPosition(&list, 5); // 应该失败
// 测试空链表操作
printf("\n3. 测试空链表操作:\n");
deleteAtHead(&list); // 应该失败
deleteAtTail(&list); // 应该失败
getNodeAtPosition(&list, 0); // 应该失败
// 测试NULL指针
printf("\n4. 测试NULL指针:\n");
insertAtHead(NULL, 100); // 应该失败
traverseForward(NULL); // 应该失败
printf("\n错误处理测试完成\n");
}
9. 总结
9.1 双向链表的优势总结
- 双向遍历能力:支持从前向后和从后向前的遍历
- 高效删除操作:在已知节点位置时,删除操作时间复杂度为O(1)
- 灵活插入:可以在任意节点的前后快速插入新节点
- 实现复杂数据结构的基础:是实现双向队列、LRU缓存等复杂数据结构的基础
9.2 适用场景建议
推荐使用双向链表的场景:
- 需要频繁双向遍历的数据集合
- 需要快速在任意位置插入删除的场景
- 实现撤销重做、浏览历史等功能
- 缓存淘汰算法实现
不推荐使用双向链表的场景:
- 只需要单向遍历的简单场景
- 对内存使用有严格限制的环境
- 需要频繁随机访问的场景
9.3 最佳实践建议
- 始终维护指针一致性:在每次插入删除操作后都要确保prev和next指针的正确性
- 边界情况处理:特别注意头节点和尾节点的特殊处理
- 内存管理:及时释放不再使用的节点,避免内存泄漏
- 错误检查:对所有输入参数进行有效性检查
- 性能优化:对于大型链表,考虑使用内存池等优化技术
9.4 进一步学习方向
- 学习更复杂的链表变种:跳表、异或链表等
- 研究并发安全链表:支持多线程环境的安全操作
- 探索持久化链表:支持版本控制的链表结构
- 了解内存优化技术:如指针压缩、内存池等
双向链表作为基础数据结构的重要组成部分,深入理解其原理和实现对于提高编程能力和解决复杂问题具有重要意义。通过本文的详细讲解和丰富代码示例,相信读者已经对双向链表有了全面而深入的理解。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)