連結串列-雙向非通用連結串列

李柱明發表於2020-10-10


前言

  • 20201010
  • 在閱讀 RTOS LiteOS 核心原始碼時發現該核心使用的連結串列時通用連結串列,而 FreeRTOS 核心使用的時非通用連結串列,所以,有必要釋出一下關於連結串列實現的筆記。
  • 以下內容為個人筆記,涉及一些非專業詞彙,敬請諒解,謝謝。

連結

參考

  • 上面連結
  • FreeRTOS 核心原始碼
  • 野火

概念

  • 正常表達

    • 連結串列:
      • 連結串列為 C 中一種基礎的資料結構。
      • 看成環形晾衣架即可。
    • 節點:
      • 節點組成連結串列
  • 自理解概念

    • 連結串列:圓形的晾衣架
    • 節點:掛鉤
      • 包含上一個
      • 下一個
      • 鉤子等其它需要的資訊
    • 襪子:掛在到 鉤子 的東西
      • 包含被鉤子
      • 襪子攜帶的資訊
  • 通用連結串列與非通用連結串列的區別

    • 通用連結串列節點內容很少一般只有 上一個下一個
    • 通用連結串列節點被放到資訊結構體中,通過偏移找到所在的結構體(即是通過偏移找到襪子頭)
    • 而非通用連結串列是在節點中攜帶資訊結構體的指標的(即是節點就攜帶資訊)。
    • 別人通俗理解,讀者不必理會本小點
      • 通用連結串列是把襪子放到晾衣架的圓形圈上,襪子與圓形圈接觸部分為襪子接待的節點。(資訊攜帶節點
      • 非通用連結串列是。(節點攜帶資訊

筆錄草稿

雙向連結串列

  • 雙向連結串列理解圖

  • 原理:連結串列包括 根節點普通節點

    • 根節點 主要管理連結串列的,一般包括

      • 上一個
      • 下一個
      • 存在多少個等資訊
    • 普通節點 主要用於鉤住襪子(即是攜帶資訊)

節點及節點結構體程式碼

  • 普通節點
    • 存放節點資訊
    • 掛載東西(掛鉤),如掛載襪子等等
/*
 * 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 節點結構體 */
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)
    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語言原始碼即可

相關文章