JS数据结构之链表

109 阅读4分钟
原文链接: zhuanlan.zhihu.com
链表

多图预警:



/*jshint esversion: 6 */

/**
 * js中数组缺点: js中数组被实现为一个对象, 执行效率低下。
 * 链表:
 *  1. 执行效率高, 除了不能队数据进行随机访问, 适合于任意情况下的一维数组。
 *  2. 是由一组节点组成的集合,每个节点都有一个对象引用指向它的后继,指向另外一个节点的引用叫做链。
 *
 */

// 遍历链表: 跟随链, 从链表的首部遍历到尾部,不包括头节点(头节点一般用作链表的接入点)。



/**
 * @desc: 节点类
 *
 * @ADT模型
 * element属性: 节点元素
 * next属性: 指向后继节点的引用
 */
class Node {
    constructor(element) {
        this.element = element;
        this.next = null;
    }
}

/**
 * @desc: 链表类
 * @ADT模型
 *
 * find(element): 根据element查找其在链表中的节点
 * insert(): 在已知节点后面向链表插入一个元素
 * remove(): 删除一个节点
 * display(): 显示链表中的元素
 * advance(n): 使当前节点向前移动 n 个节点
 * show(): 显示当前节点上的数据
 */
class LinkedList {

    constructor() {
        // 头节点
        this.head = new Node('head');
        // 当前节点
        this.current_node = this.head;
    }

    // 搜索给定节点的前一个节点
    _findPrevious(element) {
        let current_node = this.head;
        while ((current_node.next !== null) && (current_node.next.element !== element)) {
            current_node = current_node.next;
        }
        return current_node;
    }

    find(element) {
        let current_node = this.head;
        while (current_node.element !== element) {
            current_node = current_node.next;
        }
        return current_node;
    }

    insert(newElement, element) {
        const newElementNode = new Node(newElement);
        const current_node = this.find(element);
        // 交互next引用
        newElementNode.next = current_node.next;
        current_node.next = newElementNode;
    }

    remove(element) {
        const prevNode = this._findPrevious(element);
        if (prevNode.next !== null) {
            prevNode.next = prevNode.next.next;
        }
    }

    display() {
        let current_node = this.head;
        while (current_node.next !== null) {
            console.log(current_node.next.element);
            current_node = current_node.next;
        }
    }

    advance(n) {
        if (n <= 0) {
            return;
        }
        let index = 0;
        while (index < n && this.current_node.next !== null) {
            this.current_node = this.current_node.next;
            index++;
        }
    }

    show () {
        return this.current_node.element;
    }
}


// test code
const linkedList = new LinkedList();
linkedList.insert("Conway", "head");
linkedList.insert("Russellville", "Conway");
linkedList.insert("Alma", "Russellville");
linkedList.display();
linkedList.remove("Russellville");
console.log('-----remove---------');
linkedList.display();
console.log('-----default---------');
console.log(linkedList.show());
console.log('-----advance 2---------');
linkedList.advance(2);
console.log(linkedList.show());

module.exports = LinkedList;



双向链表


/*jshint esversion: 6 */

/**
 * @desc: 双向链表
 * 正向/反向遍历更方便
 */


/**
 * @desc: 节点类
 *
 * @ADT模型
 * element属性: 节点元素
 * next属性: 指向后继节点的引用
 * previous属性: 指向前驱节点引用
 */

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

/**
 * @desc: 双向链表类
 * @ADT模型
 *
 * find(element): 根据element查找其在链表中的节点
 * insert(): 在已知节点后面向链表插入一个元素
 * remove(): 删除一个节点
 * display(): 显示链表中的元素
 * dispReverse(): 反序显示链表中元素
 * back(n): 在双向链表中向后移动 n 个节点
 * show(): 只显示当前节点
 * advance(n): 使当前节点向前移动 n 个节点
 */
class DLinkedList {

    constructor() {
        // 头节点
        this.head = new Node('head');
        // 当前节点
        this.current_node = this.head;
    }

    // 搜索最后一个节点
    _findLastNode() {
        let current_node = this.head;
        while (current_node.next !== null) {
            current_node = current_node.next;
        }
        return current_node;
    }

    find(element) {
        let current_node = this.head;
        while (current_node.element !== element) {
            current_node = current_node.next;
        }
        return current_node;
    }

    insert(newElement, element) {
        const newElementNode = new Node(newElement);
        const current_node = this.find(element);
        // 交互next,previous引用
        newElementNode.next = current_node.next;
        newElementNode.previous = current_node;
        current_node.next = newElementNode;
    }

    remove(element) {
        // 删除比单链的删除效率高, 不需要搜索前一个节点了。
        const current_node = this.find(element);
        if (current_node.next !== null) {
            current_node.previous.next = current_node.next;
            current_node.next.previous = current_node.previous;
            current_node.next = null;
            current_node.previous = null;
        }
    }

    display() {
        let current_node = this.head;
        while (current_node.next !== null) {
            console.log(current_node.next.element);
            current_node = current_node.next;
        }
    }

    dispReverse() {
        let lastNode = this._findLastNode();
        while (lastNode.previous !== null) {
            console.log(lastNode.element)
            lastNode = lastNode.previous;
        }
    }

    advance(n) {
        if (n <= 0) {
            return;
        }
        let index = 0;
        while (index < n && this.current_node.next !== null) {
            this.current_node = this.current_node.next;
            index++;
        }
    }

