什么是链表

在面试中只要被问到React Hooks就常被问到为什么Hooks不能在循环和条件判断嵌套函数当中使用;相信很多人都知道标准答案,【因为声明的Hooks保存在链表当中】,但是你真的知道链表吗?

我们先看来看一个简单的单向链表结构

如上图所示我们可以分析出,链表是由多个 node(节点) 组成的,而node(节点) 指向(保存)不同的内存空间,每个node(节点)item(数据域)next(指针域) (双向链表还包括prev指针域)构成,其中item(数据域) 用于存储数据,next(指针域) 指向下一个节点从而形成一个存储数据的线性链路

总结:链表是一个用于存储数据的无序线性数据结构

而通过指针域指向链路的差异我们大致可以分为:

  • 单向链表
  • 双向链表
  • 环形链表

链表与数组的比较

不知道链表这种数据结构能否让你想起数组,这两种都是用于存储数据的线性数据结构,而不同的是链表是一种无序线性数据结构,而数组是一种有序线性数据结构,我们都知道数组是一种引用类型数据结构,当我们创建一个数组的时候会在内存种开辟一串连续的内存空间用于存储,数组就是依靠这串连续的内存空间来维持线性链路,而链表则是有一连串无序的内存保存node(节点) 通过node(节点) 的指针域指向下一个节点来维持线性链路

链表有什么作用?

假设现在有一百条客户的数据你需要对这一百条数据进行增、删、插入你会怎么做?

创建一个数组将100条数据存入数组当中,通过数组的push,splice,findIndex,indexOf等方法对数组进行操作,对于少量数据答案是显而易见的,我们直接通过数组就能解决;但是如果现在有一百万条数据让你操作呢?我们已经提到数组是通过连续内存地址来维持线性链路的一种有序线性结构,如果你在头部插入一条数据的话则后面的一系列元素都要向后移动一位,一百万条数据则要移动一百万次,如果你要删除第一万个元素则后面的九十九万个元素要向前移动一个位置,如果要将第一条数据替换为最后一条数据则要先删除再插入先将第一条数据与最后一条数据取出其余所有数据要向前移动一个位(除头部数据与尾部数据),然后再替换插入,所有数据再向后移动一位;在数据量庞大的情况下这绝对不是一个明智的方案,因为时间维度的不允许;但是如果换成链表你只需要操作node(节点) 指针域的指向便可以完成以上工作;

链表的优缺点

优点:相较于数组,链表操作更加的灵活,不受存储空间的限制;

缺点:

  • 链表不能通过下标获取值,每次要获取链表当中的node(节点) 都需要经过遍历
  • 对于存储基本类型的数据结构因为需要通过指针域的指向则需要多分配一块内存进行存储(双向链表则乘以2)

通过JS简单实现一个单向链表

而对于链表操作我们大致可以分为

  • 新增
  • 插入
  • 删除
  • 查看
  • 修改

我们以单项链表为例依次实现他们

创建Node辅助类

我们已经知道链表的大致概念也就是链表是一种以多个node(节点) 通过指针域连接的无序线性数据结构,因此首先我们需要创建辅助类Node用于创建node(节点)

//辅助类Node用于创建保存在链表中的node
class Node {
    constructor (item) {
        //数据域,用于保存数据
        this.item = item
        //指针域,用于指向下一个节点
        this.next = null
    }
}

而在链表中始终有一个head属性,这个属性是链表的开端,用于存放整个链表的线性链路;我们还需要一个size用于保存链表的长度,用于遍历链表;

//用于创建一个链表
class Linked{
    constructor () {
        //size属性用于保存链表的长度用于遍历
        this.size = 0
        //用于存放线性链路
        this.head = null
    }
}

至此我们已经完成了创建一个链表的准备工作;那么让我们看看链表的基本操作方法是如何实现的

单向链表新增操作

对于单向链表新增如果当前链表为空我们需要将链表的head属性直接指向创建的node(节点),如果链表不为空则我们需要将最末端的node(节点)next(指针域) 指向新创建的 node(节点)

