手写一个双向链表是什么体验

单向链表:链表中有n个节点,前一个节点指向后一个节点,首尾相连

 

节点本身必须包含一个节点指针,用于指向后一个节点。

节点本身是一个自定义类型的数据结构

struct node
{
struct node *next; /* 指向链表的下一个节点 */
char data1; /* 单个的数据 */
unsigned char array[]; /* 数组 */
unsigned long *prt /* 指针数据 */
struct userstruct data2; /* 自定义结构体类型数据 */
/* ...... */
}

上面的数据结构中,除了struct node *next外,其他的都可以理解为节点携带的数据。但是一般不会这么,一般是在节点里面只包含一个用于指向下一个节点的指针。通过链表存储的数据内嵌一个节点,存储的数据通过内嵌的节点挂载到链表中。

理解:就是在一个要存储的数据结构中添加一个节点,然后通过这个节点将数据挂载到链表中

1 /* 节点定义 */
2 struct node
3 {
4 struct node *next; /* 指向链表的下一个节点 */
5 }
6//用户数据结构
7 struct userstruct
8 {
9 /* 在用户数据结构体中,内嵌一个节点指针,通过这个节点将数据挂接到链表 */
10 struct node *next;
11 /* 各种各样......,要存储的数据 */
12 }

 

双向链表:与单链表的区别是节点还在那个有两个节点指针,分别指向前后两个节点其他的完全一样

 

FreeRTOS中链表的实现

struct xLIST_ITEM
{
   TickType_t xItemValue;              //辅助值,用于帮助节点做顺序排列
   struct xLIST_ITEM *pxNext;          //指向下一个节点
   struct xLIST_ITEM *pxPrevious;      //指向前一个节点
   void *pvOwner;                      //指向拥有该节点的内核对象通常是TCB
   void *pvContainer;                  //指向该节点所在的链表
};

typedef struct xLIST_ITEM ListItem_t;   //节点类型重定义

 

