JavaScript实现链表

149 阅读3分钟

一、单向链表

链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有的语言称为指针或连接)组成。类似于火车头,一节车厢载着乘客(数据),通过节点连接另一节车厢。
重点:单向链表由一个头组成

链表的优势:

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

链表的缺点:

链表访问任何一个位置的元素时,都需要从头开始访问(无法跳过第一个元素访问任何一个元素)。无法通过下标值直接访问元素,需要从头开始一个个访问,直到找到对应的元素。虽然可以轻松地到达下一个节点,但是回到前一个节点是很难的。

1.1 链表的实现

// 节点
class CreateNode {
    constructor(data) {
        this.data = data
        this.next = null
    }
}

// 链表
class LinkedList {
    constructor() {
        this.heade = null
        this.length = 0
    }

    append(data) {
        // 创建一个节点
        let newNode = new CreateNode(data)
        // 如果没有节点
        if (this.length == 0) {
            // 将链表头关联到此节点
            this.heade = newNode
        } else {
            // 已有节点,设置个变量保存头节点
            let current = this.heade
            // 循环找下一个节点,如果为空就是到底了。
            while (current.next) {
                current = current.next
            }
            current.next = newNode
        }
        //长度 + 1
        this.length++
    }

    toString() {
        let current = this.heade,
            stringLsit = '';
        while (current) {
            stringLsit += current.data + ' '
            current = current.next
        }
        return stringLsit
    }
    
    insert(index, data) {
        // 不符合返回false
        if (index < 0 || index > this.length) return false

        let node = new CreateNode(data);

        // 是否是头
        if (index == 0) {
            node.next = this.heade
            this.heade = node
        } else {
            let idx = 0,
                previous = null,
                current = this.heade;
            while (idx++ < index) {
                // 从头开始寻找
                previous = current
                current = current.next
            }

            // 插入
            node.next = current
            previous.next = node
        }
        this.length++
    }

    get(index) {
        if (index < 0 || index >= this.length) return null
        let current = this.head,
            idx = 0;
        while (idx++ < index) {
            current = current.next
        }
        return current.data
    }

    indexOf(data) {
        let current = this.head,
            idx = 0;

        // 循环寻找
        while (current) {
            if (current.data == data) {
                return idx
            }
            current = current.next
            idx += 1
        }

        // 未找到
        return null
    }

    update(index, newData) {
        if (index < 0 || index >= this.length) return false
        // 查找正确的节点
        let current = this.head,
            idx = 0;
        while (idx++ < index) {
            current = current.next
        }

        // 更改值
        current.data = newData
        return true
    }

    removeAt(index) {
        // 不存在
        if (index < 0 || index >= this.length) return false
        // 删除元素
        let current = this.head
        if (position == 0) {
            //情况2:position > 0时
            this.head = this.head.next
        } else {
            let idx = 0,
                previous = null;
            while (idx++ < position) {
                previous = current
                current = current.next
            }
            // 前一个节点的next指向下一个节点的next
            previous.next = current.next
        }
        // length-1
        this.length -= 1

        return current.data
    }
}

二、双向链表

双向链表:既可以从头遍历到尾,又可以从尾遍历到头。也就是说链表连接的过程是双向的,它的实现原理是:一个节点既有向前连接的引用,也有一个向后连接的引用。

双向链表的缺点:

每次在插入或删除某个节点时,都需要处理四个引用,而不是两个,实现起来会困难些;相对于单向链表,所占内存空间更大一些;但是,相对于双向链表的便利性而言,这些缺点微不足道。

2.1 双向链表实现

// 节点
class Node {
    constructor(data) {
        this.data = data
        this.next = null
        this.prev = null
    }
}

class DoubleLinklist {
    constructor() {
    	// 双向链表拥有头与尾
        this.length = 0
        this.head = null
        this.tail = null
    }

    append(data) {
    	// 创建节点
        let node = new Node(data)
        if (this.length == 0) {
            // 没有节点的话 头尾都是新节点
            this.head = node
            this.tail = node
        } else {
            node.prev = this.tail // 新节点的上一个就是尾节点
            this.tail.next = node // 旧尾节点的下一个是新节点
            this.tail = node // 替换尾节点为新节点
        }
        // length一定要加
        this.length++
    }

    // 双向链表 便捷于 头与尾
    // 其他方法参考单向链表 
}