JS数据结构与算法—链表

357 阅读3分钟

1 链表

链表和数组的特性

数组

优点:数组的创建通常需要申请一段连续的内存空间。每个元素都是连续紧邻分配,查找的时间复杂度是O(1),效率很高。

缺点:在使用数组时,在开始或特定索引处添加/删除元素这样的操作可能是一项性能较低的任务,因为我们必须移动所有其他元素的索引。

链表

优点:链表中的元素在内存中不必是连续的空间。可以充分利用计算机的内存, 实现灵活的内存动态管理.链表在插入和删除数据时, 时间复杂度可以达到O(1)。

缺点:链表访问任何一个位置的元素时, 都需要从头开始访问(无法跳过第一个元素访问任何一个元素)。并且无法通过下标直接访问元素,需要从头一个个访问,直到找到对应的问题。

  • 数据以查为主,很少涉及到增和删,选择数组。
  • 如果数据涉及到频繁的插入和删除,或元素所需分配空间过大,倾向于选择链表。

对链表的理解

  • 就像火车一样:火车头是head,火车尾是tail,每一节车厢是节点,车厢中乘客(数据),上一节车厢通过车钩(next)连接下一节车厢(节点),下一节车厢通过车钩(prev)连接上一节车厢。 单向链表: 只能从头遍历到尾或者从尾遍历到头。方便到达下一个节点,但是回到前一个节点是很难的。 双向链表:既可以从头遍历到尾,又可以从尾遍历到头。每次在插入或删除某个节点时,需要处理四个节点的引用,而不是两个, 也就是实现起来要困难一些。并且相当于单向链表, 必然占用内存空间更大一些。

2 单向链表封装

封装了一个单向链表,实现的方法代码中有注释。

//创建Node类
class Node {
    constructor(data) {
        this.data = data;
        this.next = null;
    }
}

class LinkedList {
    constructor() {
        this.head = null;
        this.length = 0;
    }
    //1.向链表尾部添加一个新的项
    append(data) {
        let node = new Node(data);
        if (this.head) {
            let current = this.head;
            while (current.next) {
                current = current.next;
            }
            current.next = node;
        } else {
            this.head = node;
        }
        this.length += 1;
    }
    //2.toString方法
    toString() {
        let current = this.head;
        let resString = '';
        while (current) {
            resString += current.data + ' ';
            current = current.next;
        }
        return resString;
    }
    //3.向链表特定位置插入一个新的项
    insert(position, data) {
        if (position < 0 || position > this.length) return false;
        let node = new Node(data);
        if (position == 0) {
            node.next = this.head;
            this.head = node;
        } else {
            let current = this.head;
            let previous = null;
            let index = 0;
            while (index++ < position) {
                previous = current;
                current = current.next;
            }
            previous.next = node;
            node.next = current;
        }
        this.length += 1;
        return true;
    }
    //4.获取对应位置的元素
    get(position) {
        if (position < 0 || position >= this.length) return null;
        let current = this.head;
        let index = 0;
        while (index++ < position) {
            current = current.next;
        }
        return current.data;
    }
    //5.返回元素在列表中的索引,如果没有则返回-1
    indexOf(data) {
        let current = this.head;
        let index = 0;

        while (current) {
            if (current.data == data) {
                return index;
            }
            current = current.next;
            index += 1;
        }
        return -1;
    }
    //6.修改某个位置的元素
    update(position, newData) {
        if (position < 0 || position >= this.length) return false;
        let current = this.head;
        let index = 0;
        while (index++ < position) {
            current = current.next;
        }
        current.data = newData;
        return true;
    }
    //7.从列表的特定位置移除一项
    removeAt(position) {
        if (position < 0 || position >= this.length) return null;
        let current = this.head;
        let previous = null;
        let index = 0;
        if (position == 0) {
            this.head = this.head.next;
        } else {
            while (index++ < position) {
                previous = current;
                current = current.next;
            }
            previous.next = current.next;
        }
        this.length -= 1;
        return current.data;
    }
    //8.从列表中移除一项
    remove(data) {
        let position = this.indexOf(data);
        return this.removeAt(position);
    }
    //9.size方法
    size() {
        return this.length;
    }
}

