JavaScript数据结构与算法之 "链表"

链表数据结构

  • 链表是存储有序元素的集合,但不同于数组,链表中的元素在内存中并不是连续放置的
  • 链表中的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用组成
  • 链表的优点:添加元素或移除元素的时候不需要移动其他元素,因此向链表中添加和删除元素会比数组快
  • 链表的缺点:要想访问链表中间的一个元素,需要从起点开始迭代链表直到找到所需要的元素,因此查询链表中的元素会比数组慢
  • 链表结构在现实生活中的列子:火车,拉链

帮助函数或类

  • 下面的类或函数是公用的
  • 用于比较两个元素相等的函数:const defaultEquals = (a, b) => a === b;
  • 判断某个元素是否存在:
    const isExist = (element) => {
        return element !== undefined && element !== null;
    };
    
  • 基本链表的节点
    /*链表节点类*/
    class Node {
        constructor(element) {
            this.element = element;
            this.next = undefined;  // next存储下一个节点的引用
        }
    }
    
  • 双向链表的节点
    /*双向链接节点类*/
    class DoublyNode extends Node {
        constructor(element, next, prev) {
            super(element, next);
            this.prev = prev;  // 指向上一个节点的链接
        }
    }
    

基本的链表

  • 基本链表的方法:
    • push(element):向链表尾部添加一个新元素
    • insert(element,index):向链表的特定位置插入一个新元素
    • getElementAt(index):返回链表中特定位置的元素。如果链表中不存在这个元素则返回undefined
    • remove(element):从链表中移除一个元素
    • indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回-1
    • removeAt(index):从链表中的特定位置移除一个元素
    • getHead():获取链表的第一个元素
    • isEmpty(): 判断链表中是否有元素,没有返回true,有方法false
    • clear(): 清空链表
    • size(): 返回链表中元素的个数
    • toString()
  • 代码
    /*创建链表的类*/
    class LinkedList {
        // equalsFn 比较相等的函数
        constructor(equalsFn = defaultEquals) {
            this.count = 0;  // 链表元素的计数器
            this.head = undefined; //链表的头部
            this.equalsFn = equalsFn; //比较相等的函数
        }
    
        //  push(element):向链表尾部添加一个新元素
        push(element) {
            const node = new Node(element);
            let current;  //当前节点
    
            if (!isExist(this.head)) {
                this.head = node;
            } else {
                current = this.head;
                // 如果current的下一个元素存在则将current指向下一个元素
                while (isExist(current.next)) {
                    current = current.next;
                }
                // 循环结束或current会指向当前列表的最后一个元素,将其next赋为新元素,建立链接
                current.next = node;
            }
    
            this.count += 1;
        }
    
        //  insert(element,index):向链表的特定位置插入一个新元素
        insert(element, index) {
            // 如果index越界
            if (!(index >= 0 && index < this.count)) {
                return false;
            }
    
            // index 没有越界
            const node = new Node(element);
            if (index === 0) {
                node.next = this.head;
                this.head = node;
            } else {
                const previous = this.getElementAt(index - 1);
                const current = previous.next;
                previous.next = node;
                node.next = current;
            }
    
            this.count += 1;
            return true;
        }
    
        //  getElementAt(index):返回链表中特定位置的元素。如果链表中不存在这个元素则返回undefined
        getElementAt(index) {
            // 如果index越界
            if (!(index >= 0 && index < this.count)) {
                return undefined;
            }
    
            // index 没有越界
            let current = this.head;
    
            // 循环找到指定index位置的元素
            for (let i = 1; i <= index && isExist(current); i += 1) {
                current = current.next;
            }
    
            return current;
        }
    
        //  remove(element):从链表中移除一个元素
        remove(element) {
            const index = this.indexOf(element);
            return this.removeAt(index);
        }
    
        //  indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回-1
        indexOf(element) {
            let current = this.head;
    
            //循环找到链表中和传入的元素相等的元素的索引
            for (let i = 0; i < this.count && isExist(current); i++) {
                if (this.equalsFn(element, current.element)) {
                    return i;
                }
                current = current.next;
            }
    
            // 没有相等元素返回-1
            return -1;
        }
    
        //  removeAt(index):从链表中的特定位置移除一个元素
        removeAt(index) {
            // 如果index越界
            if (!(index >= 0 && index < this.count)) {
                return undefined;
            }
    
            // index 没有越界
            let current = this.head;
    
            // 移除index位置的元素
            if (index === 0) {
                this.head = current.next;
            } else {
                const previous = this.getElementAt(index - 1); //找到index-1位置的元素
                current = previous.next; // 找到index位置的元素
                previous.next = current.next; // 移除index位置的元素
            }
    
            this.count -= 1;
            return current.element;
        }
    
        // getHead():获取链表的第一个元素
        getHead() {
            return this.head;
        }
    
        //  isEmpty(): 判断链表中是否有元素,没有返回true,有方法false
        isEmpty() {
            return this.count === 0;
        }
    
        //  clear(): 清空链表
        clear() {
            this.count = 0;
            this.head = undefined;
        }
    
        //  size(): 返回链表中元素的个数
        size() {
            return this.count;
        }
    
        //  toString()
        toString() {
            if (!isExist(this.head)) {
                return '';
            }
    
            let str = `${this.head.element}`;
            let current = this.head.next;
            for (let i = 1; i < this.size() && isExist(current); i += 1) {
                str = `${str},${current.element}`;
                current = current.next;
            }
    
            return str;
        }
    }
    

