双向链表

双向链表

小白感觉双向链表和单向链表的区别并不大,就是地址的交接有点繁琐,需要清晰的逻辑,简单理解就是俩条平行线,无线延伸,但是俩个线不交叉,但都是在一张纸上开始延展,头结点就像这张纸,理解的可能有点抽象,但我感觉这就是个抽象的概念,根据如此特画出下图进行分析:

image

新链表的初始化

创建结构体存储结点数据

  • /*****************************************************************
    *
    
    *      file name :DoubleLinkedList.c
    *      authour   :yq_dyx@163.com
    *      date      :2024/04/23
    *      function  :设计一个函数,实现对双向链表的增删改查的功能。
    *      note      :None
    *      CopyRight (c)   2024   yq_dyx@163.com   All Right Reseverd
           *
           ******************************************************************/
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    
    typedef int DataType_t;
    
    //定义一个链表结构体,分别存储数据域和指针域
    typedef struct doubleLinkedList
    {
        DataType_t   data;                 // 数据域
        struct doubleLinkedList *next;     // 指针域
        struct doubleLinkedList *prev;
    }doubLList_t;
    

    创建空链表

    创建一个空的链表和头结点,并进行初始化

    /*******************************************************************
    *
    
    *  name	 :	doubleLinkedList_creat
    
    *  function :  创建一个空链表并创建一个头结点,并进行初始化
    
    *  argument :  
    
    *  retval	 :  None
    
    *  author	 :  yq_dyx@163.com
    
    *  date	 :  2024/04/22
    
    *   note	 :  None
    
    *   
    
    * *****************************************************************/
      //创建空链表,并创建一个头结点
      doubLList_t * doubleLinkedList_creat()
      {
        doubLList_t *head = (doubLList_t *)calloc(1,sizeof(doubLList_t));  //calloc来申请不需要初始化更方便
        if(NULL == head)
        {
            perror("calloc head  is error\n");     // 分配内存失败
            exit(1);
        }
    
        head->next = NULL;   //头结点next初始化
        head->prev = NULL;   //头结点prev初始化
      }
    

    创建新结点

    在添加结点时候必然要创建新的结点,再进行添加,代码如下:

/*******************************************************************
*

*  name	 :	doubleLinkedList_NewNode

*  function :  创建一新的双向链表结点,并对新结点初始化

*  argument :  @data 新结点的数据域的数据

*  retval	 :  None

*  author	 :  yq_dyx@163.com

*  date	 :  2024/04/23

*   note	 :  此函数不单独使用,需要配合Insert函数使用

*   

* *****************************************************************/
  doubLList_t * doubleLinkedList_NewNode(DataType_t data)
  {
    doubLList_t *New =(doubLList_t *)malloc(sizeof(doubLList_t));
     if(NULL == New)
    {
        perror("calloc NewNode  is error\n");     // 分配内存失败
        return NULL;
    }

    New->data = data;  //新结点数据域初始化
    New->next = NULL;  //新结点指针域next初始化
    New->prev = NULL;  //新结点指针域prev初始化
  }

插入操作

头插

当在头部进行插入新结点动作的时候,可以先画出下图,方便更好的理解
image

  • /*******************************************************************
    *
    
    *  name	 :	doubleLinkedList_HeadInsert
    
    *  function :  结点在头部插入
    
    *  argument :  @data 要插入的结点的数据域的数据
    
    *    @head 链表的头结点
    
    *  retval	 :  None
    
    *  author	 :  yq_dyx@163.com
    
    *  date	 :  2024/04/23
    
    *   note	 :  要判断此链表是否为空,为空则直接在头结点后面插入
    
    *   
    
    * *****************************************************************/
      bool doubleLinkedList_HeadInsert(doubLList_t *head,DataType_t data)
      {
        doubLList_t *New = doubleLinkedList_NewNode(data);       
        if(NULL == New)                                         //判断新节点是否创建成功
        {
            printf("calloc NewNode is failed\n");
            return false;
        }
    
        if(NULL == head->next)                         //判断链表是否为空,则直接插入
        {    
            head->next = New;
            return true;
        }
    
        New->next = head->next;
        head->next->prev = New;
        head->next = New;
    
        return true;
      }
    

