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

目录

一、双向链表

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;
}
相关推荐
简佐义的博客2 小时前
跟着Nature学习如何联合多组学snRNA-seq + snATAC-seq + WGS+空间转录组分析重构肿瘤亚克隆演化树
学习·重构
im_AMBER2 小时前
Leetcode 112 两数相加 II
笔记·学习·算法·leetcode
卡兰芙的微笑2 小时前
编译鸿蒙6.0release版本出错
学习
近津薪荼2 小时前
优选算法——滑动窗口3(子数组)
c++·学习·算法
遨游xyz2 小时前
数据结构-栈
java·数据结构·算法
FPGA小迷弟2 小时前
基于FPGA实现HDMI接口,选型/核心技术
学习·fpga开发·verilog·fpga·modelsim
新时代牛马2 小时前
CANopenNode 接口及 CANopenLinux 完整实现
网络·学习
2501_943695332 小时前
高职大数据运维与管理专业,怎么学习Hadoop的基础操作?
大数据·运维·学习
落羽的落羽2 小时前
【Linux系统】文件IO:理解文件描述符、重定向、缓冲区
linux·服务器·开发语言·数据结构·c++·人工智能·机器学习