使用JS实现单链表数据结构

370 阅读2分钟

数组不总是组织数据的最佳数据结构,原因如下。在很多编程语言中,数组的长度是固定 的,所以当数组已被数据填满时,再要加入新的元素就会非常困难。在数组中,添加和删 除元素也很麻烦,因为需要将数组中的其他元素向前或向后平移,以反映数组刚刚进行了 添加或删除操作。然而,JavaScript 的数组并不存在上述问题,因为使用 split() 方法不需 要再访问数组中的其他元素了。 JavaScript 中数组的主要问题是,它们被实现成了对象,与其他语言(比如 C++ 和 Java) 的数组相比,效率很低(请参考 Crockford 那本书的第 6 章)。 如果你发现数组在实际使用时很慢,就可以考虑使用链表来替代它。除了对数据的随机访 问,链表几乎可以用在任何可以使用一维数组的情况中。如果需要随机访问,数组仍然是 更好的选择。-- by 数据结构与算法Javascript描述

链表的实现

节点

class Node {
    constructor(data) {
        this.data = data; // 存贮每个节点的数据
        // this.prev = null;
        this.next = null; // 指向下一个节点
    }
}

单链表

// 单项链表
class SingleList {
    constructor() {
        this.size = 0;
        this.head = new Node("head")
    }

    // 在单链表中寻找item元素
    find(item) {
        let curNode = this.head;
        while (curNode && curNode.data != item) {
            curNode = curNode.next;
        }
        return curNode
    }

    // 获取单链表的最后一个节点
    findLast() {
        var curNode = this.head;
        while (curNode.next) {
            curNode = curNode.next;
        }
        return curNode;
    }

    // 向单链表中插入元素(默认插入第一位)
    insert(item = this.head.data, data) {
        if (data == null) return false;
        var targetNode = this.find(item);
        if (!targetNode) return false;
        let dataNode = new Node(data);
        dataNode.next = targetNode.next;
        targetNode.next = dataNode;
        this.size++;
        return true;
    }

    // 在单链表中删除一个节点
    remove(item) {
        if (item == this.head.data || item == null) return false;
        // 先找到目标的前一个结点 curNode
        let curNode = this.head;
        while (curNode && curNode.next && curNode.next.data != item) {
            curNode = curNode.next;
            // 当curNode遍历到倒数第二个位置时,还没找到item,就直接设为null打断循环
            if (curNode.next == null) {
                curNode = null;
            }
        }
        // 如果没有找到前一个节点
        if (!curNode) return false;
        // 找到前一个结点的话
        let target = curNode.next;
        curNode.next = target.next;
        target.next = null;
        this.size--;
        return true;
    }

    // 在单链表的尾部添加元素
    append(item) {
        if (item == null) return false;
        var lastNode = this.findLast();
        let newNode = new Node(item);
        lastNode.next = newNode;
        this.size++;
        return true
    }

    // 判断单链表是否为空
    isEmpty() {
        return this.size
    }

    // 单链表的遍历显示
    display() {
        if (!this.size) return;
        let curNode = this.head;
        while (curNode) {
            console.log(curNode.data)
            curNode = curNode.next;
        }
    }

    // 清空单链表
    clear() {
        this.head.next = null;
        this.size = 0;
    }
}

此外,有些单链表可能有环结构,比如最后一个节点,指向中间的某一节点,构成闭环。此时需要一个方法来检测,我这里采用了快慢指针。 step1:构造环

var myList = new SingleList()
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G']

arr.map(item => myList.append(item))

var C = myList.find('C')
var G = myList.findLast()
G.next = C

step2:使用快慢指针来判断是否有环,若快指针走到最后为null,说明没有环,若两个指针在某个时刻相等了,则说明有环。

function isLoop(list) {
    // 使用快慢指针
    var p = list.head
    var q = list.head

    while (q) {
        p = p.next
        if (q.next) {
            q = q.next.next
        } else {
            q = null
        }
        if (p === q) {
            console.log('有环')
            return
        }
    }
    console.log('没有环')
}

isLoop(myList)  // 有环