1 数组与链表的优缺点

链表和数组一样,都可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。

一般情况下,要存储多个元素,数组可能是最常用的数据结构。但是使用数组存储有一些缺点:

  • 数组的创建需要申请一段连续的内存空间(一整块的内存),并且大小是固定的,所以当当前数组不能满足容量需求时,就需要扩容(一般情况下会申请一个更大的数组,比如2倍,然后将原数组中的元素复制过去)。
  • 在数组的开头或中间位置插入数据的成本很高,需要进行大量元素的位移。

存储多个元素的另一个选择就是链表,但是不同于数组,链表中的元素在内存中不必是连续的空间,链表的每个元素由一个存储元素本身的节点和指向下一个元素的引用(指针)组成。

那么和数组相比,链表有一些优势:

  • 内存空间不是必须连续的,可以充分利用计算机的内存,实现灵活的内存动态管理;
  • 链表不必在创建时就确定大小,并且大小可以无限延伸下去;
  • 链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组效率高很多;

链表也有一些缺点:

  • 链表访问任何一个元素的位置时,都需要从头开始访问,并且无法跳过第一个元素访问任何一个元素
  • 链表无法通过下标直接访问元素,需要从头一个个访问,直到找到对应的元素

2 什么是链表

链表的每个元素由一个存储元素本身的节点和指向下一个元素的引用(指针)组成。

它类似于火车,火车头就是头节点,火车车厢之间的连接类似于指针,火车上的乘客类似于数据。

接下来我们根据它的特性来手动封装一个链表。

3 封装链表结构

首先我们来封装一个链表类LindedList,用来表示我们的链表结构。LindedList类中应该有两个属性,链表的头节点head和链表的长度length

function LinkedList() {
    this.head = null; // 初始指向null
    this.length = 0; // 初始链表长度为0
}

LindedList类内部有一个ListNode类,用于创建节点,创建节点时应该为节点传入数据data,并且该节点有指向下一个节点的指针。

function LinkedList() {
    this.head = null; // 初始指向null
    this.length = 0; // 初始链表长度为0
    function ListNode(data) {
        this.data = data;
        this.next = null;
    }
}

到这里链表的基础结构就完成了,接下来我们来封装链表的方法。

4 向链表尾部添加一个新的项

向链表尾部添加一个新的节点,有两种情况:

  • 当链表本身为空时,头节点就是新添加的节点
  • 当链表不为空时,让链表最后一个节点指向新添加的节点

根据这个思路,我们可以实现append方法如下:

LinkedList.prototype.append = function (data) { // data是新节点的值
    let node = new ListNode(data); // 创建新的节点
    if (this.length === 0) { // 如果链表为空,则新节点就是头节点
        this.head = node;
    } else { // 如果链表不为空,新节点添加到链表尾部
        let current = this.head; // 将current指向头节点
        // 链表无法直接访问到最后的节点,只能通过一次次遍历来访问
        while (current.next) { // 当达到最后一个节点时,循环结束
            // 当下一个节点存在时,就让current指针移动到下一个节点上
            current = current.next;
        }
        // 最后一个节点指向新节点
        current.next = node;
    }
    this.length  = 1; // 链表的长度 1
}

5 向链表某个位置插入一个新的项

在链表的任意位置插入节点时,也分为两种情况:

  • 当插入到第一个位置时,新节点变成了头节点,那么新节点要指向原来的头节点,属性head也应该变成新节点
  • 当插入到其他位置时,首先通过循环找到该位置,同时保存上一个节点和下一个节点,然后将上一个节点指向新节点,新节点指向下一个节点

插入insert方法代码实现如下:

// position为节点要插入的位置,data为节点的值
LinkedList.prototype.insert = function (position, data) {
    // 对position进行越界判断,当该值小于0或者大于链表长度时,不能进行插入操作
    if (position <= 0 || position > this.length) return false;
    let node = new ListNode(data); // 创建新节点
    if (position === 0) { // 如果节点要插入第一个位置
        node.next = this.head; // 新节点指向原来的头节点
        this.head = node; // 头节点修改为新节点
    } else {
        let previous = null; // 指向前一个位置
        let current = this.head; // 指向下一个位置
        let index = 1; // 记录循环的位置
        // 循环结束,previous和current之间就是插入的节点
        while (index < position) {
            previous = current;
            current = current.next;
            index  ;
        }
        previous.next = node; // 在正确的位置插入元素
        node.next = current;
    }
    this.length  = 1; // 长度加1
}