尾插

当选择在尾部插入的时候,我们先画出下图,这样更有利于我们理解
image

/*******************************************************************
*

*  name	 :	doubleLinkedList_TailInsert

*  function :  结点在尾部插入

*  argument :  @data 要插入的结点的数据域的数据

*    @head 链表的头结点

*  retval	 :  None

*  author	 :  yq_dyx@163.com

*  date	 :  2024/04/23

*   note	 :  要判断此链表是否为空,为空则直接在头结点后面插入,尾结点指向NULL

*   

* *****************************************************************/
  bool doubleLinkedList_TailInsert(doubLList_t *head,DataType_t data)
  {
    doubLList_t *phead = head;                                // 备份头结点
    doubLList_t *New = doubleLinkedList_NewNode(data);       
    if(NULL == New)                                         //判断新节点是否创建成功
    {
        printf("calloc NewNode is failed\n");
        return false;
    }

    if(NULL == head->next)                         //判断链表是否为空,则直接插入
    {    
        head->next = New;
        return true;
    }

    while(phead->next)
    {
        phead = phead->next; // 遍历链表   
    }

    phead->next = New;
    New->prev = phead;

    return true;
  }

中间插入

当进行中间插入时需要考虑多种情况,比如就一个首结点,目标结点在头结点,目标结点在尾结点,我们都需要考虑,也画了下图进行简单分析有利于理解
image

  • /*******************************************************************
    *
    
    *  name	 :	doubleLinkedList_DestInsert
    
    *  function :  结点在指定位置后插入
    
    *  argument :  @data  指定结点的数据域的数据
    
    *    @value 要插入新的结点的数据域的数据
    
    *    @head  链表的头结点
    
    *  retval	 :  None
    
    *  author	 :  yq_dyx@163.com
    
    *  date	 :  2024/04/23
    
    *   note	 :  None
    
    *   
    
    * *****************************************************************/
      bool doubleLinkedList_DestInsert(doubLList_t *head,DataType_t data,DataType_t value)
      {
        doubLList_t *phead = head;             // 备份头结点
        doubLList_t *p = head;                // 备份头结点
        doubLList_t *New = doubleLinkedList_NewNode(value);     
        if(NULL == New)                                         //判断新节点是否创建成功
        {
            printf("calloc NewNode is failed\n");
            return false;
        }
    
        if(NULL == head->next)                         //判断链表是否为空,则直接插入
        {    
            head->next = New;
            return true;
        }
    
        while(phead->next  && phead->data != data)    //再循环再次到尾巴结点时退出,说明没有该数据   
        {
            phead = phead->next;       // 遍历链表,找到和data相同的节点时(phead)退出,    
        }
        //头插
        if(phead == NULL && phead->data != data)              //当phead == NULL导致退出时,则没找该data,函数直接退出
        {
            printf("not found this data\n");
            return false;       
        }
        //如果遍历链表发现目标结点,分为三种可能(头插,尾插,中间插),头插和中间插入相同
        //头插和中间插入
        if(phead->data == data)
        {
        New->next = phead->next;         //将指定插入结点的直接后继的地址给新节点的next
        phead->next->prev = New;         //将New的地址给指定插入结点的直接后继的prev
        phead->next = New;               //将新节点的地址给指定插入结点的next         
        New->prev = phead;               //将指定插入结点的地址给新节点的prev
        }
        //尾插
        else if(phead->next == NULL)
        {
            phead->next = New;
            New->prev = phead;
        }
    
        return true;
      }
    

删除操作

删除首节点

对于删除操作咱们先进行头部删除,尾部删除和中间指定删除

