也没想象中那么神秘的数据结构-一种通用化双向链表设计(底层源码)

概述

在操作系统内核中,绝大多数数据结构都是通过链表来实现的,相对于数组,链表可以很方便的用来管理数据,给数据管理带来了更多的可能性。

注意

1、不论是linux、vxWorks还是其它操作系统中内核链表都承担着绝大部分数据管理工作。

2、因为链表可以很灵活的管理数据,所以在使用和设计链表结构的时候,我们也应该尽可能提高灵活性,而不应该将链表局限于某一点或者某一类数据类型。

3、通用化的链表设计核心,在于定义链表结构的时候,只定义指针域,通过指针域完成对节点的所有处理。

4、在具体应用中,将链表结构放到具体节点中首地址(节点继承链表对象),根据继承向上造型的原则,可以将子类的指针引用和转换为基类类型的指针或引用从而通过节点地址调用链表的一系列处理函数。

介绍

示例代码从vxWorks源码中提取而来,进行二次封装,并加入测试代码验证,可直接下载使用。

另外,linux源码中的链表可以将链表结构加入到节点任何位置,通过调用宏container_of获取节点地址。后面文章会对此进行详细阐述。

核心

通用化双向链表由前驱指针域、后继指针域,以及数据构成,数据用来存储链表元素个数,前驱指针指向尾节点,后继指针指向头指针,链表节点只包含前驱指针域和后继指针域,用于节点间的链接如下图代码和图形所示。

/* 节点定义 */
struct t_node {
    struct t_node *p_prev;   /* 前节点 */
    struct t_node *p_next;   /* 后节点 */
};

/* 链表定义 */
struct t_list
{
    struct t_node node;     /* 头结点 */
    int count;              /* 链表长度 */
};

#define HEAD    node.p_next        /* 链表头结点 */
#define TAIL    node.p_prev        /* 链表尾结点 */

链表的插入分为在链表头插入、链表尾插入以及链表中插入三种。

链表头插入示意图:

链表尾插入示意图:

链表中插入示意图:

插入的核心在于找到插入位置前后的节点,然后修改对应的指针指向,从而实现插入功能。具体代码如下:

/**
 * @在链表指定节点之后插入节点
 * @p_list:链表     p_prev:指定节点         p_node:插入节点                               
**/
void lst_insert(struct t_list *p_list, 
                    struct t_node *p_prev, struct t_node *p_node)
{
    struct t_node *p_next;

    if (p_prev == NULL) {    /* 头结点插入 */
        p_next = p_list->HEAD;
        p_list->HEAD = p_node;
    } else {
        p_next = p_prev->p_next;
        p_prev->p_next = p_node;
    }

    if (p_next == NULL)     /* 尾节点插入 */
        p_list->TAIL = p_node;
    else
        p_next->p_prev = p_node;


    /* 处理插入节点指针及链表长度 */    
    p_node->p_next    = p_next;
    p_node->p_prev    = p_prev;

    p_list->count++;
}

链表节点删除同样分为删除头节点、删除尾节点以及删除中间节点。

删除头节点:将头结点移动到删除节点的下一个节点。

删除尾节点:将尾节点移动到删除节点的前一个节点。

删除中间节点:将删除节点的前一个节点和后一个节点直接相连。

/**
 * @删除链表中指定节点
 * @p_list:链表     p_node:节点                       
**/
void lst_del(struct t_list *p_list, struct t_node *p_node)
{
    if (p_node->p_prev == NULL)     /* 头结点 */
        p_list->HEAD = p_node->p_next;
    else
        p_node->p_prev->p_next = p_node->p_next;

    if (p_node->p_next == NULL)     /* 尾节点 */
        p_list->TAIL = p_node->p_prev;
    else
        p_node->p_next->p_prev = p_node->p_prev;

    p_list->count--;
}

节点的获取操作:

1. 根据偏移量和链表长度对比,判断节点属于前半段还是后半段。

2. 若在前半段,则通过后继指针,顺序遍历。

3. 若在后半段,则通过前驱指针,逆向遍历。

/**
 * @获取第N个结点,不删除
 * @p_list:链表         pos:节点偏移
 * @返回结点                    
**/
struct t_node *lst_n_th(struct t_list *p_list, int pos)
{
    struct t_node *p_node;

