JS数据结构与算法 —— 链表

本文深入讲解链表这一数据结构,包括其概念、分类、应用场景及如何使用JavaScript实现单向链表,涵盖添加、插入、移除等核心操作。

概念:链表是计算机的一种存储结构。链表由一系列结点组成,每个结点里包含了本结点的数据域和指向下一个结点的指针。如下图item存储数据,next存储下一个节点的引用,其中最后一个节点的指针指向了null,表示链表结束的位置。

作用:按一定顺序储存数据,允许在任意位置插入和删除结点。

分类:单向链表、双向链表、单向循环链表和双向循环链表

应用场景适用于需要频繁进行插入和删除操作的数据存储。

js实现一个单向链表

我们需要定义两个类,一个是Node类表示节点,一个是LinkedList类表示链表;LinkedList类里有head(头节点)和length(链长)两个属性,有几个操作方法,如append(添加),insert(插入),remove(移除),removeAt(根据下标移除),isEmpty(链表是否为空),size(链表大小),display(查看链表)。

定义Node类,表示节点

class Node{
    constructor(element){
        this.element = element;    // 数据         
        this.next = null;          // 指针
    }
}

定义LinkedList类,表示链表(为了便于阅读,已把LinkedList类的方法提取出来展示)

class LinkedList{
    constructor(){
        this.head = null;       // 列表头
        this.length = 0;        // 链表长度
    }
}

当链表为空时,即链表头head为null,下面实现链表的各操作方法

append(element)  新增元素

新增元素分为两种情况,链表为空和链表不为空。链表为空时,新增的元素作为链表头(直接赋值);链表不为空时,从链表尾添加,即原链表尾的指针next指向新增元素。

append(element){
    let node = new Node(element);   // 调用辅助类创建一个新结点
    // 链表为空
    if(this.head == null){   
        this.head = node;    // 把第一个添加的结点作为链表头
    }
    // 已存在结点
    else{
        let current = this.head;        // 当前正在查找的节点(从头部开始查找)
        while(current.next!==null){     // 当前查找项的指针不为null(即不是最后一项),继续往后找,直到next=null循环结束
            current = current.next;     // 把下一项赋值给当前查找项,即查找下一项(循环的经典方法)
        }
        // while循环结束后,current已经到了最后一项(current.next=null)
        current.next = node;            // 插入新元素
    }
    length++;   // 长度加1
}

insert(position,element)  插入元素

根据下标找到目标节点,从目标节点之插入新元素,插入元素分为两种情况,头部插入和非头部插入。头部插入(下标等于0),即插入的元素作为链表头;非头部插入,找到目标节点和目标节点的前一个节点(我们称之为前节点),使前节点的指针指向插入节点,而插入节点的指针指向目标节点,完成插入。如下图是非头部插入示意图,node2为目标节点,node1为前节点,node3为插入节点,使node1的指针指向node3,而node3的指针指向node2,即完成了插入操作。

insert(element,position){
    let node = new Node(element);           // 定义一个新结点
    // 越界
    if(position>-1 && position<length){
        let current = this.head;            // 先把原链表头赋值给current
        // 链表头插入
        if(position==0){                    
            this.head = node;               // 插入的新节点作为链表头
            this.head.next = current;       // 链表头的指针指向原链表头,完成插入
        }
        // 非链表头插入
        else{
            let index = 0;                  // 当前查找项的下标
            let previous = null;            // 查找项的前一项
            // 当前项的下标小于目标项的下标时,继续循环
            while(index < position){        
                previous = current;         // 查找下一个节点
                current = current.next;     
                index++;                    // 下标加1
            }
            // 直到index=position时,即找到了目标项
            previous.next = node;           // 目标项的前一项的指针指向插入项
            node.next = current;            // 插入项的指针指向目标项,完成插入
        }
        length++;       // 长度加1
    }
}

removeAt(position)  根据下标移除元素

 根据下标移除元素也分为两种情况,移除链表头和移除非链表头节点。移除链表头节点(下标等于0),即使原链表头的指针指向的节点作为新链表头;移除非链表头节点,根据下标找到需要移除的目标节点和它的前一个节点,使前结点的指针 指向 目标节点的指针所指向的节点,即完成了移除目标节点。如下图是移除非链表头节点示意图,node1为目标节点,head为前节点,node2为node1指针指向的节点,使head的指针指向node2,即完成了移除node1节点的操作。

removeAt(position){
    if(position>-1 && position<length){
        let current = this.head;  
        // 移除链表头
        if(position==0){                    
            this.head = current.next;       // 使原表头的指针变成新表头
        }
        // 移除非链表头
        else{
            let index = 0;                  // 当前项的下标
            let previous = null;            // 当前查找项的前一个结点
            // 当前结点的下标小于指定结点下标时,继续往后查找
            while(index<position){         
                previous = current;         // 查找下一个节点
                current = current.next;     
                index++;
            }
            // 当index = position 时跳出循环,即找到了要移除的那一项
            previous.next = current.next;   // 跳过当前项,使前一项的指针指向当前项的指针
        }
        length--;   //长度减1
        return current;         //返回移除项
    }
}

indexOf(element)  查找某元素的下标

从头部开始查找,如果当前查找节点等于目标节点就返回对应的下标,否则继续往后查找;若链表循环结束了还没找到,则返回-1(不存在)。

indexOf(element){
    let index = 0;                          // 当前下标
    let current = this.head;                // 从头部开始查找
    while(current !== null){                // 当前项不等于null,表示还不是最后一项,继续循环
        if(element === current.element){    // 如果当前节点的值等于目标项
            return index;                   // 返回对应下标
        }
        current = current.next;             // 否则继续往后查找
        index++;                            // 下标加1
    }
    return -1;       // 若循环结束还没找到,返回-1,链表里不存在该元素
}

remove(element)  移除元素(代码复用)

     1)调用indexOf(element)方法找到目标项的下标;

     2)调用removeAt(position)方法根据目标项的下标移除目标项

remove(element){
    return this.removeAt(this.indexOf(element));
}

isEmpty()  判断链表是否为空

isEmpty(){
    return length === 0;
}

size()  查询链表的大小

size(){
    return length;
}

查看完整源码

更多数据结构相关,请查看专栏:《JavaScript数据结构与算法》

(完)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值