文章目录
1. 定义
双链表(Doubly Linked List)是一种链表数据结构,其中每个节点不仅包含一个数据域,还包含两个指针域,一个指向前驱节点,一个指向后继节点。相比单链表,双链表可以更方便地进行双向遍历,插入和删除操作也更高效。
双链表的定义和操作
- 节点的定义:每个节点包含数据部分和指向前一个节点和后一个节点的指针部分。
- 头节点和尾节点:链表的头节点是链表的起点,尾节点是链表的终点,通过头节点可以遍历整个链表。
- 双向链接:每个节点都有指向前一个节点和后一个节点的指针,因此可以双向遍历链表。
- 动态扩展:节点在内存中不必是连续的,可以动态分配和释放内存。
2. 双链表和单链表的区别
结构上的区别
-
单链表:
- 每个节点包含一个数据域和一个指向下一个节点的指针。
- 只有从头节点向下遍历的方向,没有指向前一个节点的指针。
- 头节点没有前驱节点,尾节点的指针指向
NULL
。
-
双链表:
- 每个节点包含一个数据域、一个指向下一个节点的指针和一个指向前一个节点的指针。
- 可以从任一节点双向遍历,即可以向前遍历到头节点,也可以向后遍历到尾节点。
- 头节点的前驱指针指向
NULL
,尾节点的后继指针指向NULL
。
单链表的优缺点
优点:
- 简单性:实现和维护相对简单,节点只需要存储一个指针,内存开销小。
- 内存占用较少:每个节点只包含一个指针,因此内存占用较双链表少。
- 插入和删除操作效率较高:在已知前驱节点的情况下,插入和删除节点的时间复杂度为 O(1)。
缺点:
- 查找效率较低:从头节点遍历到目标节点的时间复杂度为 O(n)。
- 只能单向遍历:不能从尾节点向前遍历到头节点,灵活性差。
双链表的优缺点
优点:
- 双向遍历:可以从任一节点双向遍历,即可以向前遍历到头节点,也可以向后遍历到尾节点。
- 删除节点更方便:在已知节点本身的情况下,删除节点时不需要知道其前驱节点,因为可以通过节点本身直接访问其前驱节点和后继节点。
- 灵活性高:在某些情况下,双向遍历能够更方便地实现算法,比如在某些需要回溯的场景下。
缺点:
- 内存占用较大:每个节点包含两个指针,内存占用较单链表多。
- 实现和维护相对复杂:双向链表的实现和维护比单链表更复杂,需要处理更多的指针操作。
- 插入和删除操作相对复杂:插入和删除操作需要同时更新前驱和后继节点的指针,操作较为繁琐。
3. 代码示例
3.1 双链表节点和结构定义
#include <stdio.h>
#include <stdlib.h>
// 双链表节点结构定义
typedef struct DNode {
int data; // 节点存储的数据
struct DNode *prev; // 指向前一个节点的指针
struct DNode *next; // 指向后一个节点的指针
} DNode;
// 双链表结构定义
typedef struct {
DNode *head; // 双链表头节点指针
DNode *tail; // 双链表尾节点指针
size_t size; // 双链表中的节点个数
} DoublyLinkedList;
DNode
结构体定义了双链表的节点,包含数据域data
和指向前一个节点的指针prev
以及指向后一个节点的指针next
。DoublyLinkedList
结构体定义了双链表,包含指向头节点的指针head
和指向尾节点的指针tail
,以及链表中节点的个数size
。
3.2 初始化双链表
// 初始化双链表
void initDoublyLinkedList(DoublyLinkedList *list) {
list->head = NULL; // 初始化头节点为空
list->tail = NULL; // 初始化尾节点为空
list->size = 0; // 初始化节点个数为0
}
initDoublyLinkedList
函数将双链表的头节点和尾节点设置为NULL
,并将节点个数初始化为 0。
3.3 返回双链表的长度
// 返回双链表的长度
size_t getLength(const DoublyLinkedList *list) {
return list->size; // 返回双链表的节点个数
}
-
getLength
函数返回双链表中的节点个数。
3.4 在指定位置插入元素
// 在指定位置插入元素
void insertAt(DoublyLinkedList *list, size_t index, int element) {
// 检查插入位置是否有效
if (index > list->size) {
return; // 忽略无效的插入位置
}
// 创建新节点
DNode *newNode = (DNode *)malloc(sizeof(DNode));
newNode->data = element;
newNode->prev = NULL;
newNode->next = NULL;
// 如果插入位置是头节点
if (index == 0) {
newNode->next = list->head;
if (list->head != NULL) {
list->head->prev = newNode;
}
list->head = newNode; // 新节点成为头节点
if (list->tail == NULL) {
list->tail = newNode; // 如果链表为空,新节点也是尾节点
}
} else if (index == list->size) {
// 如果插入位置是尾节点
newNode->prev = list->tail;
if (list->tail != NULL) {
list->tail->next = newNode;
}
list->tail = newNode; // 新节点成为尾节点
if (list->head == NULL) {
list->head = newNode; // 如果链表为空,新节点也是头节点
}
} else {
// 找到插入位置的前一个节点
DNode *current = list->head;
for (size_t i = 0; i < index - 1; i++) {
current = current->next;
}
// 插入新节点
newNode->next = current->next;
newNode->prev = current;
if (current->next != NULL) {
current->next->prev = newNode;
}
current->next = newNode;
}
list->size++; // 更新节点个数
}
insertAt
函数在双链表的指定位置插入一个新的元素。如果插入位置是 0,则新节点成为头节点。如果插入位置是链表的末尾,则新节点成为尾节点。否则,通过遍历找到插入位置的前一个节点,并在该位置插入新节点。更新链表的节点个数。
3.5 在末尾插入元素
// 在末尾插入元素
void insertEnd(DoublyLinkedList *list, int element) {
insertAt(list, list->size, element); // 在链表末尾插入元素
}
insertEnd
函数在双链表末尾插入一个新的元素,通过调用insertAt
函数实现。
3.6 删除指定位置的元素并返回被删除的元素
// 删除指定位置的元素并返回被删除的元素
int deleteAt(DoublyLinkedList *list, size_t index) {
// 检查删除位置是否有效
if (index >= list->size) {
return -1; // 忽略无效的删除位置
}
int deletedElement;
// 如果删除位置是头节点
if (index == 0) {
DNode *temp = list->head;
list->head = temp->next;
if (list->head != NULL) {
list->head->prev = NULL;
} else {
list->tail = NULL; // 如果链表只有一个节点
}
deletedElement = temp->data;
free(temp); // 释放被删除节点的内存
} else if (index == list->size - 1) {
// 如果删除位置是尾节点
DNode *temp = list->tail;
list->tail = temp->prev;
if (list->tail != NULL) {
list->tail->next = NULL;
} else {
list->head = NULL; // 如果链表只有一个节点
}
deletedElement = temp->data;
free(temp); // 释放被删除节点的内存
} else {
// 找到删除位置的前一个节点
DNode *current = list->head;
for (size_t i = 0; i < index - 1; i++) {
current = current->next;
}
// 删除节点
DNode *temp = current->next;
current->next = temp->next;
if (temp->next != NULL) {
temp->next->prev = current;
}
deletedElement = temp->data;
free(temp); // 释放被删除节点的内存
}
list->size--; // 更新节点个数
return deletedElement;
}
deleteAt
函数删除双链表中指定位置的节点,并返回该节点的值。如果删除位置是 0,则直接删除头节点。如果删除位置是链表的末尾,则删除尾节点。否则,通过遍历找到删除位置的前一个节点,并删除该位置的节点。更新链表的节点个数。
3.7 删除末尾元素
// 删除末尾元素并返回被删除的元素
int deleteEnd(DoublyLinkedList *list) {
return deleteAt(list, list->size - 1); // 删除链表末尾的元素
}
deleteEnd
函数删除双链表末尾的节点,通过调用deleteAt
函数实现。
3.8 获取指定位置的元素
// 获取指定位置的元素
int getElementAt(const DoublyLinkedList *list, size_t index) {
// 检查索引位置是否有效
if (index >= list->size) {
return -1; // 返回无效的索引
}
// 遍历找到指定位置的节点
DNode *currentNode = list->head;
for (size_t i = 0; i < index; i++) {
currentNode = currentNode->next;
}
return currentNode->data; // 返回指定位置的元素
}
getElementAt
函数返回双链表中指定位置的元素。通过遍历找到指定位置的节点,并返回该节点的值。
3.9 修改指定位置的元素
// 修改指定位置的元素
void modifyAt(DoublyLinkedList *list, size_t index, int newValue) {
// 检查修改位置是否有效
if (index >= list->size) {
return; // 忽略无效的修改位置
}
// 遍历找到指定位置的节点
DNode *currentNode = list->head;
for (size_t i = 0; i < index; i++) {
currentNode = currentNode->next;
}
currentNode->data = newValue; // 修改节点的值
}
modifyAt
函数修改双链表中指定位置的元素值。通过遍历找到指定位置的节点,并修改该节点的值。
3.10 释放双链表内存
// 释放双链表内存
void destroyDoublyLinkedList(DoublyLinkedList *list) {
// 遍历释放每个节点的内存
DNode *currentNode = list->head;
while (currentNode != NULL) {
DNode *temp = currentNode;
currentNode = currentNode->next;
free(temp); // 释放每个节点的内存
}
list->head = NULL; // 头节点置为空
list->tail = NULL; // 尾节点置为空
list->size = 0; // 节点个数置为0
}
destroyDoublyLinkedList
函数释放双链表使用的内存。通过遍历链表,逐个释放每个节点的内存。将头节点和尾节点指针置为空,节点个数置为 0。
4. 流程
初始化双链表:
- 判断链表是否为空。
- 如果为空,设置头指针和尾指针为NULL。
- 如果不为空,创建头节点和尾节点。
插入节点:
- 判断插入位置是在头部、尾部还是中间。
- 如果是头部,设置新节点为头节点并更新头节点的前驱指针。
- 如果是尾部,设置新节点为尾节点并更新尾节点的后继指针。
- 如果是在中间,找到插入位置前一个节点,设置新节点的前驱和后继指针,并更新前一个节点和后一个节点的指针。
- 更新链表长度。
删除节点:
- 判断删除位置是在头部、尾部还是中间。
- 如果是头部,找到头节点并更新头节点指针。
- 如果是尾部,找到尾节点并更新尾节点指针。
- 如果是在中间,找到要删除节点的前一个节点,更新前一个节点和后一个节点的指针。
- 释放被删除节点的内存。
- 更新链表长度。
遍历链表:
- 从头节点开始。
- 判断当前节点是否为NULL。
- 如果不为NULL,访问节点数据并移动到下一个节点,重复判断。
- 如果为NULL,遍历结束。
修改节点数据:
- 找到目标节点。
- 更新节点数据。
5. 完整代码
代码包括双链表的基本操作,包括初始化、插入、删除、获取和修改元素,以及释放链表的内存.
#include <stdio.h>
#include <stdlib.h>
// 双链表节点结构定义
typedef struct DNode {
int data; // 节点存储的数据
struct DNode *prev; // 指向前一个节点的指针
struct DNode *next; // 指向后一个节点的指针
} DNode;
// 双链表结构定义
typedef struct {
DNode *head; // 双链表头节点指针
DNode *tail; // 双链表尾节点指针
size_t size; // 双链表中的节点个数
} DoublyLinkedList;
// 初始化双链表
void initDoublyLinkedList(DoublyLinkedList *list) {
list->head = NULL; // 初始化头节点为空
list->tail = NULL; // 初始化尾节点为空
list->size = 0; // 初始化节点个数为0
}
// 返回双链表的长度
size_t getLength(const DoublyLinkedList *list) {
return list->size; // 返回双链表的节点个数
}
// 在指定位置插入元素
void insertAt(DoublyLinkedList *list, size_t index, int element) {
if (index > list->size) {
return; // 忽略无效的插入位置
}
DNode *newNode = (DNode *)malloc(sizeof(DNode)); // 创建新节点
newNode->data = element;
newNode->prev = NULL;
newNode->next = NULL;
if (index == 0) { // 插入位置是头节点
newNode->next = list->head;
if (list->head != NULL) {
list->head->prev = newNode;
}
list->head = newNode;
if (list->tail == NULL) {
list->tail = newNode;
}
} else if (index == list->size) { // 插入位置是尾节点
newNode->prev = list->tail;
if (list->tail != NULL) {
list->tail->next = newNode;
}
list->tail = newNode;
if (list->head == NULL) {
list->head = newNode;
}
} else {
DNode *current = list->head;
for (size_t i = 0; i < index - 1; i++) {
current = current->next;
}
newNode->next = current->next;
newNode->prev = current;
if (current->next != NULL) {
current->next->prev = newNode;
}
current->next = newNode;
}
list->size++; // 更新节点个数
}
// 在末尾插入元素
void insertEnd(DoublyLinkedList *list, int element) {
insertAt(list, list->size, element); // 在链表末尾插入元素
}
// 删除指定位置的元素并返回被删除的元素
int deleteAt(DoublyLinkedList *list, size_t index) {
if (index >= list->size) {
return -1; // 忽略无效的删除位置
}
int deletedElement;
if (index == 0) { // 删除位置是头节点
DNode *temp = list->head;
list->head = temp->next;
if (list->head != NULL) {
list->head->prev = NULL;
} else {
list->tail = NULL; // 如果链表只有一个节点
}
deletedElement = temp->data;
free(temp); // 释放被删除节点的内存
} else if (index == list->size - 1) { // 删除位置是尾节点
DNode *temp = list->tail;
list->tail = temp->prev;
if (list->tail != NULL) {
list->tail->next = NULL;
} else {
list->head = NULL; // 如果链表只有一个节点
}
deletedElement = temp->data;
free(temp); // 释放被删除节点的内存
} else {
DNode *current = list->head;
for (size_t i = 0; i < index - 1; i++) {
current = current->next;
}
DNode *temp = current->next;
current->next = temp->next;
if (temp->next != NULL) {
temp->next->prev = current;
}
deletedElement = temp->data;
free(temp); // 释放被删除节点的内存
}
list->size--; // 更新节点个数
return deletedElement;
}
// 删除末尾元素并返回被删除的元素
int deleteEnd(DoublyLinkedList *list) {
return deleteAt(list, list->size - 1); // 删除链表末尾的元素
}
// 获取指定位置的元素
int getElementAt(const DoublyLinkedList *list, size_t index) {
if (index >= list->size) {
return -1; // 返回无效的索引
}
DNode *currentNode = list->head;
for (size_t i = 0; i < index; i++) {
currentNode = currentNode->next;
}
return currentNode->data; // 返回指定位置的元素
}
// 修改指定位置的元素
void modifyAt(DoublyLinkedList *list, size_t index, int newValue) {
if (index >= list->size) {
return; // 忽略无效的修改位置
}
DNode *currentNode = list->head;
for (size_t i = 0; i < index; i++) {
currentNode = currentNode->next;
}
currentNode->data = newValue; // 修改节点的值
}
// 释放双链表内存
void destroyDoublyLinkedList(DoublyLinkedList *list) {
DNode *currentNode = list->head;
while (currentNode != NULL) {
DNode *temp = currentNode;
currentNode = currentNode->next;
free(temp); // 释放每个节点的内存
}
list->head = NULL; // 头节点置为空
list->tail = NULL; // 尾节点置为空
list->size = 0; // 节点个数置为0
}
// 打印双链表中的所有元素
void printDoublyLinkedList(const DoublyLinkedList *list) {
DNode *currentNode = list->head;
while (currentNode != NULL) {
printf("%d ", currentNode->data);
currentNode = currentNode->next;
}
printf("\n");
}
// 主函数测试双链表操作
int main() {
DoublyLinkedList myList; // 声明双链表
initDoublyLinkedList(&myList); // 初始化双链表
printf("初始化双链表成功!\n");
insertEnd(&myList, 1); // 链表尾部插入元素1
insertEnd(&myList, 2); // 链表尾部插入元素2
insertEnd(&myList, 3); // 链表尾部插入元素3
printf("向双链表插入了3个元素\n");
printDoublyLinkedList(&myList); // 打印链表中的元素
printf("双链表长度为: %zu\n", getLength(&myList)); // 获取双链表长度
insertAt(&myList, 1, 4); // 在索引1处插入元素4
printf("在索引1处插入元素4\n");
printDoublyLinkedList(&myList); // 打印链表中的元素
printf("双链表长度为: %zu\n", getLength(&myList)); // 再次获取双链表长度
printf("索引1处的元素为: %d\n", getElementAt(&myList, 1)); // 获取索引1处的元素
modifyAt(&myList, 0, 5); // 修改索引0处的元素
printf("把索引0处的元素修改为5\n");
printDoublyLinkedList(&myList); // 打印链表中的元素
printf("删除索引1处的元素,该元素值是: %d\n", deleteAt(&myList, 1)); // 删除索引1处的元素
printDoublyLinkedList(&myList); // 打印链表中的元素
destroyDoublyLinkedList(&myList); // 销毁双链表
printf("双链表销毁成功!\n");
return 0;
}
- 初始化双链表:
initDoublyLinkedList
函数初始化双链表,将头节点和尾节点设置为NULL
,节点个数初始化为 0。 - 返回双链表的长度:
getLength
函数返回双链表中的节点个数。 - 在指定位置插入元素:
insertAt
函数在双链表的指定位置插入一个新节点,根据插入位置调整前后节点的指针。 - 在末尾插入元素:
insertEnd
函数在双链表末尾插入一个新节点,调用insertAt
函数实现。 - 删除指定位置的元素:
deleteAt
函数删除双链表中指定位置的节点,并返回该节点的值,根据删除位置调整前后节点的指针。 - 删除末尾元素:
deleteEnd
函数删除双链表末尾的节点,调用deleteAt
函数实现。 - 获取指定位置的元素:
getElementAt
函数返回双链表中指定位置的元素,通过遍历找到目标节点。 - 修改指定位置的元素:
modifyAt
函数修改双链表中指定位置的节点的值,通过遍历找到目标节点并更新其数据。 - 释放双链表内存:
destroyDoublyLinkedList
函数释放双链表中的所有节点内存,并将链表恢复到初始状态。 - 打印双链表中的所有元素:
printDoublyLinkedList
函数遍历双链表并打印每个节点的数据。