重学JavaScript数据结构之 “链表”

94 阅读1分钟

1.什么是链表

要存储多个元素。数组可能是最常用的数据结构,但是数组有一个缺点,它的大小是固定的,如果需要从数组中移除或者添加一个元素,需要移动元素的位置,成本比较高。

而链表是一个用来存储有序元素集合的数据结构,它与数组的区别是链表中的元素在内存中并不是连续放置的。

2.链表的方法

在创建一个链表时,首先我们要实现一个元素的Node类,这个类包含elementnext属性,element对应元素的值,next属性则是指向链表中下一个元素的指针,代码如下:

class Node {
    constructor(element) {
        this.element = element;
        this.next = undefined;
    }
}

下面我们将实现一个链表类LinkedList,该类包含headcount属性,head对应链表中的头部元素,count属性对应链表中元素的个数,代码如下:

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

通过上面的代码我们初始化好了元素和链表(装元素的容器),下面我们要做的就是往链表里面插入元素,我们将该方法定义为inset,该方法接收两个参数,分别是需要插入元素和插入的位置,代码如下:

inset(element, index) {
    const node = new Node(element);
    if (index >= 0 && index < this.count) {
        if (index === 0) {
            let current = this.head;
            node.next = current;
            this.head = node;
        } else {
            let pre = this.head;
            for (let i = 1; i <= index - 1; i++) {
                pre = pre.next;
            }
            node.next = pre.next;
            pre.next = node;
        }
        this.count++;
        return true;
    } else {
        return false;
    }
}

同样,我们也可以删除链表中任意位置的元素,我们将该方法定义为remove,该方法接收一个参数,参数为需要删除元素的位置,代码如下:

remove(index) {
    if (index === 0) {
        this.head = this.head.next;
    } else {
        let pre = this.head;
        for (let i = 1; i <= index - 1; i++) {
            pre = pre.next;
        }
        const current = pre.next;
        pre.next = current.next;
    }
    this.count--;
}

我们还需要一个方法返回特定位置的元素,我们将该方法定义为getElementAt,代码如下:

getElementAt(index) {
    let current = this.head;
    if (index === 0) {
        return current;
    } else {
        for (let i = 1; i <= index; i++) {
            current = current.next;
        }
    }
    return current;
}

返回元素在链表中的索引,若无对应元素则返回-1,我们将该方法定义为indexOf,代码如下:

indexOf(element) {
    let current = this.head;
    // current != null 等价于 current !== null  && current !== undefined
    for (let i = 1; i <= this.count && current != null; i++) {
        if (element === current.element) {
            return i;
        }
        current = current.next;
    }
    return -1;
}

返回链表的大小,我们将该方法定义为size,代码如下:

size() {
    return this.count;
}

链表是否为空,我们将该方法定义为isEmpty,代码如下:

isEmpty() {
    return this.size() === 0;
}

综上,可以利用getElementAt优化insertremove方法,最终代码为:

class LinkedList {
    constructor() {
        this.count = 0;
        this.head = undefined;
    }
    inset(element, index) {
        const node = new Node(element);
        if (index >= 0 && index < this.count) {
            if (index === 0) {
                let current = this.head;
                node.next = current;
                this.head = node;
            } else {
                let pre = this.getElementAt(index - 1);
                node.next = pre.next;
                pre.next = node;
            }
            this.count++;
            return true;
        } else {
            return false;
        }
    }
    remove(index) {
        if (index === 0) {
            this.head = this.head.next;
        } else {
            let pre = this.getElementAt(index - 1);
            const current = pre.next;
            pre.next = current.next;
        }
        this.count--;
    }
    getElementAt(index) {
        let current = this.head;
        if (index === 0) {
            return current;
        } else {
            for (let i = 1; i <= index; i++) {
                current = current.next;
            }
        }
        return current;
    }
    indexOf(element) {
        let current = this.head;
        // current != null 等价于 current !== null  && current !== undefined
        for (let i = 1; i <= this.count && current != null; i++) {
            if (element === current.element) {
                return i;
            }
            current = current.next;
        }
        return -1;
    }
    size() {
        return this.count;
    }
    isEmpty() {
        return this.size() === 0;
    }
}

