链表
链表
链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成
如下图:
相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素。然而,链表需要使用指针,因此实现链表时需要额外注意。在数组中,我们可以直接访问任何位置的任何元素,而要想访问链表中间的一个元素,则需要从起点(表头)开始迭代链表直到找到所需的元素。
创建链表
1 // 待实现功能 2 // push(element):向链表尾部添加一个新元素。 3 // insert(element, position):向链表的特定位置插入一个新元素。 4 // getElementAt(index):返回链表中特定位置的元素。如果链表中不存在这样的元素,则返回 undefined。 5 // remove(element):从链表中移除一个元素。 6 // indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回-1。 7 // removeAt(position):从链表的特定位置移除一个元素。 8 // isEmpty():如果链表中不包含任何元素,返回 true,如果链表长度大于0则返回 false。 9 // size():返回链表包含的元素个数,与数组的 length 属性类似。 10 // toString():返回表示整个链表的字符串。
1 function defaultEquals(a, b){ 2 // 用于比较链表中元素是否相等 3 return a===b 4 } 5 6 class Node{ 7 // 节点 8 constructor(element){ 9 this.element = element 10 this.next = undefined 11 } 12 } 13 14 class LinkedList{ 15 // 链表 16 constructor(equalsFn = defaultEquals){ 17 this.count = 0 // 长度 18 this.head = undefined // 头元素 19 this.equalsFn = equalsFn 20 } 21 22 push(element){ 23 // 向链表末尾添加一个元素 24 // 1.链表为空,添加为头部元素 25 // 2.链表不为空,追加元素 26 const node = new Node(element) 27 let current 28 if(!this.head){ 29 // 如果没有头元素,则直接插入头部 30 this.head = node 31 }else{ 32 // 如果有头元素,则依次查找至末尾,添加到末尾处 33 current = this.head 34 while(current.next){ 35 current = current.next 36 } 37 current.next = node 38 } 39 this.count++ 40 } 41 getElementAt(index){ 42 if(index >= 0 && index < this.count){ 43 let node = this.head 44 for(let i = 0; i < index && node; i++){ 45 node = node.next 46 } 47 return node 48 }else{ 49 return undefined 50 } 51 } 52 removeAt(index){ 53 // 删除指定索引处的元素,返回删除元素 54 let current = this.head 55 if (index === 0){ 56 // 若待删除索引为0,则直接替换头元素 57 this.head = current.next 58 }else{ 59 // 若索引不为0,查找至待删除索引上一位,直接连接待删除索引下一位 60 const previous = this.getElementAt(index-1) 61 current = previous.next 62 previous.next = current.next // 去除index处元素 63 } 64 this.count-- 65 return current.element 66 } 67 insert(element, index){ 68 if(0 <= index <= this.count){ 69 const node = new Node(element) 70 if(index === 0){ 71 const current = this.head 72 node.next = current 73 this.head = node 74 }else{ 75 const previous = this.getElementAt(index-1) 76 const current = previous.next 77 node.next = current 78 previous.next = node 79 } 80 this.count++ 81 return true 82 } 83 return false 84 } 85 indexOf(element){ 86 let current = this.head 87 for(let i = 0; i < this.count; i++){ 88 if (this.equalsFn(current.element, element)) { 89 return i 90 } 91 current = current.next 92 } 93 return -1 94 } 95 remove(element){ 96 // 移除某个元素 97 const index = this.indexOf(element) 98 return this.removeAt(index) 99 } 100 isEmpty(){ 101 return this.count === 0 102 } 103 size(){ 104 return this.count 105 } 106 getHead(){ 107 return this.head 108 } 109 toString(){ 110 // toString方法 111 if(!this.head){ 112 return "" 113 }else{ 114 let str = `${this.head.element}` 115 let current = this.head.next 116 for(let i = 1; i < this.size() && current !== null; i++){ 117 str = `${str},${current.element}` 118 current=current.next 119 } 120 return str 121 } 122 } 123 124 }
双向链表
双向链表和普通链表的区别在于,在链表中,一个节点只有链向下一个节点的链接;而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素

对单向链表进行扩展,主要重写插入和删除方法,需要考虑每个节点有向前的指针
1 class DoubleLinkedList extends LinkedList{ 2 constructor(equalsFn = defaultEquals){ 3 super(equalsFn) 4 this.tail = undefined // 最后一个节点 5 } 6 insert(element, index){ 7 // 在任意位置添加新元素 8 if(index >=0 && index <= this.count){ // 索引是否超限 9 const node = new DoubleNode(element) 10 let current = this.head 11 if (index === 0) { 12 // 若索引为0 13 if (!this.head) { 14 // 空链表则直接加到头部 15 this.head = node 16 this.tail = node 17 }else{ 18 node.next = this.head 19 current.prev = node 20 this.head = node 21 } 22 }else if(index === this.count){ 23 // 若索引为最后 24 current =this.tail 25 current.next = node 26 node.prev = current 27 this.tail = node 28 }else{ 29 // 其余索引 30 const previous = this.getElementAt(index - 1) 31 current = previous.next 32 node.next = current 33 node.prev = previous 34 previous.next = node 35 current.prev = node 36 } 37 this.count++ 38 return true 39 } 40 return false 41 } 42 removeAt(index){ 43 // 按索引删除 44 if(index >= 0 && index < this.count){ // 索引是否超限 45 let current = this.head 46 if(index === 0){ 47 // 删除头元素 48 this.head = current.next 49 if (this.count === 1) { 50 // 若只有一个元素 51 this.tail = undefined 52 }else{ 53 this.head.prev = undefined 54 } 55 }else if(index === this.count - 1){ 56 // 删除末尾元素 57 current = this.tail 58 this.tail = current.prev 59 this.tail.next = undefined 60 }else{ 61 current = this.getElementAt(index) 62 const previous = current.prev 63 previous.next = current.next 64 current.next.prev = previous 65 } 66 this.count-- 67 return current.element 68 } 69 return undefined 70 } 71 }
有序链表
有序链表是指保持元素有序的链表结构。新元素将按照固定的排序算法插入
1 function defaultEquals(a, b){ 2 // 用于比较链表中元素是否相等 3 return a===b 4 } 5 6 const compare = { 7 Less_Than : -1, 8 Bigger_Than : 1 9 } 10 11 function defaultCompare(a, b){ 12 // 比较大小 13 if(a === b){ 14 return 0 15 } 16 return a < b? compare.Less_Than : compare.Bigger_Than 17 } 18 19 class SortedLinkedList extends LinkedList{ 20 constructor(equalsFn = defaultEquals, compareFn = defaultCompare){ 21 super(equalsFn) 22 this.compareFn = compareFn 23 } 24 25 // 因为自动排序,所以要重写insert 26 insert(element){ 27 if(this.isEmpty){ 28 // 若是空链表,则直接插入即可 29 return super.insert(element, 0) 30 }else{ 31 // 若不是空链表,则插入排序后的位置 32 return super.insert(element, getIndexNextSortedElement(element)) 33 } 34 35 } 36 37 getIndexNextSortedElement(element){ 38 // 排序,找到元素的位置 39 let current = this.head 40 let i = 0 41 for(; i < this.size() && current; i++){ 42 const comp = this.compareFn(element, current.element) 43 if (comp === compare.Less_Than) { 44 return i 45 } 46 current = current.next 47 } 48 return i 49 } 50 51 52 }
使用链表实现栈数据结构
1 class StackLinkedList{ 2 constructor(){ 3 this.items = new DoubleLinkedList() 4 } 5 push(element){ 6 this.items.insert(this.items.size()) 7 } 8 pop(){ 9 if (!this.items.isEmpty()) { 10 return undefined 11 } 12 this.items.removeAt(this.size()-1) 13 } 14 // 使用双向链表实现栈数据结构比单向链表简单的多,同样也可实现队列 15 16 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器