js数据结构 -- 链表, 双向链表,循环链表

数组作为js常用的数据结构,存取元素都非常的方便,但是其内部的实现原理跟其他计算机语言对数组的实现都是一样的。

由于数组在内存中的存储是连续的,所以当在数组的开头或者中间插入元素时,内部都需要去移动其他元素的位置,这个移动元素的成本很高。

链表也是存储有序的元素集合,但不同于数组,链表中的元素在内存中的存放并不是连续的,每个元素有一个存储元素自身的节点和一个指向下一个元素引用(也称为指针)的节点组成。

优点:相对于传统的数组,向链表中插入和移除元素,都不要移动其他任何元素。

缺点:要想访问链表中间的一个元素,需要从 (表头)开始迭代列表直到找到 所需的元素。

链表的基本骨架:

function linkedList() {
    var Node = function(ele) {
        this.ele = ele;
        this.next = null;
    }
    var length = 0var head = null;  // 用来存储第一个元素的引用

    // 对外暴露的方法
    this.append = (ele) => {};  //添加元素
    this.insert = (pos, ele) => {};  // 向固定位置插入元素
    this.removeAt = (pos) => {};  // 移除固定位置的元素
    this.remove = () => {};    // 移除结尾元素
    this.indexOf = (ele) => {};  // 返回元素所在位置
    this.isEmpty = () => {};  // 链表是否为空
    this.size = () => {};  // 返回链表的长度
    this.toString = () => {}; // 返回
    this.print = () => {};  // 打印链表数据
}

链表的具体实现

向链表尾部追加元素
向LinkedList对象尾部添加一个元素时,可能有两种场景:
1. 列表为空,添加的是第一个元素
2. 或者列表不为空,向其追加元素

this.append = function(element){
   var node = new Node(element), //{1}
              current; //{2}
   if (head === null){ // 列表中的第一个节点 //{3}
       head = node;
   } else {
       current = head; //{4}
       //  循环列表直到找到链表的最后一项,        
       while(current.next){
            current = current.next;
       }
        //   找到链表的最后一项,将其next赋为node,建立连接    
       current.next = node; //{5}
   }
   length++; // 更新链表的长度   //{6}
};

从链表中移除元素
从链表中移除某个元素,也可能有两种场景:
1. 移除第一个元素
2. 移除第一个元素以外的其他元素

// 移除特定位置的元素
this.removeAt = (pos) => {
    // 检查边界条件
    if (pos > -1 && pos < length) {
        let current = head;  // head指向第一个元素
        let previous = null;  // 存储前一个节点
        let index = 0;  // 计数

        // 移除第一项
        if (pos === 0) {
            head = current.next; // 第二个元素
        } else {
            while (index++ < pos) {
                previous = current;
                current = current.next;
            }
            previous.next = current.next; // 将目标删除元素的前一个元素的next指向目标删除元素的后一个元素
        }
        return current.ele;  // 返回要删除节点的值
    } else {
        return null;  // 输入不合法
    }
}

要从列表中移除当前元素,要做的就是将previous.next和current.next连接起来,当前元素就会被丢弃在计算机内存中,等待着被垃圾回收机制回收

在任意位置插入元素
同样考虑两种情况插入元素
1. 链表首部插入节点
2. 链表手部除外的其他位置插入节点

this.insert = (pos, ele) => {
    // 首先检测边界值
    if (pos >=0 && pos <= length) {
        const node = new Node(ele);  // 创建节点
        let previous = null;  // 存储前一个节点
        let current = head; // 目标位置节点
        let index = 0;

        // 链表的起点插入一个值
        if (pos === 0) {
            node.next = current;
            head = node;
        } else {
            while (index++ < pos) {
                previous = current;
                current = current.next;
            }
            node.next = current;
            previous.next = node;
        }
        length++; // 更新链表的长度
        return true; // 插入成功
    } else {
        return false;
    }
}

其他方法的实现