3.双向链表

双向链表相对于链表的不同之处在于链表中的节点只是链接到了下一个节点,而双向链表中的元素链接是双向的,下面我们将继承链表的类实现双向链表:

DNode类:

class DNode extends Node {
    constructor(element) {
        super(element)
        this.prev = undefiend;
    }
}

DLinkedList类:

class DLinkedList extends LinkedList {
    constructor() {
        super()
        this.tail = undefiend;
    }
}

我们要完成双向链表还需要对链表的一些方法做重写(添加prevtail),首先是inset方法:

inset(element, index) {
    const node = new DNode(element);
    if (index >= 0 && index < this.count) {
        if (index === 0) {
            if (this.head == null) {
                this.head = node;
                this.tail = node;
            } else {
                node.next = this.head;
                this.head.prev = node;
                this.head = node;
            }
        } else {
            if (index === this.count - 1) {
                node.prev = this.tail;
                this.tail.next = node;
                this.tail = node;
            } else {
                let pre = this.getElementAt(index - 1);
                let current = pre.next;
                node.prev = pre;
                node.next = current;
                pre.next = node;
                current.prev = node;
            }
        }
        this.count++;
        return true;
    } else {
        return false;
    }
}

remove方法:

remove(index) {
    if (index === 0) {
        this.head = this.head.next;
        if (this.count === 1) {
            this.tail = undefined;
        } else {
            this.head.prev = undefined;
        }
    } else {
        if (index === this.count - 1) {
            this.tail = this.tail.prev;
            this.tail.next = undefined;
        } else {
            let current = this.getElementAt(index);
            let pre = current.prev;
            let next = current.next;
            pre.next = next;
            next.prev = pre;
        }
        pre.next = current.next;
    }
    this.count--;
}

4.循环链表

循环链表可以是单向(普通链表),也可以是双向(双向链表),它们之间唯一的区别就是循环链表最后一项的next指针总是指向链表中的第一项,而不再是undefined,下面我们在双向链表的基础上实现它,代码如下:

class CLinkedList extends DLinkedList {
    constructor() {
        super()
    }
    inset(element, index) {
        const node = new DNode(element);
        if (index >= 0 && index < this.count) {
            if (index === 0) {
                if (this.head == null) {
                    this.head = node;
                    this.tail = node;
                    this.head.prev = this.tail;
                    this.tail.next = this.head;
                } else {
                    node.next = this.head;
                    this.head.prev = node;
                    this.head = node;
                    this.head.prev = this.tail;
                    this.tail.next = this.head;
                }
            } else {
                if (index === this.count - 1) {
                    node.prev = this.tail;
                    this.tail.next = node;
                    this.tail = node;
                    this.tail.next = this.head;
                    this.head.prev = this.tail;
                } else {
                    let pre = this.getElementAt(index - 1);
                    let current = pre.next;
                    node.prev = pre;
                    node.next = current;
                    pre.next = node;
                    current.prev = node;
                }
            }
            this.count++;
            return true;
        } else {
            return false;
        }
    }
    remove(index) {
        if (index === 0) {
            if (this.count === 1) {
                this.head = undefined;
                this.tail = undefined;
            } else {
                this.head = this.head.next;
                this.head.prev = this.tail;
                this.tail.next = this.head;
            }
        } else {
            if (index === this.count - 1) {
                this.tail = this.tail.prev;
                this.tail.next = this.head;
                this.head.prev = this.tail;
            } else {
                let current = this.getElementAt(index);
                let pre = current.prev;
                let next = current.next;
                pre.next = next;
                next.prev = pre;
            }
            pre.next = current.next;
        }
        this.count--;
    }
}