美女面试官问我链表的CURD,我彻底懵圈了……

本文深入讲解了链表的定义、组成及节点结构,包括头结点和首元结点。阐述了单向链表和双向链表的特性,并详细介绍了它们的增删查改操作。提供了具体的JavaScript实现,帮助理解链表的基本操作。同时,文章还提及了链表在前端开发中的应用。

欢迎和号主【前端点线面】进群盘算法,此外本号干货满满:14个门类(100+篇原创)内容(又干又硬)《前端百题斩》pdf(助力薪资double)20+篇思维导图(知识系统化、记忆简单化),进摸鱼群

一、基础

一、定义

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

二、相关概念

一个完整的链表需要由以下几个部分组成:

  1. 头指针:一个普通的指针,它的特点是永远指向链表第一个结点的位置;

  2. 结点:节点包含三类:头结点、首元节点和其它节点。

(1)头结点(非必须):一个不存任何数据的空节点,通常作为链表的第一个节点;

(2)首元结点:链表中第一个存有数据的节点称为首元结点;

(3)其它结点:链表中的其它结点

be06a7c5ce957286a357ce080714bda5.png
image-20220328203505736.png

三、结点包含内容

链表中每个节点包含两个部分:

  1. 数据域:存储数据元素本身;

  2. 指针域:指向直接后续元素的指针

e3a17b0b0c7bc56919fee4822b377dd8.png
image-20220328200136679.png

二、链表分类及相关操作

链表存在很多种类,下面重点讲述单向链表、双向链表的结点结构,以及其对应的CURD(添加、更改、查找、删除)。

2.1 单向链表

对于单链表的相应编程,我们均按照默认头指针指向首元结点这样的结构进行代码实现。

868b96857cadc31cef689b100baf6ac0.png

一、结点结构

结点作为链表的重要组成部分,其结构可用如下代码表示:

function ListNode(val, next) {
    this.val = val;
    this.next = next === undefined ? null : next;
}

扩展:如何根据一个数组创建链表

function createLinkedList(arr) {
    const head = new ListNode(arr[0]);

    let current = head;
    for (let i = 1; i < arr.length; i++) {
        const node = new ListNode(arr[i]);
        current.next = node;
        current = current.next;
    }

    return head;
}

二、遍历(查找)

在链表中查找指定数据元素,其思路是从表头依次遍历表中节点,用被查找元素与各结点中存储的数据元素进行对比,直到对比成功或遍历至链表最末端的null。

// 查找
function selectVal(head, val) {
    let current = head;
    // 判断是否为null
    while (current) {
        // 判断是否对比成功
        if (current.val === val) {
            return current;
        }

        current = current.next;
    }

    return null;
}

三、添加

向链表中添加元素,根据添加位置不同,可分为3中情况:

  1. 插入到链表的头部

  2. 插入到链表中间的某个位置

  3. 插入到链表的最末端,作为链表中最后一个数据元素

插入思想

  1. 将新结点的next指针指向插入位置后的结点

  2. 将插入位置前结点的next指针指向插入结点

function insertVal(head, val, add) {
    const newNode = new ListNode(add);
    // 查找插入节点
    const currentNode = selectVal(head, val);

    if (!currentNode) {
        return null;
    }
    // 1. 将新结点的next指针指向插入位置后的节点
    newNode.next = currentNode.next;
    // 2. 将插入位置前节点的next指针指向插入节点
    currentNode.next = newNode;

    return head;
}

四、删除

删除的元素的时候要注意链表的结构,注意有没有空值的头结点,有头结点的时候删除的时候就不需要判断删除的是不是第一个值,否则需要进行判断

function delVal(head, val) {
    // 当一个结点也不存在时,直接返回空
    if (!head) {
        return null;
    }

    // 如果删除的是第一个节点,直接将head指向第二个节点
    if (head.val === val) {
        head = head.next;
        return head;
    }

    // 如果删除的不是第一个节点
    let current = head;
    // 找到待删除元素的前一个值
    while (current.next && current.next.val !== val) {
        current = current.next;
    }

    if (current.next) {
        // 将删除结点的前一个结点的next值直接指向删除结点的下一个节点
        current.next = current.next.next;
    }

    return head;
}