    if ((pos < 1) || (pos > p_list->count)) /* 1 <= pos <= count */
        return NULL;

    if (pos < (p_list->count >> 1)) {       /* 前半段,从头结点开始查找 */
        p_node = p_list->HEAD;

        while (--pos > 0)
            p_node = p_node->p_next;
    } else {                                /* 后半段,从尾结点开始查找 */
        pos -= p_list->count;
        p_node = p_list->TAIL;

        while (pos++ < 0)
            p_node = p_node->p_prev;
    }

    return (p_node);
}

查找指定节点位置:从头节点开始遍历链表,若找到指定节点,则返回对应的偏移量,若未找到,返回-1。

/**
 * @查找指定节点在链表中的位置
 * @p_list:链表     p_node:指定节点
 * @位置(从1开始,不存在返回-1)            
**/
int lst_find(struct t_list *p_list, struct t_node *p_node)
{
    struct t_node *p_next;
    int index = 1;

    p_next = lst_first(p_list);

    while ((p_next!=NULL) && (p_next!=p_node)) {
        index++;
        p_next = lst_next(p_next);
    }

    if (p_next == NULL)
        return (-1);
    else
        return (index);
}

获取距离指定节点N步的节点:n<0表明在前面,通过前驱指针逆向查找,n>0表明在后面,通过后继指针顺向查找。同样,若链表长度不够,意味着节点不存在。

/**
 * @获取距离指定节点N步的结点,不删除
 * @p_node:指定节点     n:距离,>0在后面,<0在前面
 * @返回结点               
**/
struct t_node *lst_n_step(struct t_node *p_node, int n)
{
    int i;

    for (i=0; i<abs(n); i++) {
        if (n < 0)
            p_node = p_node->p_prev;
        else if (n > 0)
            p_node = p_node->p_next;

        if (p_node == NULL)
            break;
    }
    return (p_node);
}

拼接两个链表:

1. 将目标链表的尾节点的下一个节点指向添加链表的头节点。

2. 将添加节点的头节点的前一个节点指向目标节点的尾节点。

3. 将目标节点的尾节点指向添加链表的尾节点。

4. 改变链表总长度,删除添加链表。

/**
 * @拼接两个链表
 * @p_dst:目标链表     p_add:添加链表                 
**/
void lst_concat(struct t_list *p_dst, struct t_list *p_add)
{
    if (p_add->count == 0)
        return;

    if (p_dst->count == 0) {
        *p_dst = *p_add;
    } else {
        p_dst->TAIL->p_next = p_add->HEAD;
        p_add->HEAD->p_prev = p_dst->TAIL;
        p_dst->TAIL         = p_add->TAIL;

        p_dst->count += p_add->count;
    }

    lst_init(p_add);    /* 清空添加链表 */
}

提取子链分为从头结点提取、提取到尾节点以及提取链表中间部分三种情况。

从头结点提取:将链表的头节点指向子链尾节点的下一个节点。

提取到尾节点:将链表的尾节点指向子链头结点的前一个节点。

提取链表中间部分:将子链头节点的前一个节点的下一个节点指向尾节点的下一个节点。并将尾节点的下一个节点的前一个节点指向头节点的前一个节点,如下图所示。

/**
 * @提取链表中的子链
 * @p_src:链表     p_dst:子链 
 * @p_start: 起始节点   p_end:终止节点                  
**/
void lst_extract(struct t_list *p_src, struct t_node *p_start, 
                        struct t_node *p_end, struct t_list *p_dst)
{
    int cnt = 0;
    struct t_node *p_node;

    /* 处理原链表,减去节点 */
    if (p_start->p_prev == NULL)
        p_src->HEAD = p_end->p_next;
    else
        p_start->p_prev->p_next = p_end->p_next;

    if (p_end->p_next == NULL)
        p_src->TAIL = p_start->p_prev;
    else
        p_end->p_next->p_prev = p_start->p_prev;


    /* 处理子链 */
    p_dst->HEAD = p_start;
    p_dst->TAIL = p_end;

    p_start->p_prev = NULL;
    p_end->p_next   = NULL;

