您当前的位置:首页 > 电脑百科 > 程序开发 > 算法

关于链表的理解

时间:2022-10-07 09:40:51  来源: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



Tags:链表   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
C++实现链表:原理、代码与解析
链表是一种常见的数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。与数组不同,链表不是连续的内存空间,而是通过指针链接在一起。下面我们将深入探讨如何...【详细内容】
2023-12-22  Search: 链表  点击:(112)  评论:(0)  加入收藏
一文搞定双链表,让你彻底弄懂线性表的链式实现
前言前面有很详细的讲过线性表(顺序表和链表),当时讲的链表以单链表为主,但在实际应用中双链表有很多应用场景,例如大家熟知的LinkedList。双链表与单链表区别单链表和双链表都...【详细内容】
2023-11-08  Search: 链表  点击:(249)  评论:(0)  加入收藏
如何在C++程序中创建链表
引言:链表是一种常用的数据结构,它在C++程序中的应用非常广泛。本文将介绍如何在C++程序中创建链表,并提供了一些基本的链表操作示例。通过本文的学习,读者将了解链表的概念、创...【详细内容】
2023-08-16  Search: 链表  点击:(268)  评论:(0)  加入收藏
关于链表的理解
一:链表是什么 1、链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,有一系列结点(地址)组成,结点可动态的生成。 2、结点包括两个部...【详细内容】
2022-10-07  Search: 链表  点击:(335)  评论:(0)  加入收藏
谈谈对链表的理解
✨最近有一些粉丝问了我个问题,我平时是怎样学习一门新的技术的,文章开始之前我先来分享一下我的制胜法宝。✨博主学习方法“三刷”官方文档或源码是我高效学习一门新的技能的...【详细内容】
2022-10-04  Search: 链表  点击:(275)  评论:(0)  加入收藏
Python数据结构之链表详解
0. 学习目标在顺序存储方式中,根据数据元素的序号就可随机存取表中任何一个元素,但同时在插入和删除运算需要移动大量的元素,造成算法效率较低。解决此缺陷的一个办法是:对线性...【详细内容】
2022-07-29  Search: 链表  点击:(336)  评论:(0)  加入收藏
「PHP数据结构」线性表?顺序表?链表?别再傻傻分不清楚
遵从所有教材以及各类数据结构相关的书书籍,我们先从线性表开始入门。今天这篇文章更偏概念,是关于有线性表的一个知识点的汇总。上文说过,物理结构是用于确定数据以何种方式存...【详细内容】
2021-07-19  Search: 链表  点击:(362)  评论:(0)  加入收藏
单链表翻转的三种方式!
当我们在聊到链表反转的时候,一定说的都是单链表,双链表本身就具有前驱指针 Prev 和后续指针 next,无需进行翻转。单链表反转,反转后的效果如下: 看起来很简单,只需要将单链表所...【详细内容】
2021-01-12  Search: 链表  点击:(408)  评论:(0)  加入收藏
找出两个链表的第一个公共节点
问题描述输入两个链表,找出它们的第一个公共节点。如下面的两个链表: 在节点 c1 开始相交。 示例 1: 输入:intersectVal = 8,listA = [4,1,8,4,5],listB = [5,0,1,8,4,5],skipA...【详细内容】
2020-10-19  Search: 链表  点击:(374)  评论:(0)  加入收藏
七道经典的关于链表的笔试题目
给定一个带有头节点的单链表,如何将其逆序,也就是说head->a->b->c,变为head->c->b->a?难点:这个需要了解链表的结构,每一个链表除了存储自身的元素之外,还会存储下一个结点的地址,...【详细内容】
2020-09-29  Search: 链表  点击:(268)  评论:(0)  加入收藏
▌简易百科推荐
小红书、视频号、抖音流量算法解析,干货满满,值得一看!
咱们中国现在可不是一般的牛!网上的网友已经破了十个亿啦!到了这个互联网的新时代,谁有更多的人流量,谁就能赢得更多的掌声哦~抖音、小红书、、视频号,是很多品牌必争的流量洼地...【详细内容】
2024-02-23  二手车小胖说    Tags:流量算法   点击:(15)  评论:(0)  加入收藏
雪花算法详解与Java实现:分布式唯一ID生成原理
SnowFlake 算法,是 Twitter 开源的分布式 ID 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 ID。在分布式系统中的应用十分广泛,且 ID 引入了时间戳...【详细内容】
2024-02-03   一安未来  微信公众号  Tags:雪花算法   点击:(51)  评论:(0)  加入收藏
程序开发中常用的十种算法,你用过几种?
当编写程序时,了解和使用不同的算法对解决问题至关重要。以下是C#中常用的10种算法,每个算法都伴随着示例代码和详细说明。1. 冒泡排序 (Bubble Sort):冒泡排序是一种简单的比...【详细内容】
2024-01-17  架构师老卢  今日头条  Tags:算法   点击:(45)  评论:(0)  加入收藏
百度推荐排序技术的思考与实践
本文将分享百度在推荐排序方面的思考与实践。在整个工业界的推广搜场景上,特征设计通常都是采用离散化的设计,需要保证两方面的效果,一方面是记忆,另一方面是泛化。特征都是通过...【详细内容】
2024-01-09  DataFunTalk  微信公众号  Tags:百度推荐   点击:(77)  评论:(0)  加入收藏
什么是布隆过滤器?如何实现布隆过滤器?
以下我们介绍了什么是布隆过滤器?它的使用场景和执行流程,以及在 Redis 中它的使用,那么问题来了,在日常开发中,也就是在 Java 开发中,我们又将如何操作布隆过滤器呢?布隆过滤器(Blo...【详细内容】
2024-01-05  Java中文社群  微信公众号  Tags:布隆过滤器   点击:(87)  评论:(0)  加入收藏
面向推荐系统的深度强化学习算法研究与应用
随着互联网的快速发展,推荐系统在各个领域中扮演着重要的角色。传统的推荐算法在面对大规模、复杂的数据时存在一定的局限性。为了解决这一问题,深度强化学习算法应运而生。本...【详细内容】
2024-01-04  数码小风向    Tags:算法   点击:(96)  评论:(0)  加入收藏
非负矩阵分解算法:从非负数据中提取主题、特征等信息
非负矩阵分解算法(Non-negativeMatrixFactorization,简称NMF)是一种常用的数据分析和特征提取方法,主要用于从非负数据中提取主题、特征等有意义的信息。本文将介绍非负矩阵分解...【详细内容】
2024-01-02  毛晓峰    Tags:算法   点击:(64)  评论:(0)  加入收藏
再谈前端算法,你这回明白了吗?
楔子 -- 青蛙跳台阶一只青蛙一次可以跳上一级台阶,也可以跳上二级台阶,求该青蛙跳上一个n级的台阶总共需要多少种跳法。分析: 当n=1的时候,①只需要跳一次即可;只有一种跳法,即f(...【详细内容】
2023-12-28  前端爱好者  微信公众号  Tags:前端算法   点击:(108)  评论:(0)  加入收藏
三分钟学习二分查找
二分查找是一种在有序数组中查找元素的算法,通过不断将搜索区域分成两半来实现。你可能在日常生活中已经不知不觉地使用了大脑里的二分查找。最常见的例子是在字典中查找一个...【详细内容】
2023-12-22  小技术君  微信公众号  Tags:二分查找   点击:(78)  评论:(0)  加入收藏
强化学习算法在资源调度与优化中的应用
随着云计算和大数据技术的快速发展,资源调度与优化成为了现代计算系统中的重要问题。传统的资源调度算法往往基于静态规则或启发式方法,无法适应动态变化的环境和复杂的任务需...【详细内容】
2023-12-14  职场小达人欢晓    Tags:算法   点击:(165)  评论:(0)  加入收藏
站内最新
站内热门
站内头条