五、更改

更新链表中的元素,只需要通过遍历找到存储此元素的节点,对节点中的数据域做更改操作即可。

function updateVal(head, val, newVal) {
    let current = head;

    while (current) {
        if (current.val === val) {
            current.val = newVal;
            return head;
        }
        current = current.next;
    }

    return null;
}

2.2 双向链表

单链表只有一个方向,从头结点到尾结点,双向链表指各个节点之间的逻辑关系是双向的,其结点结构和链表结构如下所示:

  1. 结点结构

ecbc54fc155558b2533f06f021131136.png
image-20220330194222686.png
  1. 链表结构

4ee629cd451e3b78e995814808a8b1d5.png
image-20220330193934911.png

一、结点结构

双向链表的节点结构相比于单向链表,多了一个prev属性,如下所示:

function ListNode(val, prev, next) {
    this.val = val;
    this.prev = prev === undefined ? null : prev;
    this.next = next === undefined ? null : next;
}

二、遍历(查找)

双向链表的查找和单向链表的查找类似,都是遍历链表。

function selectVal(head, val) {
    let current = head;
    while (current) {
        if (current.val === val) {
            return current;
        }

        current = current.next;
    }

    return null;
}

三、添加

在某个节点后插入结点,其思想是:

  1. 找到该插入结点;

  2. 将新结点的next指针指向插入位置后的结点;

  3. 将新结点的prev指针指向插入位置的结点;

  4. 将插入节点的next指针指向新结点

/**
 * 插入(在某个节点后插入)
 */
function insertVal(head, val, add) {
    const newNode = new ListNode(add);

    // 查找插入节点
    const currentNode = selectVal(head, val);

    if (!currentNode) {
        return null;
    }

    // 1. 将新结点的next指针指向插入位置后的结点
    newNode.next = currentNode.next;
    // 2. 将新结点的prev指针指向插入位置的结点
    newNode.prev = currentNode;
    // 3. 将插入节点的next指针指向新结点
    currentNode.next = newNode;

    return head;
}

四、删除

双链表删除结点时,只需要遍历链表找到要删除的链表,然后将该链表从表中摘除即可。

(注:针对头指针直线的结点需要做特殊处理,否则head永远指向的是原始的第一个节点)

/**
 * 删除
 * 
 * 双链表删除结点时,只需要遍历链表找到要删除的链表,然后将该链表从表中摘除即可
 */
function delVal(head, val) {
    let current = head;

    while (current) {
        if (current.val === val) {
            if (current.next) {
                current.next.prev = current.prev;
            }

            if (current.prev) {
                current.prev.next = current.next;
            } else {
                // 针对头指针直线的结点需要做特殊处理,否则head永远指向的是原始的第一个节点
                head = current.next;
            }
            return head;
        }
        current = current.next;
    }

    return null;
}

五、更改

更新链表中的元素,只需要通过遍历找到存储此元素的节点,对节点中的数据域做更改操作即可。

/**
 * 更新
 * 
 * 更新链表中的元素,只需要通过遍历找到存储此元素的节点,对节点中的数据域做更改操作即可
 */
function updateVal(head, val, newVal) {
    let current = head;

    while (current) {
        if (current.val === val) {
            current.val = newVal;
            return head;
        }

        current = current.next;
    }

    return null;
}

··············· 执鸢者简介 ·················

看号主详细信息,来这

瞅一瞅

bed256bc068699570e6c89edc2255051.png

畅所欲言交流

【1】前端百题斩系列

【2】Vue系列

【3】React系列

【4】前端基础系列

【5】基础知识

【6】浏览器系列

【7】构建工具系列

【8】网络系列

【9】Node系列

【10】源码系列

【11】前端有意思

【12】手撕算法系列

【13】个人总结

【14】杂科

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值