    /* 处理长度 */
    for (p_node=p_start; p_node!=NULL; p_node=p_node->p_next)
        cnt++;

    p_src->count -= cnt;
    p_dst->count = cnt;
}

示例

★包含头文件dll.h和源文件dll.c(均已验证通过)。

 dll.h

/**
 * @Filename : dll.h
 * @Revision : $Revision: 1.0 $
 * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅)
 * @Description : 通用双向链表设计
 * @Explain : 基于VxWorks源代码lstLib.h中提取而来, dll = doubly linked list
**/

#ifndef __DLL_H__
#define __DLL_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>

/* 节点定义 */
struct t_node {
    struct t_node *p_prev;   /* 前节点 */
    struct t_node *p_next;   /* 后节点 */
};

/* 链表定义 */
struct t_list
{
    struct t_node node;     /* 头结点 */
    int count;                   /* 链表长度 */
};

#define HEAD    node.p_next        /* 链表头结点 */
#define TAIL    node.p_prev        /* 链表尾结点 */

/**
 * @初始化链表
 * @p_list:链表                      
**/
void lst_init(struct t_list *p_list);

/**
 * @添加节点到链表尾部
 * @p_list:链表     p_node:节点                       
**/
void lst_add(struct t_list *p_list, struct t_node *p_node);

/**
 * @拼接两个链表
 * @p_dst:目标链表     p_add:添加链表                 
**/
void lst_concat(struct t_list *p_dst, struct t_list *p_add);

/**
 * @链表长度获取
 * @p_list:链表
 * @返回链表长度                         
**/
int lst_count(struct t_list *p_list);

/**
 * @删除链表中指定节点
 * @p_list:链表     p_node:节点                       
**/
void lst_del(struct t_list *p_list, struct t_node *p_node);

/**
 * @提取链表中的子链
 * @p_src:链表     p_dst:子链 
 * @p_start: 起始节点   p_end:终止节点                  
**/
void lst_extract(struct t_list *p_src, struct t_node *p_start, 
                        struct t_node *p_end, struct t_list *p_dst);

/**
 * @获取链表头结点,不删除
 * @p_list:链表 
 * @返回头结点                     
**/
struct t_node *lst_first(struct t_list *p_list);

/**
 * @获取链表头结点并删除
 * @p_list:链表 
 * @返回头结点                     
**/
struct t_node *lst_get(struct t_list *p_list);

/**
 * @在链表指定节点之后插入节点
 * @p_list:链表     p_prev:指定节点     p_node:插入节点                               
**/
void lst_insert(struct t_list *p_list, 
                    struct t_node *p_prev, struct t_node *p_node);

/**
 * @获取链表尾结点,不删除
 * @p_list:链表
 * @返回尾结点                     
**/
struct t_node *lst_last(struct t_list *p_list);

/**
 * @获取下一个结点,不删除
 * @p_node:节点
 * @返回下一个结点                     
**/
struct t_node *lst_next(struct t_node *p_node);

/**
 * @获取第N个结点,不删除
 * @p_list:链表         pos:节点偏移
 * @返回结点                    
**/
struct t_node *lst_n_th(struct t_list *p_list, int pos);

/**
 * @获取前一个结点,不删除
 * @p_node:节点
 * @返回前一个结点                 
**/
struct t_node *lst_prev(struct t_node *p_node);

/**
 * @获取距离指定节点N步的结点,不删除
 * @p_node:指定节点     n:距离,>0在后面,<0在前面
 * @返回结点               
**/
struct t_node *lst_n_step(struct t_node *p_node, int n);

/**
 * @查找指定节点在链表中的位置
 * @p_list:链表     p_node:指定节点
 * @位置(从1开始,不存在返回-1)            
**/
int lst_find(struct t_list *p_list, struct t_node *p_node);

/**
 * @销毁释放链表
 * @p_list:链表              
**/
void lst_free(struct t_list *p_list);

#ifdef __cplusplus
}
#endif

#endif

 dll.c

/**
 * @Filename : dll.c
 * @Revision : $Revision: 1.0 $
 * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅)
 * @Description : 通用双向链表设计
 * @Explain : 基于VxWorks源代码lstLib.h中提取而来, dll = doubly linked list
**/

#include "dll.h"