class Linked {
    constructor () {
        this.size = 0
        this.head = null
    }
    //新增node方法
    add (item) {
        //创建node
        let node = new Node (item)
        //this.head === null则代表链表为空需要将新head指向新创建的node
        if (this.head === null) {
            this.head = node
        } else {
            //查找需要创建的节点的上一个节点(最末端节点)
            let prevNode = this.getNode (this.size - 1)
            //将末端节点的next指向新创建的node
            prevNode.next = node
        }
        //新增成功则size 1
        this.size  
    }
    //节点查找方法传入index类似于数组下标用于标记查找
    getNode (index) {
        //如果index < 0 || index >= this.size则说明下标越界需要在此限制
        if (index < 0 || index >= this.size) {
            throw new Error ('out range')
        }
        //获取第一个节点,从第一个节点进行遍历
        let current = this.head;
        for (let i = 0; i < index; i  ) {
            //依次将当前节点指向下一个节点,直到获取最后一个节点
            current = current.next
        }
        return current
    }
}

单向链表插入操作

对于单向链表插入操作如果需要插入的位置为下标为0的位置(头部),我们需要将需要插入的node(节点)next(指向域),指向未改变之前的第一个node(节点),也就是head,如果是中间追加则我们需要 将插入node(节点) 的指向域指向插入下标的上一个节点的指向域(插入节点的下一个节点),并将插入node(节点) 的上一个节点的指向域,指向当前节点

class Linked {
    constructor () {
        this.size = 0
        this.head = null
    }
    //追加插入
    //position为插入位置下标,item为需要保存到节点的元素
    insert (position, item) {
        //下标值越位判断
        if (position < 0 || position > this.size) {
            throw new Error ('Position out range');
        }
        //创建新节点
        let node = new Node (item)
        //头部追加
        //如果插入下标为0则直接将head指向新创建的节点
        if (position === 0) {
            node.next = this.head;
            this.head = node
        } else {//中间追加
            //获取追加节点的上一个节点
            let prevNode = this.getNode (position - 1)
            //将插入下标的指向域指向插入下标的上一个节点的指向指向域(下一个节点)
            node.next = prevNode.next
            //将插入下标的上一个节点的指向域,指向当前节点
            prevNode.next = node
        }
        //插入成功,长度加一
        this.size  
    }
    getNode (index) {
        if (index < 0 || index >= this.size) {
            throw new Error ('out range')
        }
        let current = this.head;
        for (let i = 0; i < index; i  ) {
            current = current.next
        }
        return current
    }
}

单向链表删除操作

对于单向链表的删除操作,如果删除元素为链表的顶端元素则需要将head指向被删除元素的指针域(下一个元素),如果不是第一个元素则我们需要将上一个元素的指针域指向被删除元素的next(指针域)(下一个元素)

class Linked {
    constructor () {
        this.size = 0
        this.head = null
    }
    delete (position) {
        //删除下标合法判断
        if (position < 0 || position >= this.size) {
            throw new Error ('position out range')
        }
        //获取当前链表(head)
        let linkList = this.head
        //如果删除的是链表的第一个元素则将head指向第一个元素的指针域(下一个元素)
        if (position === 0) {
            this.head = linkList.next;
        } else {//中间删除
            //获取删除元素的上一个节点
            let prevNode = this.getNode (position - 1)
            //将链表指向被删除元素上一个节点
            linkList = prevNode.next
            //将链表的的上一个节点指向被删除元素的下一个节点
            prevNode.next = linkList.next
        }
        //长度减一
        this.size--
    }
    getNode (index) {
        if (index < 0 || index >= this.size) {
            throw new Error ('out range')
        }
        let current = this.head;
        for (let i = 0; i < index; i  ) {
            current = current.next
        }
        return current
    }
 }

单向链表查找操作

对于查找操作我们需要对链表进行遍历比对获取下标

class Linked {
    constructor () {
        this.size = 0
        this.head = null
    }
    //查找指定元素下标
    findIndex (item) {
        //获取当前链表
        let current  = this.head
        //进行遍历操作依次比对获取查找元素下标
        for(let i=0;i<this.size;i  ){
            if (current.item === item) {//如果current.item === item则说明该元素为需要查找的元素,返回下标
                return i
            }
            //使聊表指向他的下一个元素,使循环可以继续
           current = current.next
        }
        return null
    }
    getNode (index) {
        if (index < 0 || index >= this.size) {
            throw new Error ('out range')
        }
        let current = this.head;
        for (let i = 0; i < index; i  ) {
            current = current.next
        }
        return current
    }
}

