数据结构-双向链表(C语言)
双向链表概念和结构
双向链表是一种常见的数据结构,用于存储一系列元素。它由一系列节点(Node)组成,每个节点包含两部分:数据部分(存储数据元素)和指针部分(指向下一个节点的指针和指向上一个节点的指针),通过链表中的某个节点能访问其下一个节点,也能访问其前一个节点,因此称为“双向”。
结构特点:
- 节点(Node): 每个节点由数据和指向下一个节点的指针和指向上一个节点的指针组成。
- 头节点(Head): 指向链表的第一个节点。通过头节点可以遍历整个链表,头节点中指向上一个节点的指针指向链表地址。
- 尾节点(Tail): 链表的最后一个节点,通过尾节点可以反向遍历整个链表,尾节点中指向下一个节点的指针指向链表地址。
- 链表结束标志: 双向链表是环状结构,尾节点中指向下一个节点的指针指向链表地址,链表本身作为一个节点,指向下一个节点的指针指向头节点,
双向链表的示意:LIST(Node0) <-> Node1(Head) <-> Node2 <-> Node3 <-> ... <-> NodeN(Tail) <-> LIST(Node0)

双向链表本身是一个节点,当链表为空时,节点的两个指针都指向自身,本身构成环状结构。在非空链表中,双向链表与其他节点构成环状结构。
相比于单向链表,双向链表的最大特点是可以通过某个节点找到前一个节点,使用上比较灵活,删除节点比较高效。缺点在于双向链表节点需要存储前一节点的指针信息,空间开销大,指针操作也比较复杂。
基本操作:
- 插入操作:
- 在链表的头部插入新节点。
- 在链表的尾部插入新节点。
- 在指定节点后插入新节点。
- 在指定节点前插入新节点。
- 删除操作:
- 删除头节点。
- 删除尾节点。
- 删除指定节点。
- 查找操作:
- 按值查找:根据节点的值查找节点。
- 按位置查找:根据节点在链表中的位置查找节点。
- 遍历操作:
- 从头节点开始,依次访问链表中的每个节点,直到尾节点为止。
优点和缺点:
- 优点:
- 插入和删除节点的时间复杂度为 O(1),只需要修改指针,不需要移动大量元素。
- 不需要预先分配空间大小,可以动态地增加或减少元素。
- 缺点:
- 需要额外的指针空间来存储节点之间的关系。
- 不能直接访问链表中的任意元素,需要从头开始遍历找到指定位置的节点。
双向链表是一种基础但非常实用的数据结构,当需要频繁的删除插入某个节点或者需要双线遍历时,双向链表是一个简单而有效的选择。
双向链表实现
在嵌入式中,通常对代码的size和执行效率有很高的要求,双向链表的实现比较简单,所以某些实现定义在头文件中,将函数声明为static inline,免去了函数被调用时的jump and link和压栈出栈的指令开销,使得代码的运行效率进一步提高。
节点定义
struct dnode
{
union
{
struct dnode *head; /* ptr to head of list */
struct dnode *next; /* ptr to next node */
};
union
{
struct dnode *tail; /* ptr to tail of list */
struct dnode *prev; /* ptr to previous node */
};
};
typedef struct dnode dnode_t;
为使链表具有通用性,在节点中只定义指针部分,数据部分通过自定义结构体的方式定义。如下:
struct my_data
{
dnode node;
char* name;
uint32_t age;
} my_data_t;
node使得my_data_t可作为节点链接在链表中,my_data_t中数据可随实际情况改变。
查看节点下一个节点
static inline struct dnode *dnode_peek_next(struct dnode *node)
{
return node->next;
}
查看节点上一个节点
static inline struct dnode *dnode_peek_prev(struct dnode *node)
{
return node->prev;
}
节点初始化
static inline void dnode_init(struct dnode *node)
{
node->prev = NULL;
node->next = NULL;
}
双向链表定义
typedef struct dnode dlist_t;
双向链表中定义head和tail分别指向链表的头和尾。
双向链表初始化
static inline void dlist_init(dlist_t *list)
{
list->head = (dnode_t *)list;
list->tail = (dnode_t *)list;
}
双线链表的头指针和尾指针同时指向双向链表自身地址,即list(header) <-> list(tail) <-> list(header)。header的next/prev指针都是tail,tail的next/prev指针都是header。
双向链表是否为空
static inline bool dlist_is_empty(dlist_t *list)
{
return list->head == list;
}
通过判断双向链表的头指针是否为链表自身地址即可。
双向链表查看头节点
static inline struct dnode *dlist_peek_head(dlist_t *list)
{
return list->head;
}
双向链表查看尾节点
static inline struct dnode *dlist_peek_tail(dlist_t *list)
{
return list->tail;
}
双向链表删除节点
static inline void dlist_remove(struct dnode *node)
{
struct dnode *prev = node->prev;
struct dnode *next = node->next;
prev->next = next;
next->prev = prev;
dnode_init(node);
}
双向链表尾插节点
void dlist_insert_back(dlist_t *list, struct dnode *node)
{
struct dnode *tail = dlist_peek_tail(list);
node->next = list;
node->prev = tail;
tail->next = node;
list->tail = node;
}
当链表为空时,tail为链表本身,node的next指针设置为list,node的prev指针设置为tail即list,tail即list的next即header设置为node,list的tail设置为node。
当链表不为空时,tail为链表中最后一个节点,node的next指针设置为list,node的prev指针设置为tail,tail的next设置为node,list的tail设置为node。
双向链表头插节点
void dlist_insert_front(dlist_t *list, struct dnode *node)
{
struct dnode *head = dlist_peek_head(list);
node->next = head;
node->prev = list;
list->head = node;
head->prev = node;
}
当链表为空时,header为链表本身,node的next指针设置为header即list,node的prev指针设置为list,list的header设置为node,header即list的prev即tail设置为node。
当链表不为空时,header为链表中第一个节点,node的next指针设置为header,node的prev指针设置为list,list的header设置为node,header的prev设置为node。
双链表指定位置插入节点
void dlist_insert_after(dlist_t *list, struct dnode *ref_node, struct dnode *new_node)
{
struct dnode *next = dnode_peek_next(ref_node);
ref_node->next = new_node;
new_node->prev = ref_node;
new_node->next = next;
next->prev = new_node;
}
void dlist_insert_before(dlist_t *list, struct dnode *ref_node, struct dnode *new_node)
{
struct dnode *prev = dnode_peek_prev(ref_node);
ref_node->prev = new_node;
new_node->next = ref_node;
new_node->prev = prev;
prev->next = new_node;
}
双向链表提取头节点
struct dnode *dlist_extract_front(dlist_t *list)
{
struct dnode *node = dlist_peek_head(list);
if (node == list)
{
return NULL;
}
dlist_remove(node);
return node;
}
双向链表指定位置提取节点
struct dnode *dlist_extract_after(dlist_t *list, struct dnode *ref_node)
{
struct dnode *node = dnode_peek_next(ref_node);
if (node == list)
{
return NULL;
}
dlist_remove(node);
return node;
}
双向链表发现节点
static inline bool dlist_find(dlist_t *list, struct dnode *node)
{
struct dnode *cur = dlist_peek_head(list);
while (cur != list)
{
if (cur == node)
{
return true;
}
cur = dnode_peek_next(cur);
}
return false;
}
遍历链表,如果其中的节点与node一致,返回true,否则返回false。
双向链表大小
uint32_t dlist_size(dlist_t *list)
{
uint32_t size = 0;
struct dnode *cur = dlist_peek_head(list);
while (cur != list)
{
size++;
cur = dnode_peek_next(cur);
}
return size;
}
遍历链表,返回链表中节点个数。

被折叠的 条评论
为什么被折叠?