/**
 * @初始化链表
 * @p_list:链表                      
**/
void lst_init(struct t_list *p_list)
{
    p_list->HEAD = NULL;
    p_list->TAIL = NULL;
    p_list->count = 0;
}

/**
 * @添加节点到链表尾部
 * @p_list:链表     p_node:节点                       
**/
void lst_add(struct t_list *p_list, struct t_node *p_node)
{
    lst_insert(p_list, p_list->TAIL, p_node);
}

/**
 * @拼接两个链表
 * @p_dst:目标链表     p_add:添加链表                 
**/
void lst_concat(struct t_list *p_dst, struct t_list *p_add)
{
    if (p_add->count == 0)
        return;

    if (p_dst->count == 0) {
        *p_dst = *p_add;
    } else {
        p_dst->TAIL->p_next = p_add->HEAD;
        p_add->HEAD->p_prev = p_dst->TAIL;
        p_dst->TAIL         = p_add->TAIL;

        p_dst->count += p_add->count;
    }

    lst_init(p_add);    /* 清空添加链表 */
}

/**
 * @链表长度获取
 * @p_list:链表
 * @返回链表长度                         
**/
int lst_count(struct t_list *p_list)
{
    return (p_list->count);
}

/**
 * @删除链表中指定节点
 * @p_list:链表     p_node:节点                       
**/
void lst_del(struct t_list *p_list, struct t_node *p_node)
{
    if (p_node->p_prev == NULL)     /* 头结点 */
        p_list->HEAD = p_node->p_next;
    else
        p_node->p_prev->p_next = p_node->p_next;

    if (p_node->p_next == NULL)     /* 尾节点 */
        p_list->TAIL = p_node->p_prev;
    else
        p_node->p_next->p_prev = p_node->p_prev;

    p_list->count--;
}

/**
 * @提取链表中的子链
 * @p_src:链表     p_dst:子链 
 * @p_start: 起始节点   p_end:终止节点                  
**/
void lst_extract(struct t_list *p_src, struct t_node *p_start, 
                        struct t_node *p_end, struct t_list *p_dst)
{
    int cnt = 0;
    struct t_node *p_node;

    /* 处理原链表,减去节点 */
    if (p_start->p_prev == NULL)
        p_src->HEAD = p_end->p_next;
    else
        p_start->p_prev->p_next = p_end->p_next;

    if (p_end->p_next == NULL)
        p_src->TAIL = p_start->p_prev;
    else
        p_end->p_next->p_prev = p_start->p_prev;


    /* 处理子链 */
    p_dst->HEAD = p_start;
    p_dst->TAIL = p_end;

    p_start->p_prev = NULL;
    p_end->p_next   = NULL;

    /* 处理长度 */
    for (p_node=p_start; p_node!=NULL; p_node=p_node->p_next)
        cnt++;

    p_src->count -= cnt;
    p_dst->count = cnt;
}

/**
 * @获取链表头结点,不删除
 * @p_list:链表 
 * @返回头结点                     
**/
struct t_node *lst_first(struct t_list *p_list)
{
    return (p_list->HEAD);
}

/**
 * @获取链表头结点并删除
 * @p_list:链表 
 * @返回头结点                     
**/
struct t_node *lst_get(struct t_list *p_list)
{
    struct t_node *p_node = p_list->HEAD;

    if (p_node == NULL)
        return NULL;

    p_list->HEAD = p_node->p_next;      /* 设置第二个节点为头结点 */

    if (p_node->p_next == NULL)         /* 空链表 */
        p_list->TAIL = NULL;
    else
        p_node->p_next->p_prev = NULL;

    p_list->count--;

    return (p_node);
}

/**
 * @在链表指定节点之后插入节点
 * @p_list:链表     p_prev:指定节点         p_node:插入节点                               
**/
void lst_insert(struct t_list *p_list, 
                    struct t_node *p_prev, struct t_node *p_node)
{
    struct t_node *p_next;

    if (p_prev == NULL) {    /* 头结点插入 */
        p_next = p_list->HEAD;
        p_list->HEAD = p_node;
    } else {
        p_next = p_prev->p_next;
        p_prev->p_next = p_node;
    }