双向链表

  • 双向链表和普通链表的区别在于,在普通链表中一个节点只有指向下一个节点的链接;而在双向链表中,链接是双向的:一个指向下一个元素,另一个指向前一个元素
  • 双向链表提供了两种迭代的方法:从头到尾,或者从尾到头。
  • 我们也可以访问一个节点的前一个元素和下一个元素
  • 双向链表的方法:
    • push(element):向双向链表尾部添加一个新元素
    • insert(element,index):向双向链表的任意位置插入一个新元素
    • getElementAt(index):返回双向链表中特定位置的元素。如果双向链表中不存在这个元素则返回undefined
    • remove(element):从双向链表中移除一个元素
    • indexOf(element):返回元素在双向链表中的索引。如果双向链表中没有该元素则返回-1
    • removeAt(index):从双向链表中的特定位置移除一个元素
    • getHead():获取双向链表的第一个元素
    • getTail():获取双向链表的最后一个元素
    • isEmpty(): 判断双向链表中是否有元素,没有返回true,有方法false
    • clear(): 清空双向链表
    • size(): 返回双向链表中元素的个数
    • toString()
  • 注意:代码中没有重写的方法,将使用继承自父类的方法
  • 代码
    class DoublyLinkedList extends LinkedList {
       constructor(equalsFn = defaultEquals) {
           super(equalsFn);
           this.tail = undefined;  // 双向链表的尾部元素
       }
    
       // push(element):向双向链表尾部添加一个新元素
       push(element) {
           const node = new DoublyNode(element);
    
           if (!isExist(this.head)) {
               this.head = node;
               this.tail = node;
           } else {
               this.tail.next = node;
               node.prev = this.tail;
               this.tail = node;
           }
    
           this.count += 1;
       }
    
       //  insert(element,index):向双向链表的任意位置插入一个新元素
       insert(element, index) {
           // 如果index越界
           if (!(index >= 0 && index <= this.count)) {
               return false;
           }
    
           // index 没有越界
           const node = new DoublyNode(element);
           let current = this.head;
    
           if (index === 0) { // 插入到头部
               if (!isExist(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;
               current.next = node;
               node.prev = current;
               this.tail = node;
           } else { //插入到中间位置
               const previous = this.getElementAt(index - 1);
               current = previous.next;
               previous.next = node;
               node.next = current;
               current.prev = node;
               node.prev = previous;
           }
    
           this.count += 1;
           return true;
       }
    
       //  removeAt(index):从双向链表中的特定位置移除一个元素
       removeAt(index) {
           // 如果index越界
           if (!(index >= 0 && index < this.count)) {
               return undefined;
           }
    
           // index没有越界
           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 { // 删除中间元素
               const previous = this.getElementAt(index - 1);  // 找到链表中对应当前index位置元素的前一个元素
               current = previous.next;
               previous.next = current.next;
               current.next.prev = previous;
           }
    
           this.count -= 1;
           return current.element;
       }
    
       // getTail():获取双向链表的最后一个元素
       getTail() {
           return this.tail;
       }
    
       //  clear(): 清空双向链表
       clear() {
           super.clear();
           this.tail = undefined;
       }
    }
    

循环链表

  • 循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用
  • 循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素引用不是undefined,而是链表的第一个元素‘
  • 双向循环链表有指向head元素的tail.next 和指向 tail 元素的head.prev
  • 循环链表继承自链表
  • 方法
    • push(element):向循环链表尾部添加一个新元素
    • insert(element,index):向循环链表的任意位置插入一个新元素
    • getElementAt(index):返回循环链表中特定位置的元素。如果循环链表中不存在这个元素则返回undefined
    • remove(element):从循环链表中移除一个元素
    • indexOf(element):返回元素在循环链表中的索引。如果循环链表中没有该元素则返回-1
    • removeAt(index):从循环链表中的特定位置移除一个元素
    • getHead():获取循环链表的第一个元素
    • isEmpty(): 判断循环链表中是否有元素,没有返回true,有方法false
    • clear(): 清空循环链表
    • size(): 返回循环链表中元素的个数
    • toString()
      注意:代码中没有重写的方法,将使用继承自父类的方法
  • 代码
    /*创建循环链表的类*/
    class CircularLinkedList extends LinkedList {
        constructor(equalsFn = defaultEquals) {
            super(equalsFn);
        }
    
        // push(element):向循环链表尾部添加一个新元素
        push(element) {
            const node = new Node(element);
    
            if (this.isEmpty()) {
                this.head = node;
            } else {
                const current = this.getElementAt(this.size() - 1);
                current.next = node;
            }
    
            node.next = this.head;
            this.count += 1;
        }
    
        // insert(element,index):向循环链表的任意位置插入一个新元素
        insert(element, index) {
            // 如果index越界
            if (!(index >= 0 && index <= this.count)) {
                return false;
            }
    
            // index没有越界
            const node = new Node(element);
            if (index === 0) { // 插入到头部
                if (!isExist(this.head)) { // 不存在this.head
                    this.head = node;
                    node.next = this.head;
                } else { //存在this.head
                    const current = this.getElementAt(this.size() - 1);
                    node.next = this.head;
                    this.head = node;
                    current.next = this.head;
                }
            } else {
                const previous = this.getElementAt(index - 1);
                node.next = previous.next;
                previous.next = node;
            }
    
            this.count += 1;
            return true;
        }
    
        // removeAt(index):从循环链表中的特定位置移除一个元素
        removeAt(index) {
            // 如果index越界
            if (!(index >= 0 && index < this.count)) {
                return undefined;
            }
    
            // index 没有越界
            let current = this.head;
            if (index === 0) { //删除第一个元素
                if (this.size() === 1) { // 只有一个元素
                    this.head = undefined;
                } else { //有多个元素
                    const last = this.getElementAt(this.size() - 1); // 最后一个元素
                    this.head = current.next;
                    last.next = this.head;
                }
            } else { //删除其他元素
                const previous = this.getElementAt(index - 1);
                current = previous.next;
                previous.next = current.next;
            }
    
            this.count -= 1;
            return current.element;
        }
    }
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值