数据结构的学习(三)---双向链表与循环链表

目录

一、双向链表

1.创建链表:

2.头插法:

3.尾插法:

4.打印链表:

5.头删法:

6.尾删法:

7.查找删除:

二、循环链表(难)

1.创建链表:

2.头插(难):

3.打印:

4.头删法:

5.查找:

6.销毁:


基于上一篇的单向链表学习后,现在来学习双向链表,很多算法都是一样的,只是双向处理多了一个指针,把单向链表的各个算法搞清楚,双向和循环自然好理解

一、双向链表

1.创建链表:

结构体比单项多了一个指针

复制代码
node_t *creat_doublist(void)
{
    node_t *head = malloc(sizeof(node_t));//堆上开空间
    if(head ==NULL)//创建失败
    {
        printf("malloc fail");
        return NULL;
    }
    head->next =NULL;//创建成功,此时是空链表
    head->prev  =NULL;
    return head;
}

2.头插法:

堆上开好空间后,先把新节点的前后指针链接好后,再断开头节点和新节点后面节点的链接

注意:需要判断新节点后面是否还有节点

复制代码
//头插法
void doublist_insert_head(node_t *phead,data_t data)//返回结构体类型的指针
{
    //step1:创建一个新节点并给值
    node_t *pnew = malloc(sizeof(node_t));//堆上开空间
    if(pnew ==NULL)//创建失败
    {
        printf("malloc fail");
        return ;
    }
    pnew->prev = NULL;
    pnew->next = NULL;
    pnew->data =data; 

//插入新节点
    pnew->next = phead->next;
    pnew->prev = phead;

//断开之前的链接,指向新节点                                                  
    phead->next= pnew;
    if(pnew->next !=NULL)
        pnew->next->prev = pnew;
}

3.尾插法:

尾插只需要遍历到尾节点链接就行

复制代码
//尾插法
void doublist_insert_tail(node_t *phead,data_t data)//返回结构体类型的指针
{
    //step1:创建一个新节点并给值
    node_t *pnew = malloc(sizeof(node_t));//堆上开空间                         
    if(pnew ==NULL)//创建失败
    {
        printf("malloc fail");
        return ;
    }
    pnew->prev = NULL;
    pnew->next = NULL;
    pnew->data =data; 
    
    node_t *p = phead;
    while(p->next !=NULL )
    {
        p = p->next;
    }
    pnew->next = p->next;
    pnew->prev = p;
    p->next = pnew;
}

4.打印链表:

和单向链表差不多,多了一个可以逆序打印的功能:

复制代码
//打印链表
int printf_doublist(node_t *phead,int dir)                
{
    if(is_empty(phead)==1)//检查头节点指针域是否为空
    {
        printf("empty!\n");
        return -1;
    }
    node_t *p = phead->next;
    if(dir ==1)
    {
    while(p!=NULL)//节点到达空地址时结束,
        //注意不是节点的指针域为空的时候结束
        {
            printf("%d ",p->data);
            p = p->next;
        }
    }else
    {
        while(p->next!=NULL)
        {
            p = p->next;
        }
        while(p!= phead)
        {
            printf("%d ",p->data);
            p = p->prev;
        }
    }
    putchar('\n');
    return 0;
}

5.头删法:

先链接头和首节点的下一个节点,再删除首节点

注意:需要判断首节点的后面是否还有节点

6.尾删法:

遍历到最后一个节点之后,将它的前一个节点的指针域置为NULL即可

复制代码
//尾删法
int doublist_delete_tail(node_t *phead)
{
    if(is_empty(phead)==1)//检查头节点指针域是否为空
    {
        return -1;
    }
    node_t *p = phead->next;
    while(p->next!= NULL)
    {
        p = p->next;
    }
        p->prev->next = NULL;
        free(p);
                                                      
    return 0;
}

7.查找删除:

现在遍历找到,在循环里面判断找值,找到后使用头删法

复制代码
//查找删除
int doublist_delete_key(node_t *phead,data_t k)
{
    if(is_empty(phead)==1)//检查头节点指针域是否为空
    {
        return -1;                                         
    }
    node_t *p = phead->next;
        
    while(p!= NULL)
    {
        if(p->data == k)
        {
            p->prev->next = p->next ;
            if(p->next !=NULL)
                p->next->prev = p->prev;
            free(p);
            return 0;
        }
        p = p->next;
    }
    return -1;
}

销毁、长度计算、查找和修改数值算法与单向链表完全一致,这里不再说明。

二、循环链表(难)

和单向链表相同,但尾节点指针域指向首节点

算法需要用到do-while

1.创建链表:

头节点的指针域指向自己

复制代码
node_t *creat_circlist(void)
{
    node_t *head = malloc(sizeof(node_t));//堆上开空间
    if(head ==NULL)//创建失败
    {
        printf("malloc fail");
        return NULL;
    }else
    {
        printf("success!\n");
    }
    head->next =head;//创建成功,此时是空链表
    return head;
}