    if (p_next == NULL)     /* 尾节点插入 */
        p_list->TAIL = p_node;
    else
        p_next->p_prev = p_node;


    /* 处理插入节点指针及链表长度 */    
    p_node->p_next    = p_next;
    p_node->p_prev    = p_prev;

    p_list->count++;
}

/**
 * @获取链表尾结点,不删除
 * @p_list:链表
 * @返回尾结点                     
**/
struct t_node *lst_last(struct t_list *p_list)
{
    return (p_list->TAIL);
}

/**
 * @获取下一个结点,不删除
 * @p_node:节点
 * @返回下一个结点                     
**/
struct t_node *lst_next(struct t_node *p_node)
{
    return (p_node->p_next);
}

/**
 * @获取第N个结点,不删除
 * @p_list:链表         pos:节点偏移
 * @返回结点                    
**/
struct t_node *lst_n_th(struct t_list *p_list, int pos)
{
    struct t_node *p_node;

    if ((pos < 1) || (pos > p_list->count)) /* 1 <= pos <= count */
        return NULL;

    if (pos < (p_list->count >> 1)) {       /* 前半段,从头结点开始查找 */
        p_node = p_list->HEAD;

        while (--pos > 0)
            p_node = p_node->p_next;
    } else {                                /* 后半段,从尾结点开始查找 */
        pos -= p_list->count;
        p_node = p_list->TAIL;

        while (pos++ < 0)
            p_node = p_node->p_prev;
    }

    return (p_node);
}

/**
 * @获取前一个结点,不删除
 * @p_node:节点
 * @返回前一个结点                 
**/
struct t_node *lst_prev(struct t_node *p_node)
{
    return (p_node->p_prev);
}

/**
 * @获取距离指定节点N步的结点,不删除
 * @p_node:指定节点     n:距离,>0在后面,<0在前面
 * @返回结点               
**/
struct t_node *lst_n_step(struct t_node *p_node, int n)
{
    int i;

    for (i=0; i<abs(n); i++) {
        if (n < 0)
            p_node = p_node->p_prev;
        else if (n > 0)
            p_node = p_node->p_next;

        if (p_node == NULL)
            break;
    }
    return (p_node);
}

/**
 * @查找指定节点在链表中的位置
 * @p_list:链表     p_node:指定节点
 * @位置(从1开始,不存在返回-1)            
**/
int lst_find(struct t_list *p_list, struct t_node *p_node)
{
    struct t_node *p_next;
    int index = 1;

    p_next = lst_first(p_list);

    while ((p_next!=NULL) && (p_next!=p_node)) {
        index++;
        p_next = lst_next(p_next);
    }

    if (p_next == NULL)
        return (-1);
    else
        return (index);
}

/**
 * @销毁释放链表
 * @p_list:链表              
**/
void lst_free(struct t_list *p_list)
{
    struct t_node *p_node, *p_mid;

    if (p_list->count > 0) {
        p_node = p_list->HEAD;

        while (p_node != NULL) {
            p_mid = p_node->p_next;
            free(p_node);
            p_node = p_mid;
        }

        p_list->count = 0;
        p_list->HEAD = p_list->TAIL = NULL;
    }
}

结论

示例代码从vxWorks源码中提取而来,加入自己的分析和注释,请结合后面文章《也没想象中那么神秘的数据结构-一种通用化双向链表设计(对象设计)》和《也没想象中那么神秘的数据结构-一种通用化双向链表设计(测试例程)》测试使用。

往期 · 推荐

也没想象中那么神秘的数据结构-一环扣一环的“链表”(循环双链表)

也没想象中那么神秘的数据结构-一环扣一环的“链表”(双向链表)

‍‍‍我用C语言玩对象,框架化的模板模式‍‍‍

我用C语言玩对象,观察者模式应用2-热水的用途

我用C语言玩对象,状态模式应用1-水的三态

关注

更多精彩内容,请关注微信公众号:不只会拍照的程序猿,本人致力分享linux、设计模式、C语言、嵌入式、编程相关知识,也会抽空分享些摄影相关内容,同样也分享大量摄影、编程相关视频和源码,另外你若想要本文章源码请关注公众号:不只会拍照的程序猿,后台回复:数据结构源码,也可点击此处下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不只会拍照的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值