6 获取对应位置的元素

获取某个位置上的元素,也要通过循环链表来找到当前元素,get方法实现如下:

LinkedList.prototype.get = function (position) {
    // 越界判断,如果位置小于0或者大于链表长度,不能获取到元素
    if (position <= 0 || position > this.length) return null;
    let index = 1; // 记录当前位置
    let current = this.head; // current指向头节点
    // 循环结束,current指向该位置上的节点
    while (index < position) {
        current = current.next;
        index  ;
    }
    return current.data;
}

7 获取元素在链表中的索引

获取索引时,要循环遍历链表,将链表的每一个节点值都和给定的值比较,如果相等返回当前节点的位置,如果没找到,则返回-1。indexOf方法实现如下:

// 获取某个元素的位置
LinkedList.prototype.indexOf = function (data) {
    let current = this.head;
    let index = 1; // 记录元素的位置
    while (current) { // 循环遍历链表
        if (current.data === data) { // 如果当前节点的值等于元素的值
            return index; // 返回位置
        } else { // 如果不等于,继续循环
            current = current.next;
            index  ;
        }
    }
    return -1; // 循环结束了,说明没找到
}

8 修改某个位置的元素

修改某个位置的元素时,循环遍历链表,找到给定的位置,修改该位置上元素的值。update方法实现如下:

// 修改某个位置的元素
LinkedList.prototype.update = function (position, data) {
    // 越界判断
    if (position <= 0 || position > this.length) return false;
    let current = this.head;
    let index = 1;
    while (index < position) {
        current = current.next;
        index  ;
    }
    current.data = data; // 修改数据
    return true;
}

9 从链表中删除某位置节点

删除某个位置上的节点分为两种情况:

  • 删除第一个位置上的节点时,要将第一个位置上的节点指向null,并且第二个位置上的节点成为头节点
  • 删除其他位置上的节点,循环找到该位置,同时记录该节点上一个节,将上一个节点指向该位置的下一个节点

删除某位置节点removeAt方法实现如下:

LinkedList.prototype.removeAt = function (position) {
    if (position <= 0 || position > this.length) return false; // 越界判断
    let current = this.head;
    if (position === 1) { // 如果删除第一个位置上的节点(头节点)
        this.head = this.head.next;
    } else { // 删除其他位置的节点
        let index = 1; // 记录当前位置
        let previous = null;
        while (index < position) {
            previous = current;
            current = current.next;
            index  ;
        }
        // 上一个节点指向当前元素的下一个节点
        previous.next = current.next;
    }
    this.length--;
    return current; // 返回被删除的节点
}

10 全部代码