image

  • /*******************************************************************
    *
    
    *  name	 :	doubleLinkedList_HeadDelete
    
    *  function :  删除首结点
    
    *  argument :  @data  指定结点的数据域的数据
    
    *    @head  链表的头结点
    
    *  retval	 :  None
    
    *  author	 :  yq_dyx@163.com
    
    *  date	 :  2024/04/23
    
    *   note	 :  None
    
    *   
    
    * *****************************************************************/
      bool doubleLinkedList_HeadDelete(doubLList_t *head)
      {
        if(NULL == head->next)                         //判断链表是否为空,为空直接退出
        {    
            printf("doubleLinkedList is empty\n");
            return false;
        }
    
        doubLList_t *phead = head->next;      // 备份首结点
    
        head->next = phead->next;          //将首结点的直接后继地址给头结点
        phead->next->prev = head;          //将头结点的地址给首结点的直接后继的prev
        phead->next = NULL;               //对删除的结点释放,防止内存泄漏,以及段错误
        free(phead);
    
        return true;
      }
    

删除尾节点

删除尾结点较为简单,操作不复杂,主要就是断开就好,就像下图所示:

image

/*******************************************************************
*

*  name	 :	doubleLinkedList_TailDelete

*  function :  删除尾结点

*  argument :  @data  指定结点的数据域的数据

*  retval	 :  None

*  author	 :  yq_dyx@163.com

*  date	 :  2024/04/23

*   note	 :  None

*   

* *****************************************************************/
  bool doubleLinkedList_TailDelete(doubLList_t *head)
  {
    int i = 0;
    if(NULL == head->next)                         //判断链表是否为空,为空直接退出
    {    
        printf("doubLinkedList is empty\n");
        return false;
    }

    doubLList_t *phead = head;     // 备份头结点
    doubLList_t *p = head;                // 备份头结点

    while(phead->next)
    {
        phead = phead->next;       // 遍历链表              
    }

    phead->prev->next = NULL;          //尾结点的直接前驱的next指向NULL,断开最后一个节点
    phead->prev = NULL;               //尾结点的prev指向NULL,与直接前驱断开
    free(phead);                     //对删除的结点释放,防止内存泄漏,以及段错误

    return true;
  }



### 删除指定节点

<img src="111.assets/image-20240425103226652.png" alt="image-20240425103226652" style="zoom:150%;" />

中间删除指定结点

找到指定结点,但需要考虑多种情况,比如链表就一个首结点,链表为空,指定结点为首结点或者尾结点,简单图示如下:

image

  • /*******************************************************************
    *
    
    *  name	 :	doubleLinkedList_DestDelete
    
    *  function :  删除指定位置的结点
    
    *  argument :  @data  指定结点的数据域的数据
    
    *    @head  链表的头结点
    
    *  retval	 :  None
    
    *  author	 :  yq_dyx@163.com
    
    *  date	 :  2024/04/23
    
    *   note	 :  None
    
    *   
    
    * *****************************************************************/
      bool doubleLinkedList_DestDelete(doubLList_t *head,DataType_t data)
      {
        if(NULL == head->next)                         //判断链表是否为空,为空直接退出
        {    
            printf("doubLinkedList is empty\n");
            return false;
        }
    
        doubLList_t *phead = head->next;     // 备份头结点
        doubLList_t *p = head;                // 备份头结点
    
        while(phead->next  && phead->data != data)    //再循环再次到尾巴结点时退出,说明没有该数据   
        {
            phead = phead->next;       // 遍历链表,找到和data相同的节点时(phead)退出,    
        }
    
        if(phead->next == NULL && phead->data != data)              //当phead == NULL导致退出时,又没找该data,函数直接退出
        {
            printf("not found this data\n"); 
            return false;    
        }
    
        //头部删除
        if(phead == head->next)                     //当目标结点为首结点
        {
            head->next = phead->next;            //更新头结点,让头结点连上头结点直接后继
            if(phead->next != NULL)               //当只有一个首结点的时候
            {
                phead->next->prev = NULL;            //让新的首结点的prev指向NULL
                phead->next = NULL;                  //让首结点的next直接指向NULL
            }
    
      ​      free(phead);                         //释放删除的结点
        }
        //尾部删除
        else if(phead->next == NULL)              //当phead == NULL导致退出时,找到该data
        {
      ​      phead->prev->next = NULL;        //尾结点的直接前驱的next指向NULL
      ​      phead->prev = NULL;              //尾结点的prev指向NULL
      ​      free(phead);                     //释放删除的结点
        }
    
        else                                    //当删除中间结点的时候
        {
            phead->prev->next = phead->next;    //将指定的结点的next赋值给指定结点的直接前驱的next
            phead->next->prev = phead->prev;     //对删除的结点释放,防止内存泄漏,以及段错误
            phead->next = NULL;                  //将指定删除结点的next指向NULL;
            phead->prev = NULL;                  //将指定删除结点的prev指向NULL;
            free(phead);
        }
    
        return true;
      }
    

打印操作

对于整个链表进行遍历,当然也有不同情况要考虑,链表为空等等,切尾结点也需要打印

  • /*******************************************************************
    *
    
    *  name	 :	doubleLinkedList_Print
    
    *  function :  遍历双向链表,并打印各结点的数据域
    
    *  argument :  @data  指定结点的数据域的数据
    
    *  retval	 :  None
    
    *  author	 :  yq_dyx@163.com
    
    *  date	 :  2024/04/23
    
    *   note	 :  None
    
    *   
    
    * *****************************************************************/
      bool doubleLinkedList_Print(doubLList_t *head)
      {
        doubLList_t *phead = head;      // 备份头结点
    
        if(NULL == head->next)                         //判断链表是否为空,为空直接退出
        {    
            printf("doubLinkedList is empty\n");
            return false;
        }
        while(phead->next)
        {
            phead = phead->next;       // 遍历链表
            printf(" %d",phead->data);      // 打印链表
            
        }
        printf("\n");
        return true
      }
    

主函数测试

int main()
{
    doubLList_t * Head =  doubleLinkedList_creat();
    doubleLinkedList_HeadInsert(Head,8);
    /*
    doubleLinkedList_HeadInsert(Head,5);
    doubleLinkedList_HeadInsert(Head,3);
    doubleLinkedList_HeadInsert(Head,7);
    doubleLinkedList_Print(Head);  //7 3 5 8

​    doubleLinkedList_TailInsert(Head,42);
​    doubleLinkedList_TailInsert(Head,99);
​    doubleLinkedList_TailInsert(Head,87);
​    doubleLinkedList_TailInsert(Head,63);
​    doubleLinkedList_Print(Head);  //7 3 5 8 42 99 87 63

​    doubleLinkedList_DestInsert(Head,42,11);
​    doubleLinkedList_DestInsert(Head,3,33);
​    doubleLinkedList_DestInsert(Head,87,61);
​    doubleLinkedList_Print(Head);  //7 3 33 5 8 42 11 99 87 61 63 


​    doubleLinkedList_DestDelete(Head,42);
​    doubleLinkedList_Print(Head);//
​    doubleLinkedList_HeadDelete(Head);
​    doubleLinkedList_Print(Head);
​    doubleLinkedList_TailDelete(Head);
​    doubleLinkedList_Print(Head);// 3 33 5 8 11 99 87 61  

​    doubleLinkedList_DestDelete(Head,3);*/
​    doubleLinkedList_DestDelete(Head,8);
​    doubleLinkedList_Print(Head);// 33 5 8 11 99 87   

​    return 0;
}

已经经过测试验证并成功,测试结果如代码的测试结果一致,如图:

image

虽然和上次的单向循环链表结果相同,主要是我测试的数据一样,嘿嘿嘿,偷了个懒,还有不少作业要做呢,希望以上的代码能对同是初学者的你有所帮助

posted @ 2024-04-23 21:53  不懂小白在线记录  阅读(20)  评论(0)    收藏  举报