struct xMINI_LIST_ITEM
{
  TickType_t xItemValue;             //辅助值,用于帮助节点做升序排列
  struct xLIST_ITEM *pxNext;         //指向下一个节点
  struct xLIST_ITEM *pxPrevious;     //指向前一个节点
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

//定义根节点数据结构(链表)
typedef struct xLIST
{
  UBaseType_t uxNumberOfItems;       //链表节点计数器
  ListItem_t *pxIndex;               //链表节点索引指针,用于遍历节点
  MiniListItem_t xListEnd;           //最后一个节点(也是第一个节点,生产者)
}List_t;

 

根节点初始化
void vListInitiallise(List_t * const pxList)
{
  //将链表索引指针指向最后一个节点
  pxList->pxIndex = (ListItem_t *)&(pxList->xListEnd);

  //将链表最后一个节点辅助值排序的值设为最大,确保该节点是链表的最后节点
  pxList->xListEnd.xItemValue = portMAX_DELAY;

  //将链表最后一个节点的pxNext和pxPrevious指针均指向节点自身,表示链表为空
  pxList->xListEnd.pxNext = &(pxList->xListEnd);
  pxList->xListEnd.pxPrevious = &(pxList->xListEnd);
  //初始化链表节点计数器的值为0,表示链表为空
  pxList->uxNumberOfItems = (UBaseType_t)0;
}

将链表插入节点的尾部(是将新的节点插入根节点的前一个节点),就是将一个新的节点插入到一个空的链表
void vListInserEnd(List_t * const pxList, ListItem_t * const pxNewListItem)
{
   //根节点,也就是最后一个节点
   ListItem_t * const pxIndex = pxList->pxIndex;
   //新节点的next node指向根节点
   pxNewListItem->pxNext = pxIndex;
   //新节点的pre node指向根节点的前一个节点
   pxNewListItem->pxPrevious = pxIndex->pxPrevious;
   //设置根节点的上一个节点的next node指向新节点
   pxIndex->pxPrevious->pxNext = pxNewListItem;
   //设置根节点的上一个节点指向新节点
   pxIndex->pxPrevious = pxNewListItem;
   //记住节点所在的链表
   pxNewListItem->pvContainer = (void *)pxList;
   //节点计数器加1
   pxList->uxNumberOfItems++;
}

 

插入前:
+----------+     +----------+     +----------+     +----------+
| xListEnd |<---> | 节点A   |<---> | 节点B   |<---> | xListEnd |
+----------+     +----------+     +----------+     +----------+

插入后:
+----------+     +----------+     +----------+     +----------+     +----------+
| xListEnd |<---> | 节点A   |<---> | 节点B   |<---> | NewItem |<---> | xListEnd |
+----------+     +----------+     +----------+     +----------+     +----------+

步骤:

//新节点的next node指向根节点
pxNewListItem->pxNext = pxIndex;
//新节点的pre node指向根节点的前一个节点
pxNewListItem->pxPrevious = pxIndex->pxPrevious;

1、设置NewItem的next指向 xListEnd,NewItem的previous指向节点B(也就是根节点的previous)

//设置根节点的上一个节点的next node指向新节点
pxIndex->pxPrevious->pxNext = pxNewListItem;
//设置根节点的上一个节点指向新节点
pxIndex->pxPrevious = pxNewListItem;

2、设置节点B指向NewItem:也就是 xListEnd的上一个节点的next指向新节点,然后在设置根节点的previous指向新节点

3、最后设置节点的所在的链表,以及节点计数器加一

将节点按照升序排列插入链表

//将节点按照升序排列插入到链表
void vListInsert(List_t * const pxList, ListItem_t * const pxNewListItem)
{
   ListItem_t *pxIterator;
   //获取节点的辅助值
   const TickType_t xValueOfInsertion =  pxNewListItem.xItemValue;
   //寻找节点要插入的位置
   if(xValueOfInsertion == portMAX_DELAY)
  {
       //如果新节点的辅助值是最大,那么新节点要插入的位置就是最后一个节点
       pxIterator = pxList->xListEnd.pxPrevious;
  }
   else
  {
       /*
           再次强化,根节点既是尾节点也是头节点,所以这里
           pxIterator->pxNext->xItemValue就是第一个节点的辅助值
           且这里的判断是pxIterator的下一个节点的辅助值,所以新节点永远在pxIterator后面
           依次遍历,找到新节点要插入的位置
       */
       for(pxIterator = (ListItem_t *)&(pxList->xListEnd);
           pxIterator->pxNext->xItemValue <= xValueOfInsertion;
           pxIterator = pxIterator->pxNext)
      {/*只是迭代找到节点要插入的位置*/}
  }
   //根据升序排列,将节点插入
   //是将新节点的next插入到当前节点的下一个节点
   pxNewListItem->pxNext = pxIterator->pxNext;
   //将后一个节点的pre设置为当前节点
   pxNewListItem->pxNext->pxPrevious = pxNewListItem;
   //设置新节点的pre为当前节点
   pxNewListItem->pxPrevious = pxIterator;
   //当前节点的next设置为新节点
   pxIterator->pxNext = pxNewListItem;

   //记住节点所在的链表
   pxNewListItem->pvContainer = (void *)pxList;
   //节点计数器加1
   pxList->uxNumberOfItems++;
}
xListEnd <-> 10 <-> 20 <-> 30 <-> xListEnd
将25节点插入上面列表
xListEnd <-> 10 <-> 20 <-> 25 <-> 30 <-> xListEnd
通过迭代会找到20对应的节点,然后将25插入到20和30之间
//将25节点的next设置为30,因为pxIterator表示的是20
pxNewListItem->pxNext = pxIterator->pxNext;
//将30节点的pre设置为25
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
//设置25节点的pre为20
pxNewListItem->pxPrevious = pxIterator;
//设置20节点的next为25
pxIterator->pxNext = pxNewListItem;

 

//将节点从链表中删除
UBaseType_t uxListRemove(ListItem_t * const pxItemToRemove)
{
    //获取节点所在的链表
    List_t * const pxList = (List_t *)pxItemToRemove->pvContainer;
    //将指定的节点从链表删除
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
    //调整链表的节点索引指针
    if(pxList->pxIndex == pxItemToRemove)
    {
        pxList->pxIndex = pxItemToRemove->pxNext;
    }
    //初始化该节点所在的链表为空,表示该节点没有插入链表
    pxItemToRemove->pvContainer = NULL;
    pxList->uxNumberOfItems--;
    return pxList->uxNumberOfItems;
}

 

 

posted @ 2025-05-29 17:16  lqxhgd  阅读(20)  评论(0)    收藏  举报