嵌入式学习day19
本文详细介绍了双向链表的结构定义、特点及基本操作。双向链表每个节点包含前驱和后继指针,支持双向遍历,插入删除更灵活。文章详细讲解了创建链表、头插/尾插、查找、更新、统计长度等操作的实现方法,并提供了C语言代码示例。此外还介绍了逆序打印、删除操作及双向链表的典型应用场景。双向链表虽提高了操作灵活性,但也增加了内存开销,需根据实际需求选择数据结构。
数据结构学习笔记:双向链表详解
一、双向链表基础
结构定义
节点结构:每个节点包含三个部分:
data:存储数据。
next:指向下一个节点的指针。
prev:指向前一个节点的指针。
结构体定义(C语言示例):
typedef struct node {
data_t data;
struct node *next; // 指向后继节点
struct node *prev; // 指向前驱节点
} node_t;
双向链表的特点
双向遍历:既可以从头节点向尾节点遍历(通过next指针),也可以从尾节点向头节点遍历(通过prev指针)。
插入/删除操作更灵活:由于每个节点都有前驱和后继指针,插入和删除时可以更方便地调整指针,减少遍历次数。
二、双向链表的基本操作
创建链表
创建头节点:
分配内存给头节点。
初始化prev和next指针为NULL。
代码示例:
node_t *creat_doublist(void) {
node_t *head = (node_t *)malloc(sizeof(node_t));
if (head == NULL) {
// 处理内存分配失败的情况
return NULL;
}
head->prev = NULL;
head->next = NULL;
return head;
}
插入操作
头插法:
步骤:
创建新节点p_new,填充数据。
调整指针:
p_new->next = phead->next;
p_new->prev = phead;
phead->next = p_new;
如果原头节点的next不为NULL,则原头节点的next的prev指向p_new。
代码示例:
void doublisr_insert_head(node_t *phead, data_t data)
{
node_t *p_new = (node_t *)malloc(sizeof(node_t));
if (p_new == NULL)
{
// 处理内存分配失败的情况
return;
}
p_new->data = data;
p_new->next = phead->next;
p_new->prev = phead;
phead->next = p_new;
if (p_new->next != NULL)
{
p_new->next->prev = p_new;
}
}
尾插法:
步骤:
创建新节点p_new,填充数据。
找到尾节点(通过遍历链表,直到next为NULL)。
调整指针:
尾节点的next指向p_new。
p_new的prev指向尾节点。
p_new的next指向NULL。
代码示例:
void doublisr_insert_tail(node_t *phead, data_t data) {
node_t *p_new = (node_t *)malloc(sizeof(node_t));
if (p_new == NULL) {
// 处理内存分配失败的情况
return;
}
p_new->data = data;
p_new->next = NULL;
node_t *p = phead;
while (p->next != NULL) {
p = p->next;
}
p->next = p_new;
p_new->prev = p;
}
查找操作
查找某个值:
从头节点开始遍历链表,比较每个节点的data与目标值,如果相等则返回该节点的指针,否则返回NULL。
代码示例:
node_t *doublisr_find_key(node_t *phead, data_t data) {
node_t *p = phead->next; // 从第一个数据节点开始
while (p != NULL) {
if (p->data == data) {
return p;
}
p = p->next;
}
return NULL; // 未找到
}
更新操作
更新某个值:
先通过查找操作找到目标节点,然后修改其data。
代码示例:
node_t *doublisr_update_key(node_t *phead, data_t old_data,
data_t new_data)
{
node_t *p = doublisr_find_key(phead, old_data);
if (p != NULL)
{
p->data = new_data;
return p;
}
return NULL; // 未找到
}
统计长度
统计链表长度:
从头节点的下一个节点开始遍历,直到next为NULL,统计节点个数。
代码示例:
int length(node_t *phead) {
int len = 0;
node_t *p = phead->next; // 从第一个数据节点开始
while (p != NULL) {
len++;
p = p->next;
}
return len;
}
三、双向链表的其他操作
逆序打印
从尾节点开始,通过prev指针向前遍历,直到遇到头节点,打印每个节点的data。
代码示例:
void print_reverse(node_t *phead) {
node_t *p = phead;
while (p->next != NULL) {
p = p->next; // 找到尾节点
}
while (p != phead) {
printf("%d ", p->data);
p = p->prev; // 通过prev指针向前遍历
}
printf("n");
}
删除操作(图中未详细展示,但属于基本操作)
删除某个节点:
找到要删除的节点p_del。
调整其前驱节点的next指针和后继节点的prev指针:
p_del->prev->next = p_del->next;
p_del->next->prev = p_del->prev;
释放p_del的内存。
四、双向链表的应用场景
需要频繁双向遍历的场景:如浏览器的前进/后退功能,双向链表可以方便地向前或向后移动。
需要频繁在头部或尾部插入/删除的场景:如队列、栈的实现,双向链表可以在O(1)时间内完成头尾操作。
需要快速查找前驱或后继的场景:如某些缓存算法(LRU),需要快速调整节点的位置。
五、总结
双向链表通过增加prev指针,实现了双向遍历和更灵活的插入/删除操作,但同时也增加了内存开销(每个节点需要额外存储一个指针)。在实际应用中,需要根据具体需求选择合适的数据结构。
更多推荐



所有评论(0)