数据结构篇-双向循环链表

2023-12-15 08:28:49

目录

一、学习目标

二、概念

节点设计:

节点初始化(链表初始化):

节点头插:

插入数据的变形:

遍历显示:

有序插入:

四、总结


一、学习目标

  • 知识点:
    • 一文掌握数据结构的双向循环链表
    • 通过打怪实战来提升自己的理解

二、概念

????????对链表而言,双向均可遍历是最方便的,另外首尾相连循环遍历也可大大增加链表操作的便捷性。因此,双向循环链表,是在实际运用中是最常见的链表形态。

节点设计:

// 数据域设计
typedef struct DataType
{
    int Num ;
    char Name[32];
}DataType;


// 节点设计
typedef struct Node
{
    // 数据域
    DataType Data;

    // 指针域
    struct Node * Prev ; // 前驱指针
    struct Node * Next ; // 后继指针
}Node , *P_Node ;

节点初始化(链表初始化):


P_Node NewNode( DataType * NewData )
{
    // 在堆内存中申请一个节点
    P_Node New = calloc(1 , sizeof(Node));

    // 数据域初始化
    if (NewData != NULL)
    {
        memcpy( &New->Data , NewData , sizeof(DataType) );
    }
    

    // 指针域初始化
    New->Next = New->Prev = New ;

    return New ;
}

节点头插:

void Add2Head(P_Node head , P_Node New )
{

    // 中心思想:先对新节点进行操作,然后再操作链表
    // 1. 让【新节点】的【后继指针】指向【第一个有效数据节点】
    New->Next = head->Next ;

    // 2. 让【新节点】的【前驱指针】指向头节点
    New->Prev = head ;

    // 3. 让链表的【头节点】的后继指针指向新节点
    head->Next = New; 

    // 4. 让【第一个有效数据节点】的前驱指针指向新节点
    New->Next->Prev = New ;

    return ;
}

插入数据的变形:

????????在一下示例中该函数即可以用于实现头插也可以实现用于尾插。

????????中心思想: 当需要把一个新数据插入到链表中的头节点的上一个位置称为尾差,反之插入下一个位置为头插。因此头插尾插的区别就在于把新数据插入到前方或后方的区别。

????????

????????得到结论:

????????????????把新数据节点插入到目标节点的前方为尾插,因此给一下函数传递的参数就可以写成 Add2List ( 前驱节点 , 新数据 , 目标节点 ) ;

????????????????把新数据节点插入到目标节点的后方为头插 , 因此给一下函数传递的参数就可以写成 Add2List ( 目标节点 , 新数据 , 后继节点 ) ;

void Add2List ( P_Node Prev ,  P_Node New , P_Node Next )
{
    Prev->Next = New ;
    New->Next = Next ;

    Next->Prev = New ;
    New->Prev = Prev ;
}

以上操作函数中的4个步骤不再需要关系先后顺序,因为每一个指针都是独立的,在操作过程中不会丢失。

遍历显示:

void DisplayList( P_Node head )
{

    for (P_Node tmp = head->Next ; tmp != head ; tmp = tmp->Next)
    {
        printf("[%d]%s\n" , tmp->Data.Num , tmp->Data.Name);
    }
}

有序插入:

void Add2List ( P_Node Prev ,  P_Node New , P_Node Next )
{
    Prev->Next = New ;
    New->Next = Next ;

    Next->Prev = New ;
    New->Prev = Prev ;
}


void Add2ListOrder( P_Node head , P_Node New )
{
    P_Node tmp = NULL ;
    for (tmp = head->Next ; tmp != head &&   // 条件1 tmp 不指向头节点
                    tmp->Data.Num < New->Data.Num ;  // 条件2 tmp 的数据小于 新数据的
                     tmp = tmp->Next );  // tmp 往后遍历
    
    Add2List( tmp->Prev , New , tmp );


}

打怪实战:

    • 理解现有代码
    • 尝试自行编写以上操作
    • 尝试实现链表的其他操作,比如增删改查
    • 【拓展】链表生成后使用其他关键字进行排序
    • 【拓展】思考双向循环链表的通用性

BOOS进阶:

    • 回顾本周所学,把不懂的标记出来尽快解决
    • 尝试自行编写双向循环链表的操作(增删改查从新排序等)
      • 【拓展】自行编写双向链表的通用性操作
    • 【拓展】 假设有两个双向循环链表L1,L2尝试编写一个函数用于合并他们,可以尝试完成一下两种合并需求:
      • 直接把L2连接到L1的尾部
      • 把L2有序地合并到L1中

四、总结

? ? ? ? 本文介绍了数据结构的双向循环链表的基础概念和操作,理解本文所有知识点后,便可打败其中的小怪,拿下经验值~

? ? ? ? 本文参照?粤嵌文哥?部分课件经整理和修改后发布在C站,如有转载,请联系本人

文章来源:https://blog.csdn.net/qq_51294997/article/details/134980939
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。