前端算法基础-链表

简介: 前端算法基础-链表

链表

在计算机科学中, 一个 链表 是数据元素的线性集合, 元素的线性顺序不是由它们在内存中的物理位置给出的。 相反, 每个元素指向下一个元素。它是由一组节点组成的数据结构,这些节点一起,表示序列。

简单的说, 链表由无数个节点组成,而节点=当前节点内容+下一个节点,当到了结尾,下一个节点指向null空指针。

image.png

分类

单向链表

一般为了标识链表的头部,会在第一个节点加上head标识:

image.png

双向链表

在计算机科学中, 一个 双向链表(doubly linked list) 是由一组称为节点的顺序链接记录组成的链接数据结构。每个节点包含两个字段,称为链接,它们是对节点序列中上一个节点和下一个节点的引用。开始节点和结束节点的上一个链接和下一个链接分别指向某种终止节点,通常是前哨节点或null,以方便遍历列表。如果只有一个前哨节点,则列表通过前哨节点循环链接。它可以被概念化为两个由相同数据项组成的单链表,但顺序相反。

image.png

循环链表

循环链表同时又可以区分为 单向循环链表、双向循环链表

image.png

优缺点

  • 优点:适合动态插入和删除的应用场景
  • 缺点:不能快速的定位和随机访问数据

应用场景(非前端)

从网上搜索来的几种应用场景:

  • LRU 缓存淘汰算法。
  • 各类缓冲池 和 栈 的实现。
  • 使用链接来解决哈希冲突的哈希表。
  • 简单的内存分配器。
  • FAT文件系统中大文件的元数据。

基本上,前端都没有场景可以应用到链表数据结构,所以很多时候前端开发者学习链表

代码实现

在JavaScript,并没有直接以链表为结构的数据类型,所以如果我们要在JavaScript实现一个链表,需要自己去实现。

一个完整链表结构LinkNode 包括几块:

  1. 属性:节点内容+下一个节点指向next
  2. 方法:新增、删除等方法

单向链表

class LinkNode {
    constructor(value) {
        this.value = value;
        this.next = null;
    }
    toString() {
        return this.value;
    }
}
class LinkList {
    // 构造函数
    constructor() {
        this.head = null;
        this.length = 0;
    }
    // 追加
    append(value) {
        const node = new LinkNode(value);
        if (this.head === null) {
            this.head = node;
        } else {
            let current = this.head;
            // 将当前节点挪到最后
            while (current.next) {
                current = current.next;
            }
            current.next = node;
        }
        this.length++;
        return this;
    }
    // 在指定对象插入链表节点
    insert(existValue, insertValue) {
        const node = new LinkNode(insertValue);
        const existNode = this.find(existValue);
        if (existNode === null) {
            throw new Error("插入的节点值不存在");
        }
        const nextNode = existNode.next;
        existNode.next = node;
        node.next = nextNode;
        this.length++;
        return this;
    }
    // 删除指定对象 返回删除节点
    remove(value) {
        if (this.head === null) {
            throw new Error("链表为空,无法删除");
        }
        // 第一个删除
        if (this.head.value === value) {
            const current = this.head;
            this.head = this.head.next;
            current.next = null;
            this.length--;
            return current;
        }
        // 查找前一个
        const preNode = this.findPre(value)
        if (preNode !== null) {
            const removeNode = preNode.next;
            const nextNode = removeNode.next;
            preNode.next = nextNode;
            removeNode.next = null;
            this.length--;
            return removeNode;
        }
    }
    // 查询
    find(value) {
        if (this.head === null) {
            return null;
        }
        let current = this.head;
        while (current.next && current.value !== value) {
            current = current.next
        }
        if (current.value === value) {
            return current;
        }
        return null;
    }
    // 查询值的前一个
    findPre(value) {
        let current = this.head;
        let prevNode = null;
        while (current.next && current.next.value !== value) {
            current = current.next;
        }
        if (current.next && current.next.value === value) {
            prevNode = current;
        }
        return prevNode
    }
    toString() {
        if (this.head === null) {
            return null;
        }
        let current = this.head;
        const array = [];
        while (current.next) {
            array.push(current.value);
            current = current.next;
        }
        array.push(current.value);
        return array.join('->');
    }
}
// 测试案例
const linkList = new LinkList();
linkList.append("1");
linkList.append("2");
linkList.append("3");
// 预期输出1->2->3
console.log(linkList.toString());
linkList.insert("2", "4");
// 预期输出1->2->4->3
console.log(linkList.toString());
linkList.remove("2");
// 预期输出1->4->3
console.log(linkList.toString());

