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

目录

一、双向链表

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;
}
相关推荐
CSharp精选营4 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
刘马想放假7 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
北域码匠8 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
Darling噜啦啦15 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
通信小呆呆16 天前
当算法有了“五感”:多模态数据融合如何向人体感官协同学习?
人工智能·学习·算法·机器学习·机器人
H__Rick16 天前
自动对焦学习-3
人工智能·学习·计算机视觉
Daisy Lee16 天前
量化学习-第1章-什么是量化金融
学习·金融·datawhale
小小工匠16 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
Alsn8616 天前
等待学习-学习目录:Docker 容器安全攻防
学习·安全·docker
玖玥拾16 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器