    back (n) {
        if (n <= 0) {
            return;
        }
        let index = 0;
        while (index < n && this.current_node.previous !== null) {
            this.current_node = this.current_node.previous;
            index++;
        }
    }

    show () {
        return this.current_node.element;
    }

}


// test code

const cities = new DLinkedList();
cities.insert("Conway", "head");
cities.insert("Russellville", "Conway");
cities.insert("Carlisle", "Russellville");
cities.insert("Alma", "Carlisle");
cities.display();
console.log('------');
cities.remove("Carlisle");
cities.display();
console.log('------');
cities.dispReverse();
console.log('-----default---------');
console.log(cities.show());
console.log('-----advance 3---------');
cities.advance(3);
console.log(cities.show());
console.log('-----back 2---------');
cities.back(2);
console.log(cities.show());


module.exports = DLinkedList;


循环链表


/*jshint esversion: 6 */


/**
 *
 * @desc: 循环链表: 创建节点时候, 使next都指向头节点, 形成一个闭环。
 * 循环链表也可以很方便从后往前遍历, 但是创建循环链表的开销小于双向链表。
 */


/**
 * @desc: 节点类
 *
 * @ADT模型
 * element属性: 节点元素
 * next属性: 指向后继节点的引用
 */
class Node {
    constructor(element) {
        this.element = element;
        this.next = null;
    }
}


/**
 * @desc: 循环链表类
 * @ADT模型
 *
 * find(element): 根据element查找其在链表中的节点
 * insert(): 在已知节点后面向链表插入一个元素
 * remove(): 删除一个节点
 * display(): 显示链表中的元素
 */
class LoopLinkedList {

    constructor() {
        // 头节点
        this.head = new Node('head');
        // 每个节点的next都指向头节点
        this.head.next = this.head;
    }

    // 搜索给定节点的前一个节点
    _findPrevious(element) {
        let current_node = this.head;
        while ((current_node.next !== null) && (current_node.next.element !== element)) {
            current_node = current_node.next;
        }
        return current_node;
    }

    find(element) {
        let current_node = this.head;
        while (current_node.element !== element) {
            current_node = current_node.next;
        }
        return current_node;
    }

    insert(newElement, element) {
        const newElementNode = new Node(newElement);
        const current_node = this.find(element);
        // 交互next引用
        newElementNode.next = current_node.next;
        current_node.next = newElementNode;
    }

    remove(element) {
        const prevNode = this._findPrevious(element);
        if (prevNode.next !== null) {
            prevNode.next = prevNode.next.next;
        }
    }

    display() {
        let current_node = this.head;
        while ((current_node.next !== null) && (current_node.next.element !== 'head')) {
            console.log(current_node.next.element);
            current_node = current_node.next;
        }
    }

}

// test code
const loopLinkedList = new LoopLinkedList();
loopLinkedList.insert("Conway", "head");
loopLinkedList.insert("Russellville", "Conway");
loopLinkedList.insert("Alma", "Russellville");
loopLinkedList.display();
loopLinkedList.remove("Russellville");
console.log('-----remove---------');
loopLinkedList.display();

module.exports = LoopLinkedList;


实践
/*jshint esversion: 6 */
// 循环链接解决约瑟夫环问题

/**
 * 写一段程序将 n 个人围成一圈,并且第 m 个人会被杀掉,计算 一圈人中哪两个人最后会存活。
 * 使用循环链表解决该问题。
*/



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

class LoopLinkedList {

    constructor() {
        // 头节点
        this.head = new Node('head');
        // 每个节点的next都指向头节点
        this.head.next = this.head;
        // 当前节点
        this.currentNode = this.head;
    }

    // 搜索给定节点的前一个节点
    _findPrevious(element) {
        let current_node = this.head;
        while ((current_node.next !== null) && (current_node.next.element !== element)) {
            current_node = current_node.next;
        }
        return current_node;
    }

    find(element) {
        let current_node = this.head;
        while (current_node.element !== element) {
            current_node = current_node.next;
        }
        return current_node;
    }

    insert(newElement, element) {
        const newElementNode = new Node(newElement);
        const current_node = this.find(element);
        // 交互next引用
        newElementNode.next = current_node.next;
        current_node.next = newElementNode;
    }

    remove(element) {
        const prevNode = this._findPrevious(element);
        if (prevNode.next !== null) {
            prevNode.next = prevNode.next.next;
        }
    }

    display() {
        let current_node = this.head;
        while ((current_node.next !== null) && (current_node.next.element !== 'head')) {
            console.log(current_node.next.element);
            current_node = current_node.next;
        }
    }

    // 往前移动n
    advance(n) {
        if (n <= 0) {
            return;
        }
        while (n > 0) {
            if (this.currentNode.next.element == 'head') {
                this.currentNode = this.currentNode.next.next;
            } else {
                this.currentNode = this.currentNode.next;
            }
            n--;
        }
    }

    // 计算链表个数
    count () {
        let num = 0;
        let current_node = this.head;
        while ((current_node.next !== null) && (current_node.next.element !== 'head')) {
            current_node = current_node.next;
            num++;
        }
        return num;
    }

}

// 假设10个人 3个一循环
const list = new LoopLinkedList();
for (let i = 1; i <= 10; i++) {
    if (i === 1) {
        list.insert(i, 'head');
    } else {
        list.insert(i, i - 1);
    }

}

console.log('count', list.count())
while (list.count() >= 3) {
    list.advance(3);
    list.remove(list.currentNode.element);
}

list.display(); // 4, 10