<返回更多

关于链表的理解

2022-10-07  CSDN  legendarykk
加入收藏
一:链表是什么

 

1、链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,有一系列结点(地址)组成,结点可动态的生成。

 

2、结点包括两个部分:(1)存储数据元素的数据域(内存空间),(2)存储指向下一个结点地址的指针域。

 

3、相对于线性表顺序结构,操作复杂。

 

4.链表分为 (1)单链表 (2)双链表 (3)单向循环链表 (4)双向循环链表

 

二:链表的作用

 

1、实现数据元素的存储按一定顺序储存,允许在任意位置插入和删除结点。

 

2、包括单向结点,双向结点,循环接点

 

三:链表与数组的区别

说到链表那肯定要聊一下数组,为什么会出现链表呢?

 

(1)数组:使用一块连续的内存空间地址去存放数据,但

 

例如:

int  a[5]={1,2,3,4,5}。突然我想继续加两个数据进去,但是已经定义好的数组不能往后加,只能通过定义新的数组

 

int b[7]={1,2,3,4,5,6,7};      ***********这样就相当不方便比较浪费内存资源,对数据的增删不好操作。

 

(2)链表:使用多个不连续的内存空间去存储数据, 可以 节省内存资源(只有需要存储数据时,才去划分新的空间),对数据的增删比较方便。

 

四、链表的优缺

优点:

(1)插入和删除速度快,保留原有的物理顺序,在插入或者删除一个元素的时候,只需要改变指针指向即可。(2)没有空间限制,存储元素无上限,只与内存空间大小有关。(3)动态分配内存空间,不用事先开辟内存

 

缺点:

(1)占用额外的空间以存储指针,比较浪费空间,不连续存储,malloc函数开辟空间碎片比较多) (2) 查找速度比较慢,因为在查找时,只能顺序查找,需要循环链表

 

五、关于头指针,头节点,首元节点的那些事

头指针:指向链表第一个节点的指针(不一定是头节点,因为……链表要是没有头节点呢?),没有实际开辟空间 (即没有用malloc等动态分配内存) 而且必须存在,因为头指针不存在,就不便于对链表进行操作。

 

头节点:不是必须存在(若存在则为链表的第一个节点)其主要作用是使所有链表(包括空表)的头指针非空,并使对单链表的插入、删除操作不需要区分是否为空表或是否在第一个位置进行(还是挺有用的)。其数据域可以不储存任何数据,若储存则通常是链表的长度啥的。

 

首元节点:第一个数据域中储存有效数据的节点 若头节点不存在 则其为链表的第一个节点,是一定要存在的(除非是空表)

 

有关链表的一些操作

1.单链表 

节点结构默认为:

 

struct ListNode

{

int val;

struct ListNode *next;

}

 

①单链表的创建

//创建链表 

struct ListNode* createList()

{

    struct ListNode*p=NULL,*tail=NULL,*head=NULL;//p为待开辟的节点指针,tail为指向链表尾部的指针,head为指向链表头部的指针。

                                  //笔者喜欢在创建链表时 创建head tail 两个指针 虽然不一定都用得到

    int num;                        //将指针置空是个好习惯 

    scanf("%d",&num);

    //尾插法  顺序插法 

    while(num!=-1)//假设以num/val值为-1作为链表的结束标志  或者你用定长条件也行 

    {

       p=(struct ListNode*)malloc(sizeof(struct ListNode));//注意此处用sizeof计算大小时在ListNode前要加struct关键字

       p->val=num;

       p->next=NULL;

       if(head==NULL)//第一次循环时将头指针head指向p 

       {

           head=p;    

       } 

       else

       {

          tail->next=p;    

       } 

       tail=p;//此句放else里面也行  笔者更喜欢在第一次循环时就将tail与p与head产生关联 

       scanf("%d",&num); 

    } 

//    头插法   逆序插法 

//    while(num!=-1)

//    {

//        p=(struct ListNode*)malloc(sizeof(struct ListNode));

//        p->val=num;

//        if(head==NULL)

//        {

//            head=p;

//        }

//        else

//        {

//           p->next=head;

//           head=p;    

//        }

//    } 

    tail->next=NULL//用p->next也行 这是尾插法 

    return head;//返回链表头指针                              

②链表节点的插入

 

struct ListNode *insertList(ListNode *head,int index,int num)//index 表示在链表中插入的位置(从1开始)原位置的元素接在其后  num表示要插入的数值

{

    struct ListNode *p=NULL,q;//在此q为虚拟节点 主要方便对链表进行操作 

    int cnt=1;//计数器从1开始计数

    if(head==NULL&&head->next==NULL)//链表为空 返回一个空指针 

    return NULL; 

    if(index==1)//若插在头结点(在此特指链表的第一个节点)的位置

