JavaScript数据结构之链表
链表相较于数组的优缺点
1. 链表在 插入、删除、移动数据效率比数组要高,数组插入、移动、删除数据需要改变没有数据的索引,而链表则只需要更改指针即可
2. 在查询方面,数组要优于链表,数组存储的数据是连续的,可以直接通过位置信息读取到数据,而链表则需要通过遍历的方式找到对应的指针
3. 灵活性方面,传统的数组是需要固定长度的,而链表则可以是任意长度
备注:JavaScript中会根据实际情况,将数组转换为 快数组或者慢数组(掘金的文章:https://juejin.cn/post/6844903943638794248#heading-0)
链表图解:
链表实现(实现了循环链表、双向链表、单项链表):
// 创建链表 class Node { constructor(element) { this.element = element this.next = null // 实现双向链表指针 this.prev = null } } class SingleList { constructor() { this.size = 0 this.head = null this.tail = this.head } // 在尾部添加元素 append(element) { const newNode = new Node(element) // 实现循环链表(其实就是将最后一个元素的指针指向 head) // newNode.next = this.head if (!this.head) { this.head = newNode } else { this.tail.next = newNode newNode.prev = this.tail } this.tail = newNode this.size++ } // 对 size 进行缓存,获取长度的时候直接返回即可 getLength() { return this.size } find(element) { let _elm = this.head let i = 0 // while(_elm) {} 因为实现了循环链表,_elm不可能为空 while (i < this.size) { if (_elm.element === element) { return _elm } _elm = _elm.next i++ } return null } // 删除指定元素 remove(element) { const _delItem = this.find(element) if(!_delItem) { return false } // 通过改变指针选项实现 const _oldNext = _delItem.next const prev = _delItem.prev prev.next = _oldNext return true } // 或者最后一个元素,this.tail 已经进行缓存,故直接返回即可 findLast() { return this.tail } insert(newItem, posItem) { if (posItem) { const pos = this.find(posItem) if (!pos) { return false } // 通过改变指针指向实现插入元素 const oldNext = pos.next const newNext = new Node(newItem) newNext.prev = pos newNext.next = oldNext pos.next = newNext } else { this.append(newItem) } } }
链表拓展算法:
1. 链表反转
function reverse(head) { let _newHead = null while(head) { const tmp = head.next head.next = _newHead _newHead = head head = tmp } return _newHead } // 模拟一个链表格式 const head = {"element":1,"next":{"element":2,"next":{"element":3,"next":null}}} reverse(head) // 分析一波: // 第一次while(head = {"element":1,"next":{"element":2,"next":{"element":3,"next":null}}}): // const tmp = head.next // tmp -> {"element":2,"next":{"element":3,"next":null}} // head.next = _newHead // head -> {"element":1,"next": null } // _newHead = head // _newHead -> {"element":1,"next": null } // head = tmp // head -> {"element":2,"next":{"element":3,"next":null}} // 第二次while(head = {"element":2,"next":{"element":3,"next":null}}): // const tmp = head.next // tmp -> {"element":3,"next":null} // head.next = _newHead // head -> {"element":2,"next": {"element":1,"next": null }} // _newHead = head // _newHead -> {"element":2,"next": {"element":1,"next": null }} // head = tmp // head -> {"element":3,"next":null} // 第三次while(head = {"element":3,"next":null}): // const tmp = head.next // tmp -> null // head.next = _newHead // head -> {"element":3,"next":{"element":2,"next": {"element":1,"next": null }}} // _newHead = head // _newHead -> {"element":3,"next":{"element":2,"next": {"element":1,"next": null }}} // head = tmp // head -> null
2. 判断是否为回文链表
备注:这里使用 快慢指针 的方式实现 时间复杂度O(n),空间复杂度O(1),也可以先将链表转为数组实现,时间复杂度O(n) 空间复杂度O(n)
// 判断是否是 回文链表 function isPalindrome(head) { if(!head || !head.next) { return true } let fast = head; let slow = head; let prev; while (fast && fast.next) { prev = slow; slow = slow.next; fast = fast.next.next; } // 断开链表 prev.next = null // 翻转后半段链表 let head2 = null while(slow) { const tmp = slow.next slow.next = head2 head2 = slow slow = tmp } // 对比 while(head && head2) { if(head.element !== head2.element) { return false } head = head.next head2 = head2.next } return true }