function LinkedList() {
    this.head = null; // 初始指向null
    this.length = 0; // 初始链表长度为0
    function ListNode(data) { // 创建新节点类
        this.data = data;
        this.next = null;
    }
    // 添加元素
    LinkedList.prototype.append = function (data) { // data是新节点的值
        let node = new ListNode(data); // 创建新的节点
        if (this.length === 0) { // 如果链表为空,则新节点就是头节点
            this.head = node;
        } else { // 如果链表不为空,新节点添加到链表尾部
            let current = this.head; // 将current指向头节点
            // 链表无法直接访问到最后的节点,只能通过一次次遍历来访问
            while (current.next) { // 当达到最后一个节点时,循环结束
                // 当下一个节点存在时,就让current指针移动到下一个节点上
                current = current.next;
            }
            // 最后一个节点指向新节点
            current.next = node;
        }
        this.length  = 1; // 链表的长度 1
    }
    // 插入元素
    // position为节点要插入的位置,data为节点的值
    LinkedList.prototype.insert = function (position, data) {
        // 对position进行越界判断,当该值小于0或者大于链表长度时,不能进行插入操作
        if (position <= 0 || position > this.length) return false;
        let node = new ListNode(data); // 创建新节点
        if (position === 0) { // 如果节点要插入第一个位置
            node.next = this.head; // 新节点指向原来的头节点
            this.head = node; // 头节点修改为新节点
        } else {
            let previous = null; // 指向前一个位置
            let current = this.head; // 指向下一个位置
            let index = 1; // 记录循环的位置
            // 循环结束,previous和current之间就是插入的节点
            while (index < position) {
                previous = current;
                current = current.next;
                index  ;
            }
            previous.next = node; // 在正确的位置插入元素
            node.next = current;
        }
        this.length  = 1; // 长度加1
    }
    // 获取某个位置上的元素
    LinkedList.prototype.get = function (position) {
        // 越界判断,如果位置小于0或者大于链表长度,不能获取到元素
        if (position <= 0 || position > this.length) return null;
        let index = 1; // 记录当前位置
        let current = this.head; // current指向头节点
        // 循环结束,current指向该位置上的节点
        while (index < position) {
            current = current.next;
            index  ;
        }
        return current.data;
    }
    // 获取某个元素的位置
    LinkedList.prototype.indexOf = function (data) {
        let current = this.head;
        let index = 1; // 记录元素的位置
        while (current) { // 循环遍历链表
            if (current.data === data) { // 如果当前节点的值等于元素的值
                return index; // 返回位置
            } else { // 如果不等于,继续循环
                current = current.next;
                index  ;
            }
        }
        return -1; // 循环结束了,说明没找到
    }
    // 修改某个位置的元素
    LinkedList.prototype.update = function (position, data) {
        // 越界判断
        if (position <= 0 || position > this.length) return false;
        let current = this.head;
        let index = 1;
        while (index < position) {
            current = current.next;
            index  ;
        }
        current.data = data; // 修改数据
        return true;
    }
    LinkedList.prototype.removeAt = function (position) {
        if (position <= 0 || position > this.length) return false; // 越界判断
        let current = this.head;
        if (position === 1) { // 如果删除第一个位置上的节点(头节点)
            this.head = this.head.next;
        } else { // 删除其他位置的节点
            let index = 1; // 记录当前位置
            let previous = null;
            while (index < position) {
                previous = current;
                current = current.next;
                index  ;
            }
            // 上一个节点指向当前元素的下一个节点
            previous.next = current.next;
        }
        this.length--;
        return current; // 返回被删除的节点
    }
}

到此这篇关于JavaScript数据结构之链表各种操作详解的文章就介绍到这了,更多相关JS链表内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

JavaScript数据结构之链表各种操作详解的更多相关文章

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

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

  2. HTML5数字输入仅接受整数的实现代码

    这篇文章主要介绍了HTML5数字输入仅接受整数的实现代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. amaze ui 的使用详细教程

    这篇文章主要介绍了amaze ui 的使用详细教程,本文通过多种方法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  4. html5简介_动力节点Java学院整理

    这篇文章主要介绍了html5简介,用于指定构建网页的元素,这些元素中的大多数都用于描述网页内容,有兴趣的可以了解一下

  5. ios 8 Homescreen webapp,关闭和打开iPad停止javascript

    我有一个适用于iPad的全屏HTML5网络应用程序,并且刚刚安装了IOS8来试用它,它一切正常,直到你关闭并重新启动iPad.一旦web应用程序重新启动javascript就会停止并加载新页面不会重新启动它.在iPad上的Safari中打开同一页面时,关闭和打开iPad会继续按预期工作.其他人注意到了这个或想出了一个解决方案吗?解决方法这似乎是我在iOS8.1.1更新中解决的.

  6. iOS 6 javascript与object.defineProperty的间歇性问题

    当访问使用较新的Object.defineProperty语法定义属性的对象的属性时,有没有其他人注意到新iOS6javascript引擎中的间歇性错误/问题?https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty我正在看到javascript失败的情况,说

  7. ios – 如何使用JSExport导出内部类的方法

    解决方法似乎没有办法将内部类函数导出到javascript.我将内部类移出并创建了独立的类,它起作用了.

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

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

  9. ios – 内存泄漏与UIWebView和Javascript

    清楚地包含一个Javascript文件到我的HTML是使UIWebView泄漏内存.当我重复使用相同的UIWebView对象时,或者每当我有内容实例化一个新的漏洞时,会出现泄漏的事实,导致我认为必须有一些JavaScript文件被loadHTMLString处理,导致泄漏.有人知道如何解决这个问题吗?

  10. iOS应用程序的UI自动化测试如何与乐器和Javascript

    从WWDC2010视频会议中了解iOS应用程序的自动化UI测试,但没有实践.从代码项目project,我们可以有一个例子.这个问题在这里听到有涉及这个的人.任何限制?解决方法我建议从AlexWollmer开始使用thisblogpost.他创建了一个非常有用的JavaScript库:tuneup_jswithtest()函数,它允许测试分离和有用的帮助者以及为自动化仪器编写测试的断言.

随机推荐

  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受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部