前端内练基础-你应该知道的数据结构之链表

THIS

前言:为什么要学数据结构,它能帮助我们什么?能解决什么问题呢,首页数据结构并不是一门具体的编程语言,它教会我们的是一种思维方式,即如何以更优的方式存储数据和解决的一些问题,通过学习数据结构,可以大大拓宽我们的思维模式。掌握了数据结构与算法,我们看待问题的深度、解决问题的角度会大有不同,对于个人逻辑思维的提升,也是质的飞跃。

下面从以下几个点出现逐一的了解他们实现的原理和场景:

  • 什么是栈
  • 什么是队列
  • 什么是链表
  • 什么是集合
  • 什么是字典
  • 什么 二叉树

什么是链表

    要储存多个元素、数组或者列表可能是最常用的数据结构,但是这种数据结构有个缺点:数组的大小都是固定的,移动元素和插入元素的成本很高;

    链表存储是有序的元素集合,不同于数组的是链表的元素在内存中并不是连续的,每个元素是由一个元素的本身的节点指向下一个元素的引用。如下图所示:

image.png

如何实现一个链表

  • 创建一个LinkedList的类骨架
  • 创建一个连接链表数据结构的Node类
  • 丰富链表的数据方法
创建一个LinkedList的类

初始三个值:

  • count: 代表链表的元素数量
  • head: 代表链表的数据结构
  • equalsFn: 使用一个内部函数,代表链表中的元素是否相等
const defaultEquals = (a, b) => a === b

class LinkedList {
    constructor(equalsFn = defaultEquals){
        this.count = 0;  // 
        this.head = undefined
        this.equalsFn = equalsFn
    }
}

创建一个Node的类

初始两个值:

  • element: 代表加入链表元素的值
  • next: 代表指向下一个链表的指针
class Node  {
    constructor(element){
        this.element = element
        this.next = undefined
    }
}

丰富链表的方法
  • push(element):向链表尾部添加一个新元素。
  • insert(element, position):向链表的特定位置插入一个新元素。
  • getElementAt(index):返回链表中特定位置的元素。如果链表中不存在这样的元素, 则返回 undefined
  • remove(element):从链表中移除一个元素。
  • indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回-1
  • removeAt(position):从链表的特定位置移除一个元素。
  • isEmpty():如果链表中不包含任何元素, 返回 true,如果链表长度大于 0则返回 false。
  • size():返回链表包含的元素个数,与数组的 length 属性类似。
class LinkedList {
    constructor(equalsFn = defaultEquals){
        this.count = 0;
        this.head = undefined
        this.equalsFn = equalsFn
    }

    push(element) {
        const node = new Node(element)
        let current; 

        // 第一次进入是否为空
        if(this.head == null) {
            this.head = node
        }else {
            
            // 记录当前的引用关系
            current = this.head;

            while (current.next != null) {

                current = current.next;
            }

            // 将next 赋予新元素、建立链接
            current.next = node 
        }

        // 每次更新链表的长度
        this.count ++ 
    }

    // 返回链表中特定位置的元素
    getElementAt(index){
        
        if(index > 0 && index <= this.count ) {

            let node = this.head;
            let j = 0;
            
            while(j < index && node != null) {
                // 不断进行赋值
                node = node.next
                j++
            }
            return node;
        }

        return undefined
    }

    // 移除链表指定位置
    removeAt(index) {

        // 检查是否过界
        if(index >= 0 && index < this.count ) {
            
            // 保留对象的引用
            let current = this.head

            // 移除第一项
            if(index === 0 ) {

                this.head = current.next;
            }else {
                const previous = this.getElementAt(index - 1)
                current = previous.next;
                
                // 建立previous和current的下一项链接起来、起到一个移动的作用
                previous.next = current.next
            }

            // 移除完毕、进行长度减一
            this.count-- 
            return current.element

        }
        return undefined
    }

    // 项链表插入一个新元素
    insert(element, index ){
        if(index >=0 && index <= this.count) {

            const node = new Node(element);

            if(index === 0) {
                
                // 第一个位置添加
                const current = this.head;
                node.next = current
                this.head = node;
            }else {
                // 找到当前的链表位置
                const previous = this.getElementAt(index - 1);

                // 链表的下一个引用
                const current = previous.next;
                
                // 新添加的链表节点 进行连接
                node.next = current;

                // 更新整个链表
                previous.next = node;
            }

            // 更新长度
            this.count ++ 
            return true
        }

        return false

    }

    // 返回元素在链表中的索引
    indexOf(element) {
        let current = this.head;

        for (let i = 0; i< this.count && current != null; i++) {
            
            if(this.equalsFn(element, current.element)) {
                return i
            }

            // 不断的更新current的值。
            current = current.next
        }
        return -1
    }

    // 移除元素
    remove (element) {
        const index = this.indexOf(element)
        return this.removeAt(index)
    }

    // 获取链表的个数
    size () {
        return this.count
    }

    // 链表是否为空
    isEmpty(){
        
        return this.size() === 0
    }

    // 获取head 方法
    getHead() {
        return this.head
    }
}
测试
const nodes = new LinkedList()

nodes.push(10)
nodes.push(20)
nodes.push(30)
nodes.push(40)
nodes.push(50)
nodes.push(60)

console.log(nodes.getHead())

结果如下图:

image.png

移除指定的链表位置:

const nodes = new LinkedList()

nodes.push(10)
nodes.push(20)
nodes.push(30)
nodes.push(40)
nodes.push(50)
nodes.push(60)

// 移除制定位置的链表
nodes.removeAt(4)

console.log(nodes.getHead())

结果如下图:

image.png

那么链表可以解决什么问题?

参考力扣的原题为例,试着用链表的思维来解决

  来自力扣83题删除排序链表中的重复元素

image.png

题解:

const nodes = new LinkedList()

nodes.push(10)
nodes.push(10)
nodes.push(20)
nodes.push(40)
nodes.push(40)
nodes.push(60) 

var deleteDuplicates = function({ head }) {
    // 保持引用关系
    let cur = head;

    while(cur && cur.next) {
    
        // 层级向下比较
        if(cur.element === cur.next.element) {
            
            // 跟移除元素类似
            cur.next = cur.next.next
        }else {
            cur = cur.next
        }
    }

    return head
};

let result = deleteDuplicates(nodes)
console.log(result)

以上就是简单回顾了一下链表的基本实现和场景应用、在实际的需求场景中还有很链表结构类型、比如:双向链表循环链表有序链表等等只有深入的了解其内部的实现方式和方法才能在应付复杂的项目中大展手脚。

最后

本文系列参照《学习JavaScript数据结构与算法第3版》进行的整理归纳、希望能帮助大家。

阅读 169

杨老头的小书桌
平常记录项目问题的坑坑洼洼

多读书、多看报、少吃零食、多睡觉

716 声望
9 粉丝
0 条评论
你知道吗?

多读书、多看报、少吃零食、多睡觉

716 声望
9 粉丝
文章目录
宣传栏