this.toString = () => {
    let current = head;
    let rets = '';
    let index = 0;
    while (current) {
        rets += current.ele;
        current = current.next;
    }
    return rets;
}
this.indexOf = (ele) => {
    let current = head;
    let index = -1;
    while (current) {
        if (current.ele === ele) {
            return index++;
        }
        index++;
        current = current.next;
    }
    return index;
}
this.remove = (ele) => {
    var pos = this.indexOf(ele); // 返回元素所在的位置
    this.removeAt(pos);
}
this.isEmpty = () => {
    return length === 0;
}
this.size = () => {
    return length;
}

双向链表

双向链表和普通链表的区别在于,在普通链表中, 一个节点只有指向下一个节点的连接,而在双向链表中,链接是双向的:一个指向下一个元素, 另一个指向前一个元素

双向链表的实现

function DoublyLinkedList() {
    var Node = function(ele) {
        this.ele = ele;
        this.prev = null; // 新增的
        this.next = null;
    }
    var length = 0var head = null;  // 用来存储第一个元素的引用
    var tail = null;  // 用来存储最后一个元素的引用  新增的

    // 对外暴露的方法
    this.append = (ele) => {};  //添加元素
    this.insert = (pos, ele) => {};  // 向固定位置插入元素
    this.removeAt = (pos) => {};  // 移除固定位置的元素
    this.remove = () => {};    // 移除结尾元素
    this.indexOf = (ele) => {};  // 返回元素所在位置
    this.isEmpty = () => {};  // 链表是否为空
    this.size = () => {};  // 返回链表的长度
    this.toString = () => {}; // 返回
    this.print = () => {};  // 打印链表数据
}

双向链表提供了两种迭代列表的方法:从头到尾,或者反过来。我们也可以访问一个特定节 的下一个或前一个元素。在单向链表中,如果迭代列表时错过了要找的元素,就需要回到列表起点,重新开始迭代。这是双向链表的一个优点。

插入方法实现

this.insert = (pos, ele) {
    // 检查边界条件
    if (pos > -1 && pos <= length) {
        let node = new Node(ele);
        let current = head; // 链表第一个节点
        let previous = null;
        let index = 0;
        // 链表首部新增
        if (pos === 0) {
            if(!head) {
               head = node;
               tail = node;
            } else {
                node.next = current;
                current.prev = node;
                head = node;
            }
        } else if(pos === length) {  // 最后一项
            current = tail;
            current.next = node;
            node.pre = current;

            tail = node; // 更新最后一项
        } else {
            // 中间位置
            while (index++ < pos) {
                previous = current;
                current = current.next;
            } 
            previous.next = node;
            node.next = current;

            current.prev = node; // 新增
            node.prev = previous;  // 新增
        }
        length++;   // 更新链表长度
        return true;
    } else {
        return false;
    }
}

从任意位置移除元素算法实现
需要处理三种场景:
1. 删除链表第一个元素
2. 删除链表最后一个元素
3. 删除链表中间某个元素

this.removeAt = (pos) => {
    // 检查边界值
    if (pos > -1 && pos <= length) {
        let current = head;
        let previous = null;
        let index = 0;

        // 移除第一个元素
        if (pos === 0) {
            head = current.next;
            if(!head){ // 只有一个元素
                tail = null;
            } else {
                head.prev = null;
            }
        } else if (pos === length) {
            // 移除最后一个元素
            current = tail;
            tail = current.prev;
            tail.next = null;
        } else {
            // 中间位置
            while (index++) {
                previous = current;
                current = current.next; 
            }
            previous.next = current.next;
            current.next.prev = previous;
        }
        length--;   // 更新链表长度
        return true;
    } else {
        return false;
    }
}

循环链表

循环链表可以向链表一样单向引用,也可以像双向链表一样有双向引用。循环链表于链表的唯一区别是:循环链表的最后一个元素指向下一个元素的指针不是null,而是指向第一个元素(head);

总结: 链表相比数组最大的优点是,无需移动链表中的元素,就可以轻松实现元素的添加和删除。因此,当需要添加和删除很多元素时,最好的选择是链表而非数组

你可能感兴趣的:(Javascript,js数据结构,链表,双向链表,循环链表)