链表数据结构

要储存多个元素,js中数组可能是最常用的数据结构,但是从数组的起点或中间插入或者移除项的成本很高,因为需要移动元素。
链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置,而是每个元素由一个存储元素本身的节点和指向下一个元素的引用组成,这是正常的链表,双向链表比正常的多了一个指向前一个的元素引用,循环链表可以是单向引用,也可以是双向引用,循环链表的最后一个元素指向下一个元素的指针不是undefined而是第一个元素head.
image
画的图有点渣,这是一个双向的循环链表。
创建一个最简单的链表

function defaultEquals(a,b){
    return a===b;
}
class Node{
    constructor(el){
        this.el = el;
        this.next = undefined;
    }
}
class LinkedList{//单向链表
    constructor(equals = defaultEquals){
        this.count = 0;
        this.head = undefined;
        this.equalsFn = equals;
    }
    push(el){
        const tail = this.getElAt(this.count-1);
        if(tail.next){
            throw '循环链表不支持此方法!';
            return;
        }
        const node = new Node(el);
        let current;
        if(!this.head){
            this.head = node;
        }else{
            current = this.head;
            while(current.next){
               current = current.next;
            }
            current.next = node;
        }
        this.count++;
    }
    getElAt(index){//根据位置获取元素
         if(index>=0 && index<this.count){
            let current = this.head;
            for(let i=0;i<index && current;i++){
                current = current.next;
            }
            return current;
         }
         return undefined;
    }
    removeAt(index){//根据位置移除
        if(index>=0 && index<this.count){
            let current = this.head;
            if(index===0){
                this.head = current.next;
            }else{
                let previous=this.getElAt(index-1);//获取前一个
                current = previous.next;         
                previous.next = current.next;
            }
            this.count--;
            return current.el;
        }
        return undefined;
    }
    insert(el,index){//任意位置插入
        if(index>=0 && index<=this.count){
            const node = new Node(el);
            if(index===0){
                const current = this.head;
                node.next = current;
                this.head = node;
            }else{
                const previous = this.getElAt(index-1);
                const current = previous.next;
                node.next = current;
                previous.next = node;
            }
            this.count++;
            return true;
        }
        return false;
    }
    indexOf(el){
        let current = this.head;
        for(let i=0;i<this.count;i++){
            if(this.equalsFn(current.el,el)){
                return i;
            }
            current = current.next;
        }
        return -1;
    }
    remove(el){
        let index = this.indexOf(el);
        return this.removeAt(index);
    }
    size(){
        return this.count;
    }
    isEmpty(){
        return this.size() === 0;
    }
    getHead(){
        return this.head;
    }
    toString(){
        if(!this.head){
            return '';
        }
        let str = `${this.head.el}`;
        let current = this.head.next;
        for(let i=0;i<this.size() && current;i++){
            str=`${str}${current.el}`;
            current = current.next;
        }
        return str;
    }
}

双向链表,增加一个指向前一个元素的引用

class DoublyNode extends Node{
    constructor(el,next,prev){
        super(el);
        this.prev = prev;
    }
}
class DoublyLinkedList extends LinkedList{
    constructor(equals = defaultEquals){
       super(equals);
       this.tail = undefined;
    }
     push(el){
        const node = new DoublyNode(el);
        let current;
        if(!this.head){
            this.head = node;
            
        }else{
            current = this.head;
            while(current.next){
               current = current.next;
            }
            current.next = node;
            node.prev = current;
        }
        this.tail = node;
        this.count++;
    }
    insert(el,index){
        if(index>=0 && index<=this.count){
            const node = new DoublyNode(el);
            let current = this.head;
            if(index===0){
                if(!this.head){
                    this.head = node;
                    this.tail = node;
                }else{
                    node.next = current;
                    current.prev = node;
                    this.head = node;
                }
            }else if(index === this.count){
                current = this.tail;
                node.prev = current;
                current.next = node;
                this.tail = node;
            }else{
                const previous = this.getElAt(index-1);
                current = previous.next;
                previous.next = node;
                node.prev = previous;
                node.next = current;
                current.prev = node;
            }
            this.count++;
            return true;
        }
        return false;
    }
    removeAt(index){
        if(index>=0 && index<this.count){
            let current = this.head;
            if(index===0){
                this.head = current.next;
                if(this.count===1){
                    this.tail=undefined;
                }else{
                    this.head.prev = undefined;
                }
            }else if(index===this.count-1){
                current = this.tail;
                this.tail = current.prev;
                this.tail.next = undefined;
            }else{
                current = this.getElAt(index);
                const previous = current.prev;
                previous.next = current.next;
                current.next.prev = previous;
            }
            this.count--;
            return curren.el;
        }
        return undefined;
    }
}

循环链表,单向 最后一个元素指向下一个元素指针tail.next不是undefined而是第一个元素head,双向,是第一个元素上一个引用head.prev不是undefined而是指向最后tail.

class CircularLinkedList extends LinkedList{
    constructor(equalsFn=defaultEquals){
        super(equalsFn)
    }
   
    insert(el,index){
        if(index>=0 && index<=this.count){
            const node = new Node(el);
            const current = this.head;
            if(index==0){
                if(!this.head){
                    this.head = node;
                    node.next = this.head;
                }else{
                    node.next = current;
                    current = this.getElAt(this.size()-1);
                    this.head = node;
                    current.next = this.head;
                }
            } else {
                const previous = this.getElAt(index-1);
                node.next = previous.next;
                previous.next = node;
            }
            this.count++;
            return true;
        }
        return false;
    }
    removeAt(index){
        if(index>=0 && index<this.count){
            let current = this.head;
            if(index===0){
                if(this.size()==1){
                    this.head = undefined;
                }else{
                    const removed = this.head;
                    current = this.getElAt(this.size()-1);
                    this.head = this.head.next;
                    current.next = this.head;
                    current = removed;
                }
            }else{
                const previous = this.getElAt(index-1);
                current = previous.next;
                previous.next = current.next;
            }
            this.count--;
            return current.el;
        }
        return undefined;
    }
}

有序链表,根据自己规则生成比较函数,来将元素插入正确的位置保证链表的有序性。

const Compare = {
    LESS_THAN:-1,
    BIGGER_THAN:1
}
function defultCompare(a,b){
    if(a===b){
        return 0;
    }
    return a<b?Compare.LESS_THAN:Compare.BIGGER_THAN;
}
class SortedLinkedList extends LinkedList{
    constructor(equalsFn=defaultEquals,compareFn=defultCompare) {
        super(equalsFn)
        this.compareFn = compareFn;
    }
    insert(el){
        if(this.isEmpty()){
            return super.insert(el,0);
        }else{
            const pop = this.getIndexNextSortedEl(el);
            return super.insert(el,pop);
        }
    }
    getIndexNextSortedEl(el){
        let current = this.head;
        let i = 0;
        for(;i<this.size() && current;i++){
            const comp = this.compareFn(el,current.el);
            if(comp === Compare.LESS_THAN){
                return i;
            }
            current = current.next;
        }
        return i;
    }
}

有序链表不能使用push方法,不然无法保证有序性了。
可以使用链表来创建其他数据结构,比如栈,队列,双向队列。
链表相对数组最重要优点,就是无需移动链表中的元素就可以轻松添加删除元素,所以当需要频繁添加和删除很多元素时,最好的选择是链表而非数组。
该内容借鉴与学习javascript数据结构与算法


Charon
57 声望16 粉丝

世界核平