2.头插(难):

开堆后,把头节点与新节点相连,再把末尾节点的指针域链接到新节点

难点:

1.末尾节点需要定义一个指针去遍历链表找到它

2.链表只有单个节点时需要把指针域指向自己

复制代码
//头插法

node_t *insert_circlist_head(node_t * phead,data_t data)
{
    if(phead ==NULL)
    {
        return NULL;
    }
    node_t *pnew = malloc(sizeof(node_t));//堆上开空间
    if(pnew ==NULL)//创建失败
    {
        printf("malloc fail");
        return NULL;
    }
    pnew->next = NULL;
    pnew->data =data; 

    //链接
    pnew->next = phead->next;
    node_t *p = phead->next;
    while(p->next != phead->next)
    {
        p = p->next;
    }
        p->next = pnew;
        if(pnew->next ==phead)                                    
    {
        pnew->next = pnew;
    }
        return pnew;
}

3.打印:

和单向链表打印一样,只是循环结束条件变为p遍历到首届点,

难点:

需要用到do-while,如果使用while,遍历到最后之后无法再打印数据,而是跳出循环

复制代码
//打印
void printf_circlist(node_t *phead)
{
    if(is_empty(phead)==1  || is_empty(phead) == -1 )
    {
        printf("empty\n");
        return ;
    }

    node_t *p = phead->next;
    do
    {
        printf("%d ",p->data);
        p = p->next;
    }while(p!= phead->next);//用do-while可以先执行再判断
    
    putchar('\n');
    return;
}                                                          

4.头删法:

需要用到两个指针,一个是首节点,一个需要遍历找到尾节点, 链接头节点的指针域和尾节点的指针域,断开首节点即可

易错点:先后顺序一定不能搞错

复制代码
void circlist_delete_head(node_t *phead)
{
    if(is_empty(phead)==1  || is_empty(phead) == -1 )
    {
        return ;
    }
    node_t *p = phead->next;
    node_t *ptemp = phead->next;
    do
    {
        p = p->next;
    }while(p->next != phead->next);//用do-while先执行再判断   
    
    phead->next = ptemp->next;
    p->next = ptemp->next;
    free(ptemp);
    return ;
}

5.查找:

和单向链表一样,只是跳出循环的条件变了

复制代码
node_t *circlist_find(node_t *phead,data_t k)
{
    if(is_empty(phead)==1  || is_empty(phead) == -1 )
    {
        return NULL;
    }
    node_t *p = phead->next;
    do
    {
        if(p->data == k)
        {
            return p;
        }
        p = p->next;
    }while(p!= phead->next);//用do-while先执行再判断
    return NULL;                                                

}

6.销毁:

要从首节点的下一个节点开始删,循环删完后需要再free掉这个最后的节点,同时不要忘记free头节点,和头节点指针域置为NULL

易错点:忘记处理头节点

复制代码
//销毁
void circlist_destory(node_t **phead)
{
    if(is_empty(*phead)==1  || is_empty(*phead) == -1 )
    {
        return ;
    }
    node_t *p = (*phead)->next->next;
    do{
        node_t *ptemp = p;
        p = p->next;
        free(ptemp);
    }while(p->next != (*phead)->next);
    free(p);
    free(*phead);                                       
    *phead = NULL;
    return;
}
相关推荐
小雨笙笙42 分钟前
机器学习:线性回归学习
学习·机器学习·线性回归
HugoStudio_SWAN1 小时前
洛谷 P1420 / P1179 / B4262 最长连号、数字统计与词频统计——统计的三种面孔
c++·学习·程序人生·算法
是隼人1 小时前
buuctf-pwn [HarekazeCTF2019]baby_rop2(64位ret2libc)题解(学习过程持续更新)
c语言·学习·安全·pwn入门·ctf入门
aprilaaaaa3 小时前
(TC397 学习笔记)二、ICU和TIM的时钟计算
笔记·学习·fpga开发
wuyk5553 小时前
107.FreeRTOS 链表深度解析:从原理到面试满分答案
c语言·开发语言·数据结构·stm32·单片机·链表·面试
-dzk-3 小时前
【链表】LC 138.随机链表的复制
数据结构·链表
xian_wwq4 小时前
【学习笔记】-深度认知系列-第8讲主流AI模型横向评测——GPT、Claude、Gemini、盘古、文心、通义
人工智能·笔记·学习
大牧师4 小时前
TypeORM 学习教程
数据库·sql·学习·node.js·orm·nest.js·typeorm
洋不写bug4 小时前
链表补充练习,双链表的模拟实现
java·数据结构·链表·双链表·底层实现
白狐_7985 小时前
408 数据结构|外部排序:流程与 k 路归并
数据结构·算法