单向链表修改操作

对于单向链表的修改操作我们只需用下标获取需要修改的元素,并对其item重新进行赋值即可;

class Linked {
    constructor () {
        this.size = 0
        this.head = null
    }
    getNode (index) {
        if (index < 0 || index >= this.size) {
            throw new Error ('out range')
        }
        let current = this.head;
        for (let i = 0; i < index; i  ) {
            current = current.next
        }
        return current
    }
    //修改操作
    //position为需要修改元素的下标,item为修改的值
    update(position,item){
        let current = this.getNode(position)
        current.item = item
    }
}

单向链表类方法整合

class Node {
    constructor (item) {
        this.item = item
        this.next = null
    }
}
class Linked {
    constructor () {
        this.size = 0
        this.head = null
    }
    add (item) {
        let node = new Node (item)
        if (this.head === null) {
            this.head = node
        } else {
            let current = this.getNode (this.size - 1)
            current.next = node
        }
        this.size  
    }
    //追加插入
    insert (position, item) {
        //下标值越位判断
        if (position < 0 || position > this.size) {
            throw new Error ('Position out range');
        }
        //创建新节点
        let node = new Node (item)
        //头部追加
        //如果插入下标为0则直接将head指向新创建的节点
        if (position === 0) {
            node.next = this.head;
            this.head = node
        } else {//中间追加
            let prevNode = this.getNode (position - 1)
            //将插入下标的指向域指向插入下标的上一个节点的指向指向域(下一个节点)
            node.next = prevNode.next
            //将插入下标的上一个节点的指向域,指向当前节点
            prevNode.next = node
        }
        //插入成功,长度加一
        this.size  
    }
    delete (position) {
        //删除下标合法判断
        if (position < 0 || position >= this.size) {
            throw new Error ('position out range')
        }
        //获取当前链表(head)
        let linkList = this.head
        //如果删除的是链表的第一个元素则将head指向第一个元素的指针域(下一个元素)
        if (position === 0) {
            this.head = linkList.next;
        } else {//中间删除
            //获取删除元素的上一个节点
            let prevNode = this.getNode (position - 1)
            //将链表指向被删除元素上一个节点
            linkList = prevNode.next
            //将链表的的上一个节点指向被删除元素的下一个节点
            prevNode.next = linkList.next
        }
        //长度减一
        this.size--
    }
    //查找指定元素下标
    findIndex (item) {
        //获取当前链表
        let current  = this.head
        //进行遍历操作依次比对获取查找元素下标
        for(let i=0;i<this.size;i  ){
            if (current.item === item) {//如果current.item === item则说明该元素为需要查找的元素,返回下标
                return i
            }
            //使聊表指向他的下一个元素,使循环可以继续
           current = current.next
        }
        return null
    }
    getNode (index) {
        if (index < 0 || index >= this.size) {
            throw new Error ('out range')
        }
        let current = this.head;
        for (let i = 0; i < index; i  ) {
            current = current.next
        }
        return current
    }
    //修改操作
    //position为需要修改元素的下标,item为修改的值
    update(position,item){
        let current = this.getNode(position)
        current.item = item
    }
}

写在最后

对于链表的使用感受,我觉得我们不能将链表看成一个整体的数据结构,而是要将它单元化,通过指针域来灵活的使用它。其实我更加倾向于将链表理解成一种设计思路,我们也没必要将每个指针域命名为next,我们可以通过指针域的不同来构建千变万化的数据结构,它也可以拥有不止一个指针域,如果我们添加一个prev指针指向上一个节点那么这个链表就是一个双向链表,如果链表的最底层next指向的不是null而是当前链表中任意一个节点,那么它就是一个环形链表;当然我们也可以使一个节点拥有left和right两个指针域,指向不同的节点,那么它就是一个二叉树,甚至我们可以用链表的指针域概念来实现一个优先队列,虽然在前端开发中链表的操作非常少,但是在阅读源码当中我不止一次的看到链表这种数据结构。说白了,这是一个无用的知识,因为你在开发当中基本上用不到,但是链表的指针域概念却能提升你的思维,让你对数据结构有更广的思考;本来我是想再讲讲双向链表与环形链表的,但是我实在不知道如何用文字表达用快慢指针来判断环形链表中是否存在一个环,因此便没有继续;

