本文总结一下链表常用方法的实现。
准备工作:由于链表由一个个节点组成,且节点在内存中的存储不连续,所以我们需要封装一个内部的Node类,包含两个属性:节点值data和下一个节点的引用地址next。此外在链表类中还需要两个属性:head用来指向第一个节点,length表示当前链表的长度。
// 内部的类:节点类 function Node(data) { this.data = data this.next = null } // 指向链表中第一项的地址 this.head = null // 链表长度 this.length = 0
1、append方法:在链表末尾插入一个新节点。
该方法的实现比较简单:创建新节点—若是插入的第一个元素,则修改this.head—若不是第一个元素,则从head开始一步一步走到末尾的节点,让其next指向新节点。最后不要忘记修改length。
LinkedList.prototype.append = function (data) { let newNode = new Node(data) if (this.length === 0) { this.head = newNode } else { let current = this.head while (current.next) { current = current.next } current.next = newNode } this.length += 1 }
2、toString方法:将各个节点值以空格为分隔符,以字符串形式输出。
这个方法实现起来也很简单,就是节点遍历+字符串拼接。
LinkedList.prototype.toString = function () { let current = this.head let listString = "" while (current) { listString += current.data + " " current = current.next } // 终止循环时,current指向null return listString }
需要注意的一点是,在遍历节点时,若对所有节点执行的操作相同,则以current作为终止条件,退出循环时current指向null;若需要对最后一个节点执行单独的操作,则以current.next作为终止条件,这样退出循环时current指向最后一个节点。
3、get方法:根据索引获取对应节点的值。
这个方法就是读操作,只需要设置一个指针变量,让其逐步前进,当其与传入的索引相等时,返回指针所在节点的值即可。
LinkedList.prototype.get = function(position){ if(position < 0 || position >= this.length) return null let index = 0; let current = this.head while( index++ < position){ current = current.next } return current.data }
可见,循环退出时,指针Index恰好指向正确的节点。此外,这里对传入的索引进行了边界判断,如果超出了当前链表的索引范围,则读取到的值为null。
4、indexOf方法:根据值获取对应的索引。
设置一个变量动态存储索引,仍然是从头开始遍历,每比较一个节点将索引值加一,直到找到对应的节点,返回此时的Index。如果遍历结束仍未找到,则返回-1。
LinkedList.prototype.indexOf = function(data){ let index = 0 let current = this.head while(current){ if(data === current.data){ return index }else{ current = current.next index++ } } return -1 }
5、upDate方法:根据索引修改对应节点的值。
这个方法与get方法差不多,无非是在找到对应节点后,不返回而是修改其值。
LinkedList.prototype.update = function(position, newData) { if(position < 0 || position >= this.length) return false let index = 0; let current = this.head while( index++ < position){ current = current.next } current.data = newData return true }
6、insert方法:在指定索引处插入新元素。
这个方法需要分类讨论。指定的索引是否越界?新元素是否插入在头部?如果不在,那么需要两个指针:curr,指向指定索引原来的元素;prev,指向curr前一个元素。每次循环,将prev指向curr。到达正确的索引后,将prev的next指向新节点,将新节点的next执行curr。最后不要忘记修改长度。
LinkedList.prototype.insert = function(position, data){ if(position < 0 || position > this.length) return false let newNode = new Node(data) if(position == 0){ newNode.next = this.head this.head = newNode }else{ let current = this.head let previous = null let index = 0 while(index++ < position){ previous = current current = current.next } previous.next = newNode newNode.next = current } this.length += 1 return true }
7、removeAt方法:删除指定索引处的元素。
这个方法实现思路几乎与上一个相同,只需要比较一下删除和插入的区别,修改一些细节即可。记得修改length的长度。
LinkedList.prototype.removeAt = function(position){ if(position < 0 || position >= this.length) return null let current = this.head if(position === 0){ this.head = this.head.next }else{ let previous = null let index = 0 while(index++ < position){ previous = current current = current.next } previous.next = current.next } this.length -= 1 return current.data }
值得注意的是:因为需要将删除的数据返回,即要在else语句块外面使用current,所以把current的定义放在外部。
8、remove方法:删除指定值。
没什么好说的,先根据值获取索引(indexOf方法),然后根据索引删除元素(removeAt)。
LinkedList.prototype.remove = function(data){ let position = this.indexOf(data) this.removeAt(position) }
9、isEmpty方法:返回链表是否为空。
等价于长度是否为0。
LinkedList.prototype.isEmpty = function(){ return this.length === 0 }
10、size方法:返回链表长度。
等价于长度。
LinkedList.prototype.size = function(){ return this.length }