双向链表

相比较单向链表,多了一个指向前者对象 prev

class LinkNode {
    constructor(value) {
        this.value = value;
        this.next = null;
        this.prev = null;
    }
    toString() {
        return this.value;
    }
}
class LinkList {
    // 构造函数
    constructor() {
        this.head = null;
        this.length = 0;
    }
    // 追加
    append(value) {
        const node = new LinkNode(value);
        if (this.head === null) {
            this.head = node;
        } else {
            let current = this.head;
            // 将当前节点挪到最后
            while (current.next) {
                current = current.next;
            }
            current.next = node;
            node.prev = current;
        }
        this.length++;
        return this;
    }
    // 在指定对象插入链表节点
    insert(existValue, insertValue) {
        const node = new LinkNode(insertValue);
        const existNode = this.find(existValue);
        if (existNode === null) {
            throw new Error("插入的节点值不存在");
        }
        const nextNode = existNode.next;
        existNode.next = node;
        node.next = nextNode;
        node.prev = existNode;
        nextNode.prev = node;
        this.length++;
        return this;
    }
    // 删除指定对象 返回删除节点
    remove(value) {
        if (this.head === null) {
            throw new Error("链表为空,无法删除");
        }
        // 第一个删除
        if (this.head.value === value) {
            const current = this.head;
            this.head = this.head.next;
            current.next = null;
            this.length--;
            return current;
        }
        // 查找前一个
        const preNode = this.findPre(value)
        if (preNode !== null) {
            const removeNode = preNode.next;
            const nextNode = removeNode.next;
            const prevNode = removeNode.prev;
            preNode.next = nextNode;
            nextNode.prev = preNode;
            removeNode.next = null;
            removeNode.prev = null;
            this.length--;
            return removeNode;
        }
    }
    // 查询
    find(value) {
        if (this.head === null) {
            return null;
        }
        let current = this.head;
        while (current.next && current.value !== value) {
            current = current.next
        }
        if (current.value === value) {
            return current;
        }
        return null;
    }
    // 查询值的前一个
    findPre(value) {
        let current = this.head;
        let prevNode = null;
        while (current.next && current.next.value !== value) {
            current = current.next;
        }
        if (current.next && current.next.value === value) {
            prevNode = current;
        }
        return prevNode
    }
    toString() {
        if (this.head === null) {
            return null;
        }
        let current = this.head;
        const array = [];
        while (current.next) {
            array.push(current.value);
            current = current.next;
        }
        array.push(current.value);
        return array.join('<->');
    }
}
// 测试案例
const linkList = new LinkList();
linkList.append("1");
linkList.append("2");
linkList.append("3");
// 预期输出 1<->2<->3
console.log(linkList.toString());
linkList.insert("2", "4");
// 预期输出 1<->2<->4<->3
console.log(linkList.toString());
linkList.remove("2");
// 预期输出 1<->2<->3
console.log(linkList.toString());

循环链表

循环链表对比之前,会把最后一个节点的netx指向第一个节点, 第一个节点的prev指向最后一个节点

