数据结构-双向链表(C语言)

数据结构-双向链表(C语言)

双向链表概念和结构

双向链表是一种常见的数据结构,用于存储一系列元素。它由一系列节点(Node)组成,每个节点包含两部分:数据部分(存储数据元素)和指针部分(指向下一个节点的指针和指向上一个节点的指针),通过链表中的某个节点能访问其下一个节点,也能访问其前一个节点,因此称为“双向”。

结构特点:

  1. 节点(Node): 每个节点由数据和指向下一个节点的指针和指向上一个节点的指针组成。
  2. 头节点(Head): 指向链表的第一个节点。通过头节点可以遍历整个链表,头节点中指向上一个节点的指针指向链表地址。
  3. 尾节点(Tail): 链表的最后一个节点,通过尾节点可以反向遍历整个链表,尾节点中指向下一个节点的指针指向链表地址。
  4. 链表结束标志: 双向链表是环状结构,尾节点中指向下一个节点的指针指向链表地址,链表本身作为一个节点,指向下一个节点的指针指向头节点,

双向链表的示意:LIST(Node0) <-> Node1(Head) <-> Node2 <-> Node3 <-> ... <-> NodeN(Tail) <-> LIST(Node0)

绘图2

双向链表本身是一个节点,当链表为空时,节点的两个指针都指向自身,本身构成环状结构。在非空链表中,双向链表与其他节点构成环状结构。

相比于单向链表,双向链表的最大特点是可以通过某个节点找到前一个节点,使用上比较灵活,删除节点比较高效。缺点在于双向链表节点需要存储前一节点的指针信息,空间开销大,指针操作也比较复杂。

基本操作:

  1. 插入操作
    • 在链表的头部插入新节点。
    • 在链表的尾部插入新节点。
    • 在指定节点后插入新节点。
    • 在指定节点前插入新节点。
  2. 删除操作
    • 删除头节点。
    • 删除尾节点。
    • 删除指定节点。
  3. 查找操作
    • 按值查找:根据节点的值查找节点。
    • 按位置查找:根据节点在链表中的位置查找节点。
  4. 遍历操作
    • 从头节点开始,依次访问链表中的每个节点,直到尾节点为止。

优点和缺点:

  • 优点
    • 插入和删除节点的时间复杂度为 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;

双向链表中定义headtail分别指向链表的头和尾。

双向链表初始化

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为链表本身,nodenext指针设置为listnodeprev指针设置为taillisttaillistnextheader设置为node,listtail设置为node

当链表不为空时,tail为链表中最后一个节点,nodenext指针设置为listnodeprev指针设置为tailtailnext设置为nodelisttail设置为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为链表本身,nodenext指针设置为headerlistnodeprev指针设置为listlistheader设置为nodeheaderlistprevtail设置为node

当链表不为空时,header为链表中第一个节点,nodenext指针设置为headernodeprev指针设置为listlistheader设置为nodeheaderprev设置为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;
}

遍历链表,返回链表中节点个数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值