29

链表-双向非通用链表

 4 years ago
source link: http://www.cnblogs.com/lizhuming/p/13792784.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

目录

前言

  • 20201010
  • 在阅读 RTOS LiteOS 内核源码时发现该内核使用的链表时 通用链表 ,而 FreeRTOS 内核使用的时 非通用链表 ,所以,有必要发布一下关于链表实现的笔记。
  • 以下内容为个人笔记,涉及一些非专业词汇,敬请谅解,谢谢。

链接

参考

  • 上面链接
  • FreeRTOS 内核源码
  • 野火

概念

  • 正常表达

    • 链表:
      • 链表为 C 中一种基础的数据结构。
      • 看成环形晾衣架即可。
    • 节点:
      • 节点组成链表
  • 自理解概念

    • 链表:圆形的晾衣架
    • 节点:挂钩
      • 包含上一个
      • 下一个
      • 钩子等其它需要的信息
    • 袜子:挂在到 钩子 的东西
      • 包含 被钩子
      • 袜子携带的信息
        bQZJVb.png!mobile
  • 通用链表与非通用链表的区别

    • 通用链表节点内容很少一般只有 上一个下一个
    • 通用链表节点被放到信息结构体中,通过偏移找到所在的结构体(即是通过偏移找到袜子头)
    • 而非通用链表是在节点中携带信息结构体的指针的(即是节点就携带信息)。
    • 别人通俗理解,读者不必理会本小点
      • 通用链表是把袜子放到晾衣架的圆形圈上,袜子与圆形圈接触部分为袜子接待的节点。( 信息携带节点
      • 非通用链表是。( 节点携带信息

笔录草稿

双向链表

  • 双向链表理解图

  • 原理:链表包括 根节点普通节点

    • 根节点主要管理链表的,一般包括

      • 上一个
      • 下一个
      • 存在多少个等信息
        VfiMjeQ.png!mobile
    • 普通节点主要用于钩住袜子(即是携带信息)

节点及节点结构体代码

  • 普通节点
    • 存放节点信息
    • 挂载东西(挂钩),如挂载袜子等等
      iIF3Q3Z.png!mobile
/*
 * The linked list node
 */
struct LIST_ITEM_LSS_T
{   
    struct LIST_ITEM_LSS_T * pxNext; // 下一个
    struct LIST_ITEM_LSS_T  * pxPrevious; // 上一个
    
    /* 节点属性,(根据个人需求增减以下内容) */
    uint32_t xItemValue; // 记号值,一般用于排序
    void * pvOwner; // 挂钩,即携带的信息
    void * pvContainer; // 归属,即属于哪一个链表
};
typedef struct LIST_ITEM_LSS_T listItem_t;
  • root节点(链表点)
    • 存放链表的信息
    • 有一个 mini节点,用于驳接和定位(相当于位置校准点),不挂载如何东西,且简洁为妙
      • mini节点

        mini节点的记号值在双向链表中为最大值,因为最大是尾也是头。

        3IFFzi7.png!mobile
/* mini 节点结构体 */
struct LIST_MINI_ITEM_LSS_T
{
    /* 指向,(根据单、双链表删减以下内容) */
    struct node *pxPrevious; // 上一个节点
    struct node *pxNext; // 下一个节点
     /* 节点属性,(根据个人需求增减以下内容) */
    uint32_t xItemValue; // 记号值,在双向链表中为最大值
};
typedef struct LIST_MINI_ITEM_LSS_T ListMiniItem_t;

/* 链表结构体,即根节点 */
struct LIST_LSS_T
{
    /* 节点属性,(根据个人需求增减以下内容) \*/
    uint32_t uxNumberOfItems; // 节点数,统计粘附在本链表上的节点数
    struct LIST_ITEM_LSS_T * pxIndex; // 索引,链表索引,指向链表中的某个节点
    struct LIST_MINI_ITEM_LSS_T xListEnd; // 链表根节点
};
typedef struct LIST_LSS_T List_t;

链表操作的函数代码

链表初始化函数

  1. 链表索引指向该链表的 尾节点 (尾节点,即也是头节点)
  2. 链表 尾节点 记号值赋值为最大值( 根节点包含尾节点
  3. 初始化尾节点的 上一个下一个 ,分别都指向**尾节点
  4. 初始化节点总数为 0。
/**
  * @brief  链表初始化
  * @param 
  * @retval 
  * @author lzm
  */
void listInit(list_t * const list)
{
    /* 索引指向最后:尾就是头 */
    list->pxIndex = (listItem_t *) &(list->xListEnd);
    
    /* 链表最大值 */
    list->xListEnd.xItemValue = lssLIST_MAX_VALUE;
    
    list->xListEnd.pxNext = ( listItem_t * ) &( list->xListEnd );
    list->xListEnd.pxPrevious = ( listItem_t * ) &( list->xListEnd );
    
    list->uxNumberOfItems = (lssLIST_BASE_TYPE)0U;
}

节点初始化函数

  1. 初始化节点携带的信息为空
/**
  * @brief  节点初始化
  * @param 
  * @retval 
  * @author lzm
  */
void listItemInit(listItem_t * const item)
{
    item->pvContainer = NULL;
}

节点插入链表尾部函数

注意 :需要插入的节点以下称为 节点A

  1. 获取索引(索引即游标,也就是该链表当前指向的节点)
  2. 节点A下一个 指向 索引节点
  3. 节点A前一个 指向 索引节点前一个
  4. 索引节点前一个下一个 指向 节点A
  5. 索引节点前一个 指向 节点A
  6. 设置 节点A 归属于哪个链表
  7. 链表节点计数值 +1
/**
* @brief  插入链表尾部(*双向链表没有绝对的头尾,此处是以游标为参考物*)
* @param 
* @retval 
* @author lzm
*/
void listInsertEnd( list_t * const pxList, listItem_t * const pxNewListItem )
{
    listItem_t * const pxIndex = pxList->pxIndex;

    pxNewListItem->pxNext = pxIndex;
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;

    pxIndex->pxPrevious->pxNext = pxNewListItem;
    pxIndex->pxPrevious = pxNewListItem;

    /* Remember which list the item is in. */
    pxNewListItem->pvContainer = ( void * ) pxList;

    ( pxList->uxNumberOfItems )++;
}

节点有序插入链表函数

注意 :需要插入的节点以下称为 节点A

  1. 获取新节点记号值

    2.找出需要插入的位置

    1. 如果记号值为链表中最大值(即和 尾节点 的记号值相等),则取出尾节点的前一个节点作为参考节点
    2. 如果记号值 为链表中最大值,则从尾节点开始找,直至找到 当前节点下一个节点 的记号值为 大于 节点A 的记号值(即是在链表中找出红框节点B)
      u63Ejun.png!mobile
    3. 插入节点
      1. 节点A 节点A 为需要插入的节点)的 下一个 指向 索引节点
      2. 节点A前一个 指向 节点B前一个
      3. 节点B的前一个下一个 指向 节点A
      4. 节点B前一个 指向 节点A
      5. 设置 节点A 归属于哪个链表
      6. 链表节点计数值 +1
/**
* @brief  按记号值值插入
* @param 
* @retval 
* @author lzm
*/
void listInsert( list_t * const pxList, listItem_t * const pxNewListItem )
{
    listItem_t *pxIterator;
    const lssLIST_BASE_TYPE xValueOfInsertion = pxNewListItem->xItemValue; // 获取新节点记号值
    
    /* 按顺序寻找 */
    if( xValueOfInsertion == lssLIST_MAX_VALUE )
    {
          pxIterator = pxList->xListEnd.pxPrevious;
    }
    else
    {
          for( pxIterator = ( listItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
          {
                /* There is nothing to do here, just iterating to the wanted insertion position. */
          }
    }

    pxNewListItem->pxNext = pxIterator->pxNext;
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
    pxNewListItem->pxPrevious = pxIterator;
    pxIterator->pxNext = pxNewListItem;

    /* Remember which list the item is in.  This allows fast removal of the
    item later. */
    pxNewListItem->pvContainer = ( void * ) pxList;

    ( pxList->uxNumberOfItems )++;
}

从链表中删除函数

注意 :被删除的节点以下称为 节点A

  1. 获取链表
  2. 节点A下一个节点前一个 指向 节点A的前一个节点
  3. 节点A前一个节点下一个 指向 节点A的下一个节点
  4. 检查链表索引是否指向了 节点A
    1. 是:索引更新为指向 节点A的前一个节点
    2. 否:跳过
  5. 清空 节点A 的链表归属
  6. 链表节点计数值 -1
  7. 返回链表节点数
/**
* @brief  从链表中删除节点
* @param 
* @retval 
* @author lzm
*/
uint32_t listRemove( listItem_t * const pxItemToRemove )
{
/* The list item knows which list it is in.  Obtain the list from the list
item. */
    list_t * const pxList = ( list_t * ) pxItemToRemove->pvContainer;

    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

    /* Make sure the index is left pointing to a valid item. */
    if( pxList->pxIndex == pxItemToRemove )
    {
          pxList->pxIndex = pxItemToRemove->pxPrevious;
    }

    pxItemToRemove->pvContainer = NULL;
    
    ( pxList->uxNumberOfItems )--;

    return pxList->uxNumberOfItems;
}

源码集合

  • 内含
    • 节点及链表结构体
    • 节点初始化函数
    • 链表初始化函数
    • 节点插入函数
    • 删除节点函数
    • 配对挂钩与袜子函数
    • 获取节点信息函数
    • 获取记号值函数
    • 获取第一个节点的节点值函数
    • 获取链表的入口节点函数
    • 获取下一个节点函数
    • 获取链表最后一个节点(尾节点)函数
    • 判断链表是否为空函数
    • 获取链表当前节点总数函数
    • 获取链表索引指向的下一个节点函数
  • 跳转到 非通用链表完整C语言源码 即可

Recommend

  • 37
    • studygolang.com 6 years ago
    • Cache

    Go源码学习之双向链表

    双向链表的定义 双向链表也叫 双链表 ,是链表的一种,它的每个数据结点中都有两个

  • 30
    • www.tuicool.com 5 years ago
    • Cache

    Redis 双向链表:adlist

    总结 Redis 无环双向链表的实现。 函数指针 Redis 链表结构内置了三个指向操作函数的函数指针,先梳理下函数指针用法。 定义 函数体在编译期间会被存入进程代码段内存的一片连续区域中,而函数...

  • 39
    • www.tuicool.com 5 years ago
    • Cache

    Go实现双向链表

    本文介绍什么是链表,常见的链表有哪些,然后介绍链表这种数据结构会在哪些地方可以用到,以及 Redis 队列是底层的实现,通过一个小实例来演示 Redis 队列有哪些功能,最后通过 Go 实现一个双向链表。

  • 31
    • 微信 mp.weixin.qq.com 4 years ago
    • Cache

    数据结构:循环\双向链表

    一、回顾 单链表 是由 一段 连续或不连续 的物理地址来 存储元素 的。通过存储指针的方式,链表的每个结点中都储存了下一个结点元素的地址。...

  • 12

    JS实现数据结构(四):双向链表 单向...

  • 8
    • www.cxyxiaowu.com 3 years ago
    • Cache

    链式存储结构之双向链表与跳表

    链式存储结构之双向链表与跳表-五分钟学算法 当前位置:五分钟学算法 > 数据结构 > 链式存储结构之双向链表与跳表 ...

  • 6
    • segmentfault.com 3 years ago
    • Cache

    JZ-026-二叉搜索树与双向链表

    JZ-026-二叉搜索树与双向链表发布于 12 月 15 日二叉搜索树与双向链表输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不...

  • 9

    百图画鸿蒙 | 一图一主干 如果把鸿蒙比作人,百图目的是要画出其骨骼系统。 双向链表是内核最重要的结构体,说它怎么重要都不为过,其插入删除操作被内核高频,灵活的使用,若不理解透彻在分析源码过程中很容易卡壳...

  • 5

    使用线程安全型双向链表实现简单 LRU Cache 模拟

  • 6

    Redis 源码解析之通用双向链表(adlist) Redis源码中广泛使用 adlist(A generic doubly linked list),作为一种通用的双向链表,用于简单的数据集合操作。adlist提供了基本的增删改查能力,并支...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK