单向循环链表的初体验

单向循链表

单向循环链表经过小白今天一天的学习,感觉就是在单向链表的尾结点加了一个首结点的地址,不再指向NULL,个人理解就像一群孩子围成了一个圆,头尾相连,同时多少个孩子就是多少个结点,如击鼓传花一般一个个将手上的手绢传递给下一个人,一遍下来就像是单向循环的遍历,想要到谁的手上,就像是指定位置进行对链表的修改,删除呢就是某个人在游戏中被淘汰了,可以是头部,也可以是尾部,同时也可以是中间的某个人。由此的启发。
经过再次查看并测试,发现了仍有错误的地方,在遍历的代码中判断链表是否为空,为空时头结点指向本身,这里的判断出错了,我写的指向NULL,还有头删中在链表只有一个首结点的情况下进行处理,但没有退出,导致代码继续运行访问了保留区,发生段错误。
希望看完代码的你发现错误,请评论指正,非常感谢。
示意图如下:
image

新链表的初始化

创建结构体存储结点数据

  • /*****************************************************************
    *
    *      file name :one_way_circularLinkediIst
    *      authour   :yq_dyx@163.com
    *      date      :2024/04/26
    *      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 CircularLinkedList
    {
        DataType_t   data;           // 数据域
        struct CircularLinkedList *next;     // 指针域
    }CircLList_t;
    

    创建空链表

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

    //创建空链表以及头结点
    /*******************************************************************
    *
    *	name	 :	CircLList_crea
    *	function :  创建一个空链表并创建一个头结点,并对头结点进行初始化
    *	argument :  
    *	retval	 :  None
    *	author	 :  yq_dyx@163.com
    *	date	 :  2024/04/26
    * 	note	 :  None
    * 	
    * *****************************************************************/
    CircLList_t * CircLList_creat()
    {
        CircLList_t *head = (CircLList_t *)malloc(sizeof(CircLList_t));       //创建头结点
        if(NULL == head)
        {
            perror("calloc head is error\n");     // 分配内存失败
            exit(1);
        }
    
        head->next = head;   //头结点初始化,为表示循环所以指向自己
    }
    

    创建新结点

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

//创建新节点
/*******************************************************************
*
*	name	 :	CircLList_NewNode
*	function :  创建一新的链表节点,并对节点初始化
*	argument :  @data 新结点的数据域的数据
*	retval	 :  None
*	author	 :  yq_dyx@163.com
*	date	 :  2024/04/23
* 	note	 :  此函数不单独使用,需要配合Insert函数使用
* 	
* *****************************************************************/
CircLList_t * CircLList_NewNode(DataType_t data)
{
    CircLList_t *New =(CircLList_t *)malloc(sizeof(CircLList_t));
     if(NULL == New)
    {
        perror("calloc NewNode  is error\n");     // 分配内存失败
        return NULL;
    }

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

插入操作

头插

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

  • //头部插入
    /*******************************************************************
    *
    *	name	 :	CircLList_HeadInsert
    *	function :  节点在头部插入
    *	argument :  @data 要插入的结点的数据域的数据
    *	            @head 链表的头结点
    *	retval	 :  None
    *	author	 :  yq_dyx@163.com
    *	date	 :  2024/04/23
    * 	note	 :  要判断此链表是否为空,为空则直接在头结点后面插入,且此函数需要配合creat函数使用
    * 	
    * *****************************************************************/
    bool CircLList_HeadInsert(CircLList_t *head,DataType_t data)
    {
        CircLList_t *phead = head;                        //备份头文件,用作遍历寻找尾结点
        CircLList_t *New = CircLList_NewNode(data);       // 创建新节点, 并初始化
        if(NULL == New)                                   //判断新节点是否创建成功
        {
            printf("calloc NewNode is failed\n");
            return false;
        }
    
        if(head == head->next)                            //判断链表是否为空
        {    
            head->next = New;
            New->next = New;
            return true;
        }
        
        while(phead->next)
        {
            phead = phead->next;
            if(head->next == phead->next)                 //寻找尾结点,尾结点指向首结点
            break;
        }
    
        New->next = head->next;
        head->next = New;
        phead->next = New;
    
        return true;
    }
    

尾插

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

/*******************************************************************
*
*	name	 :	CircLList_TailInsert
*	function :  节点在尾部插入
*	argument :  @data 要插入的结点的数据域的数据
*	            @head 链表的头结点
*	retval	 :  None
*	author	 :  yq_dyx@163.com
*	date	 :  2024/04/23
* 	note	 :  要判断此链表是否为空,为空则直接在头结点后面插入,需要循环找到尾结点,尾结点指向首结点
* 	
* *****************************************************************/
bool CircLList_TailInsert(CircLList_t *head,DataType_t data)
{
    CircLList_t *phead = head;            // 备份头结点
    CircLList_t *New = CircLList_NewNode(data);            
    if(NULL == New)                                 //判断新节点是否创建成功
    {
        printf("calloc NewNode is failed\n");
        return false;
    }

    if(head == head->next)                         //判断链表是否为空,为空就加在头结点后
    {    
        head->next = New;
        New->next = New;
        return true;
    }

    while(phead->next)
    {
        phead = phead->next;
        if(head->next == phead->next)                 //寻找尾结点,尾结点指向首结点
        break;
    }
    
    phead->next = New;
    New->next = head->next;

    return true;
}

中间插入

当进行中间插入时需要考虑多种情况,比如链表为空,或者找不到数据,我们都需要考虑,也画了下图进行简单分析有利于理解
image

  • /*******************************************************************
    *
    *	name	 :	CircLList_DestInsert
    *	function :  结点在指定结点后插入
    *	argument :  @data   指定结点的数据域的数据
    *	            @head   链表的头结点
    *               @value  要插入的结点的数据域的数据
    *	retval	 :  None
    *	author	 :  yq_dyx@163.com
    *	date	 :  2024/04/23
    * 	note	 :  要判断此链表是否为空,为空则直接在头结点后面插入
    * 	
    * *****************************************************************/
    bool CircLList_DestInsert(CircLList_t *head,DataType_t data,DataType_t value)
    {
        CircLList_t *phead = head->next;     // 备份头结点
        CircLList_t *New = CircLList_NewNode(value);            
        if(NULL == New)                                 //判断新节点是否创建成功
        {
            printf("calloc NewNode is failed\n");
            return false;
        }
    
        if(head == head->next)                          //判断链表是否为空,为空就加在头结点后
        {    
            head->next = New;
            New->next = New;
            return true;
        }
       
        while(phead->next  && phead->data != data)    //再循环再次到首结点时退出,说明没有该数据   
        {
            if(phead->next == head->next)
            {
                printf("not found this data\n");
                return false;
            }
    
            phead = phead->next;       // 遍历链表,phead的地址改变,到下一个结点       
        }
    
        New->next = phead->next;                  //让尾结点把头结点的地址给新节点
        phead->next = New;                        //连接尾结点和新结点
    
        return true;
    }
    

删除操作

删除首节点

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

  • /*******************************************************************
    *
    *	name	 :	CircLList_HeadDelete
    *	function :  删除头部的结点
    *	argument :   @head   链表的头结点	           
    *	retval	 :  None
    *	author	 :  yq_dyx@163.com
    *	date	 :  2024/04/23
    * 	note	 :  要判断此链表是否为空,为空则直接退出,要找到尾结点,把新首结点的地址给尾结点
    * 	
    * *****************************************************************/
    bool CircLList_HeadDelete(CircLList_t *head)
    {
        CircLList_t *phead = head;                   // 备份头结点,用于循环遍历
        CircLList_t *p = head->next;                // 循环中用于备份首结点
        if(NULL == head->next)                         //判断链表是否为空,为空直接退出
        {    
            printf("linkedlist is empty\n");
            return false;
        }
    
        if(p->next == head->next)                  //当链表中只有一个首结点
        {
            p = NULL;
            head->next = head;
            free(p);
            return true;
        }
    
        while(phead->next)
        {
            phead = phead->next;
            if(head->next == phead->next)                 //寻找尾结点,尾结点指向首结点
            break;
        }
    
        head->next = p->next;
        phead->next = p->next;               //对删除的结点释放,防止内存泄漏,以及段错误
        p->next = NULL;
        free(p);
    
        return true;
    }
    

删除尾节点

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

/*******************************************************************
*
*	name	 :	CircLList_TailDelete
*	function :  删除尾部的结点
*	argument :   @head   链表的头结点	           
*	retval	 :  None
*	author	 :  yq_dyx@163.com
*	date	 :  2024/04/23
* 	note	 :  要判断此链表是否为空,为空则直接退出,要找到尾结点,把首结点的地址给新的尾结点
* 	
* *****************************************************************/
bool CircLList_TailDelete(CircLList_t *head)
{
    CircLList_t *phead = head;                       // 备份头结点,用于循环遍历
    CircLList_t *p = head->next;                    // 循环中用于备份phead
    if(NULL == head->next)                         //判断链表是否为空,为空直接退出
    {    
        printf("linkedlist is empty\n");
        return false;
    }

    if(p->next == head->next)                  //当链表中只有一个首结点
    {
        p = NULL;
        head->next = head;
        free(p);
    }
        
    while(phead->next)
    {
        p = phead;                                      //记录phead改变前的地址也就是当前结点
        phead = phead->next;                           // 遍历链表,phead的地址改变,到下一个结点 
        if(head->next == phead->next)                 //寻找尾结点,尾结点指向首结点
        break;
    }

    p->next = head->next;           //将新的尾结点连接首结点
    phead->next = NULL;
    free(phead);                   //对删除的结点释放,防止内存泄漏,以及段错误

    return true;
}

中间删除指定结点

找到指定结点,但需要考虑多种情况,比如链表就一个首结点,链表为空,指定结点为首结点或者尾结点,简单图示如下:
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)                     //当目标结点为首结点
        {
     
    

打印操作

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

  • /*******************************************************************
    *
    *	name	 :	CircLList_Print
    *	function :  遍历所有的结点,并打印结点的数据域
    *	argument :   @head   链表的头结点	           
    *	retval	 :  None
    *	author	 :  yq_dyx@163.com
    *	date	 :  2024/04/23
    * 	note	 :  要判断此链表是否为空,为空则直接退出,要找到尾结点,同时要把尾结点的数据域打印
    * 	
    * *****************************************************************/
    bool CircLList_Print(CircLList_t*head)
    {
        CircLList_t *phead = head;      // 备份头结点
        if(head == head->next)                         //判断链表是否为空,为空直接退出
        {    
            printf("linkedlist is empty\n");
            return false;
        }
        while(phead->next)
        {
            phead = phead->next;       // 遍历链表
            if(head->next == phead->next)
            {
               printf(" %d",phead->data);      // 打印链表 
               printf("\n");
               return false;
            }
            printf(" %d",phead->data);      // 打印链表
            
        }
        
        
        return true;
    }
    

主函数测试

int main()
{
    CircLList_t * Head =  CircLList_creat();
    CircLList_HeadInsert(Head,8);
    CircLList_HeadInsert(Head,5);
    CircLList_HeadInsert(Head,3);
    CircLList_HeadInsert(Head,7);
    CircLList_Print(Head);  //测试结果:7 3 5 8

    CircLList_TailInsert(Head,42);
    CircLList_TailInsert(Head,99);
    CircLList_TailInsert(Head,87);
    CircLList_TailInsert(Head,63);
    CircLList_Print(Head);  //测试结果:7 3 5 8 42 99 87 63

    CircLList_DestInsert(Head,42,11);
    CircLList_DestInsert(Head,3,33);
    CircLList_DestInsert(Head,87,61);
    CircLList_Print(Head);  //测试结果:7 3 33 5 8 42 11 99 87 61 63 


    CircLList_DestDelete(Head,42);
    CircLList_Print(Head);
    CircLList_HeadDelete(Head);
    CircLList_Print(Head);
    CircLList_TailDelete(Head);
    CircLList_Print(Head);  //测试结果: 3 33 5 8 11 99 87 61            
    return 0;
}

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

希望以上的代码能对同是初学者的你有所帮助,一起加油吧,小白们

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