以上就是React前端解链表数据结构示例详解的详细内容,更多关于React 解链表数据结构的资料请关注Devmax其它相关文章!

React前端解链表数据结构示例详解的更多相关文章

  1. ios – React native链接到另一个应用程序

    如果是错误的,有人知道如何调用正确的吗?

  2. ios – React Native – 在异步操作后导航

    我正在使用ReactNative和Redux开发移动应用程序,我正面临着软件设计问题.我想调用RESTAPI进行登录,如果该操作成功,则导航到主视图.我正在使用redux和thunk所以我已经实现了异步操作,所以我的主要疑问是:我应该把逻辑导航到主视图?我可以直接从动作访问导航器对象并在那里执行导航吗?.我对组件中的逻辑没有信心.似乎不是一个好习惯.有没有其他方法可以做到这一点?

  3. 在ios中使用带有React Native(0.43.4)的cocoapods的正确方法是什么?

    我已经挖掘了很多帖子试图使用cocoapods为本地ios库设置一个反应原生项目,但我不可避免地在#import中找到了丢失文件的错误.我的AppDelegate.m文件中的语句.什么是使用反应原生的可可豆荚的正确方法?在这篇文章发表时,我目前的RN版本是0.43.4,而我正在使用Xcode8.2.1.这是我的过程,好奇我可能会出错:1)

  4. ios – React Native WebView滚动行为无法按预期工作

    如何确保滚动事件的行为与ReactNative应用程序中的浏览器相同?

  5. ios – React Native – BVLinearGradient – 找不到’React/RCTViewManager.h’文件

    谢谢.解决方法几天前我遇到了完全相同的问题.问题是在构建应用程序时React尚未链接.试试这个:转到Product=>Scheme=>管理方案…=>点击你的应用程序Scheme,然后点击Edit=>转到Build选项卡=>取消选中ParallelizeBuild然后点击标志添加目标=>搜索React,选择第一个名为React的目标,然后单击Add然后在目标列表中选择React并将其向上拖动到该列表中的第一个.然后转到Product=>再次清理并构建项目.这应该有所帮助.

  6. ios – React Native – NSNumber无法转换为NSString

    解决方法在你的fontWeight()函数中也许变成:

  7. ios – React native error – react-native-xcode.sh:line 45:react-native:command not found命令/ bin/sh失败,退出代码127

    尝试构建任何(新的或旧的)项目时出现此错误.我的节点是版本4.2.1,react-native是版本0.1.7.我看过其他有相同问题的人,所以我已经更新了本机的最新版本,但是我仍然无法通过xcode构建任何项目.解决方法要解决此问题,请使用以下步骤:>使用节点版本v4.2.1>cd进入[你的应用]/node_modules/react-native/packager>$sh./packager.s

  8. 反应原生 – 如何通过Xcode构建React Native iOS应用程序到设备?

    我试图将AwesomeProject应用程序构建到设备上.构建成功并启动屏幕显示,但后来我看到一个红色的“无法连接到开发服务器”屏幕.它表示“确保节点服务器正在运行–从Reactroot运行”npmstart“.看起来节点服务器已经运行,因为当我做npm启动时,我收到一个EADDRINUSE消息,表示该端口已经在使用.解决方法从设备访问开发服务器您可以使用开发服务器快速迭代设备.要做到这一点,你的

  9. 静音iOS推送通知与React Native应用程序在后台

    我有一个ReactNative应用程序,我试图获得一个发送到JavaScript处理程序的静默iOS推送通知.我看到的行为是AppDelegate中的didReceiveRemoteNotification函数被调用,但是我的JavaScript中的处理程序不会被调用,除非应用程序在前台,或者最近才被关闭.我很困惑的事情显然是应用程序正在被唤醒,并且它的didReceiveRemoteNotifi

  10. 如何为iOS的React Native设置分析

    所以我已经完成了一个针对iOS的ReactNative项目,但是我想在其中分析.我尝试了react-native-google-analytics软件包,但是问题阻止了它的正常工作.此外,react-native-cordova-plugin软件包只适用于Android,因此插入Cordova插件进行分析的能力现在已成为问题.我也没有Swift/ObjectiveC的经验,所以将完全失去GA的插入.有没有人有任何建议如何连接GoogleAnalytics的ReactNativeforiOS?

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部