    {

        p=(struct ListNode*)malloc(sizeof(struct ListNode));//给p开辟实际空间 用来储存操作存入的值 

        p->val=num;

        p->next=head; 

        head=p; 

    } 

    else 

    {

        q=head;

        while(q->next!=NULL&&cnt<=index)//对于链表我们通常对当前节点的下一个节点进行操作 

        {

            if(cnt+1<index)//为什么要加一呢?因为表示下一个位置 

            {

                q=q->next; 

            }

            else

            {

                p=(struct ListNode *)malloc(sizeof(struct ListNode));

                p->val=num;

                p->next=q->next;

                q->next=q;    

            }

        }

    }

    if(cnt<index)//如果cnt还是 小于 index 则表明插在链表末尾 

    {

        p=(struct ListNode*)malloc(sizeof(struct ListNode));

        p->val=num;

        p->next=NULL;

        q->next=p;

    } 

    return head;

}   

 

③链表节点的删除

 

struct ListNode *deleteList(struct ListNode *head,int num)//执行删除节点val值为num的操作

{

    struct ListNode *p=NULL,*q=NULL;//p,q均为辅助指针 不给他们开辟实际空间 

    if(head->val==num)//若删除节点为头结点 

    {

           p=head;

           head=head->next;

           free(p);

    } 

    else//若删除节点为除头结点外的其他节点 

    {

        p=head;

        while(p->next!=NULL)

        {

            if(p->next->val==num)

            {

                q=p->next;

                p->next=p->next->next;

                free(q);    

            } 

            else

            {

                p=p->next;

            }

        }

    }

    return head; 

     

链表的遍历

 

void printList(struct ListNode *head)

{

    struct ListNode *p=head;//p依然为辅助指针

    while(p!=NULL)//因为是遍历(打印输出)操作 所以对当前节点进行操作即可 

    {

       printf("%d",p->val);

       p=p->next; 

    } 

}

 

双向链表

双向链表即单链表由单向的链变成了双向的链(一个指针域(next)变成一前一后两个指针域(prev,next)) 这里只演示双向链表的创建

另:

节点结构为:

 

struct ListNode

{

int val;

ListNode *prev;

ListNode *next;

};

 

双向链表的创建 

struct ListNode *createDbList()

{

    struct ListNode*p=NULL,*head=NULL,*tail=NULL;

    int num;

    scanf("%d",&num);

    while(num!=-1)//以-1作为创建链表结束的标志 

    {

        p=(struct ListNode*)malloc(sizeof(struct ListNode));

        p->val=num;

        if(head==NULL)//这里主要演示尾插法 

        {

           head=p;    

           p->prev=NULL; //就不将双向链表和循环链表相互掺和了 

        } 

        else

        {

          tail->next=p;

          p->prev=tail;     

        }

        tail=p;

    } 

    tail->next=NULL;

}

 

循环链表

即将链表首尾相接 同样这里只演示循环链表的创建

 

struct ListNode *createList()

{

    struct ListNode*p,*head,*tail;

    int num;

    scanf("%d",&num);

    while(num!=-1)

    {

       p=(struct ListNode*)malloc(sizeof(struct ListNode));

       p->val=num;

       if(head==NULL)

       {

          head=p;    

       }    

       else

       {

             tail->next=p;

       }

       tail=p;

    }

    tail->next=head;//创建结束后将链表首尾相接    

 

静态链表

静态链表的节点通常用结构体数组表示

 

struct  staticListNode

   int val;

   int cur;//游标cur用来储存后继节点的下标

};

 

 

1.Floyd判圈法:判断链表是否有环(快慢指针的应用)

 

 

对于链表找环路的问题,有一个通用的解法——快慢指针(Floyd) 。给定两个指针, 分别命名为 slow 和 fast ,起始位置在链表的开头。每次 fast 前进两步, slow 前进一步。如果 fast 可以走到尽头,那么说明没有环路;如果fast 可以无限走下去,那么说明一定有环路,且一定存 在一个时刻 slow 和 fast 相遇。当 slow 和 fast 第一次相遇时,我们将 fast 重新移动到链表开头,并 让 slow 和 fast 每次都前进一步。当 slow 和 fast 第二次相遇时,相遇的节点即为环路的开始点。

 

2. 判断两个链表是否相交

 

 

假设链表 A 的头节点到相交点的距离是 a,链表 B 的头节点到相交点的距离是 b,相交点到链表终点的距离为 c。我们使用两个指针,分别指向两个链表的头节点,并以相同的速度前进,若到达链表结尾,则移动到另一条链表的头节点继续前进。按照这种前进方法,两个指针会在a + b + c 次前进后同时到达相交节点

————————————————

版权声明:本文为CSDN博主「legendarykk」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.NET/legendarykk/article/details/125592024

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>