class LinkNode {
    constructor(value) {
        this.value = value;
        this.next = null;
        this.prev = null;
    }
    toString() {
        return this.value;
    }
}
class LinkList {
    // 构造函数
    constructor() {
        this.head = null;
        this.length = 0;
    }
    // 追加
    append(value) {
        const node = new LinkNode(value);
        if (this.head === null) {
            this.head = node;
        } else {
            let current = this.head;
            // 将当前节点挪到最后 由于循环 这里需要多判断一个是否指向头部
            while (current.next && current.next !== this.head) {
                current = current.next;
            }
            current.next = node;
            node.prev = current;
            // 将最后一个指针指向第一个
            node.next = this.head;
            this.head.prev = node;
        }
        this.length++;
        return this;
    }
    // 在指定对象插入链表节点
    insert(existValue, insertValue) {
        const node = new LinkNode(insertValue);
        const existNode = this.find(existValue);
        if (existNode === null) {
            throw new Error("插入的节点值不存在");
        }
        const nextNode = existNode.next;
        existNode.next = node;
        node.next = nextNode;
        node.prev = existNode;
        nextNode.prev = node;
        this.length++;
        return this;
    }
    // 删除指定对象 返回删除节点
    remove(value) {
        if (this.head === null) {
            throw new Error("链表为空,无法删除");
        }
        // 第一个删除
        if (this.head.value === value) {
            const current = this.head;
            this.head = current.next;
            this.head.prev = current.prev;
            current.next = null;
            current.prev = null;
            this.length--;
            return current;
        }
        // 查找前一个
        const preNode = this.findPre(value)
        if (preNode !== null) {
            const removeNode = preNode.next;
            const nextNode = removeNode.next;
            const prevNode = removeNode.prev;
            prevNode.next = nextNode;
            nextNode.prev = preNode;
            removeNode.next = null;
            removeNode.prev = null;
            this.length--;
            return removeNode;
        }
    }
    // 查询
    find(value) {
        if (this.head === null) {
            return null;
        }
        let current = this.head;
        while (current.next != this.head && current.value !== value) {
            current = current.next
        }
        if (current.value === value) {
            return current;
        }
        return null;
    }
    // 查询值的前一个
    findPre(value) {
        let current = this.head;
        let prevNode = null;
        while (current.next != this.head && current.next.value !== value) {
            current = current.next;
        }
        if (current.next && current.next.value === value) {
            prevNode = current;
        }
        return prevNode
    }
    toString() {
        if (this.head === null) {
            return null;
        }
        let current = this.head;
        const array = [];
        while (current.next != this.head) {
            array.push(current.value);
            current = current.next;
        }
        array.push(current.value);
        return array.join('<->');
    }
}
// 测试案例
const linkList = new LinkList();
linkList.append("1");
linkList.append("2");
linkList.append("3");
// 预期输出 1<->2<->3
console.log(linkList.toString());
linkList.insert("2", "4");
// 预期输出 1<->2<->4<->3
console.log(linkList.toString());
linkList.remove("2");
// 预期输出 1<->2<->3
console.log(linkList.toString());
// 第一个的prev 指向最后 输出3
console.log(linkList.head.prev.toString());
// 最后一个 next指向第一个 输出1
console.log(linkList.head.prev.next.toString());

源码地址放在 Github上,地址是:github.com/qiubohong/f…

目录
相关文章
|
14天前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
76 29
|
14天前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
72 25
|
4月前
|
算法
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
94 1
|
4月前
|
算法 索引
❤️算法笔记❤️-(每日一刷-141、环形链表)
❤️算法笔记❤️-(每日一刷-141、环形链表)
71 0
|
4月前
|
算法
【❤️算法笔记❤️】-(每日一刷-876、单链表的中点)
【❤️算法笔记❤️】-(每日一刷-876、单链表的中点)
72 0
|
4月前
|
算法
【❤️算法笔记❤️】-每日一刷-23、合并 K 个升序链表
【❤️算法笔记❤️】-每日一刷-23、合并 K 个升序链表
48 0
|
4月前
|
存储 算法
【❤️算法笔记❤️】-每日一刷-21、合并两个有序链表
【❤️算法笔记❤️】-每日一刷-21、合并两个有序链表
156 0
|
2月前
|
机器学习/深度学习 前端开发 算法
婚恋交友系统平台 相亲交友平台系统 婚恋交友系统APP 婚恋系统源码 婚恋交友平台开发流程 婚恋交友系统架构设计 婚恋交友系统前端/后端开发 婚恋交友系统匹配推荐算法优化
婚恋交友系统平台通过线上互动帮助单身男女找到合适伴侣,提供用户注册、个人资料填写、匹配推荐、实时聊天、社区互动等功能。开发流程包括需求分析、技术选型、系统架构设计、功能实现、测试优化和上线运维。匹配推荐算法优化是核心,通过用户行为数据分析和机器学习提高匹配准确性。
174 3
|
3月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
3月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法

热门文章

最新文章

  • 1
    【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
  • 2
    【Java若依框架】RuoYi-Vue的前端和后端配置步骤和启动步骤
  • 3
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 4
    【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 5
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 6
    详解智能编码在前端研发的创新应用
  • 7
    巧用通义灵码,提升前端研发效率
  • 8
    【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 9
    智能编码在前端研发的创新应用
  • 10
    【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程