//测试代码---------------------------------------------------------------
let list = new LinkedList();
list.append('aaa');
list.append('bbb');
console.log(list.toString());
// console.log(list);
list.insert(0, 'ccc');
// console.log(list);
list.insert(1, 'ddd');
console.log(list);
console.log(list.get(3));
console.log(list.indexOf('aaa'));
console.log(list.indexOf('d'));
console.log(list);
list.update(3, '111');
console.log(list);
list.removeAt(2);
console.log(list.removeAt(2));
console.log(list);
// console.log(list.remove('aaa'));
list.remove('ccc');

console.log(list);
list.append('eee');
console.log(list.size());

3 双向链表封装

直接继承前面单向链表继续写双向链表

 class DoublyNode extends Node {
     constructor(data) {
         super(data);
         this.prev = null;
     }
 }
 class DoublyLinkedList extends LinkedList {
     constructor() {
         super();
         this.tail = null;
     }
     //1 append 向链表末尾添加元素
     append(data) {
         const newNode = new DoublyNode(data);
         if (this.head === null) {
             this.head = newNode;
             this.tail = newNode;
         } else {
             this.tail.next = newNode;
             newNode.prev = this.tail;
             this.tail = newNode;
         }
         this.length++;
     }
     //2 insert 向任意位置插入一个元素
     insert(position, data) {
         if (position < 0 || position > this.length) return false;
         let newNode = new DoublyNode(data);
         let current = this.head;
         if (position === 0) {
             if (this.head === null) {
                 this.head = newNode;
                 this.tail = newNode;
             } else {
                 newNode.next = this.head;
                 this.head.prev = newNode;

                 this.head = newNode;
             }
         } else if (position === this.length) {
             this.tail.next = newNode;
             newNode.prev = this.tail;
             this.tail = newNode;
         } else {
             let previous = null;
             let index = 0;
             while (index++ < position) {
                 previous = current;
                 current = current.next;
             }
             previous.next = newNode;
             newNode.next = current;
             newNode.prev = previous;
             current.prev = newNode;
         }
         this.length++;
         return true;
     }
     //3 get 获取对应位置的元素
     //直接继承单向链表LinkedList的get方法

     //4 indexOf 返回元素在列表中的索引,如果没有则返回-1
     //直接继承单向链表LinkedList的indexOf方法

     //5 remove 在列表的特定位置移除某项
     removeAt(position) {
         if (position < 0 || position >= this.length) return null;
         let current = this.head;

         if (position === 0) {
             if (this.length === 1) {
                 this.head = null;
                 this.tail = null;
             } else {
                 this.head = this.head.next;
                 this.head.prev = null;
             }
         } else if (position === this.length - 1) {
             current = this.tail;
             this.tail = this.tail.prev;
             this.tail.next = null;
         } else {
             let index = 0;
             let previous = null;
             while (index++ < position) {
                 previous = current;
                 current = current.next;
             }
             previous.next = current.next;
             current.next.prev = previous;
         }
         this.length--;
         return current.data;
     }
     // 6 update 更改某个位置的值,同样直接继承
     //7 remove  删除某个元素
     //8 toString 继承
     // 9 size 继承
 }
 // 测试代码----------------------------------------------------------------
 let dbList = new DoublyLinkedList();
 dbList.append('aaa');
 dbList.append('bbb');
 dbList.append('ccc');

 // console.log(dbList );
 dbList.insert(0, '111');
 dbList.insert(2, '222');

 // console.log(dbList );
 console.log(dbList.get(0));
 console.log(dbList.get(2));
 // console.log(dbList);
 console.log(dbList.indexOf('222'));
 // console.log(dbList);
 // console.log(dbList);
 console.log(dbList.removeAt(0));

 // console.log(dbList);
 console.log(dbList.removeAt(0));
 dbList.update(0, '555');
 dbList.remove('555');
 console.log(dbList);