在存储多个元素时,咱们最经常使用的数据结构多是数组,究其缘由多是数组访问方便,能够直接经过[]访问,可是数组也存在必定的缺点,数组的大小是固定,数组在执行插入或者删除的时候成本很高。
链表存储的是有序的元素集合,和数组不一样的是,链表中的元素在内存并非连续放置,每一个元素由一个存储元素自己的节点和一个指向下一个元素的引用组成,结构以下图所示:
和数组相比,链表的优点在于:添加或者删除元素不须要移动其余元素,劣势在与链表相对于数组结构更复杂,须要一个指向下一个元素的指针,在访问链表中的某个元素也须要从头迭代,而不是像数组同样直接访问node
首先让咱们来看一下链表的大概骨架,须要定义一些什么属性:数组
function LinkedList() { let Node = function (element) { this.element = element this.next = null } let length = 0 let head = null this.append = function (element) { } this.insert = function (position, element) { } this.removeAt = function (position) { } this.remove = function (element) { } this.indexOf = function (element) { } this.isEmpty = function () { } this.size = function () { } this.getHead = function () { } this.toString = function () { } this.print = function () { } }
首先LinkedList里面须要定义一个Node辅助类,表示要加入链表的项,包含一个element属性和一个指向下一个节点的指针next,接下来定义了一个指针的头head和指针的长度length,而后就是最重要的类里面的方法,接下来就让咱们一块儿来看这些方法的职责和实现数据结构
链表在向尾部追加元素的时候有两种状况:链表为空,添加的是第一个元素,或者链表不为空,追加元素,下面来看具体实现:app
this.append = function (element) { let node = new Node(element), current if (head === null) { head = node // 链表为空时,插入 } else { current = node while (current.next) { // 循环链表,直到最后一项 current = current.next } current.next = node // 找到最后一项,将新增元素链接 } length++ }
如今让咱们先来看第一个种状况,链表为空时,插入就直接是头,所以直接将node赋值给head就行,第二种状况,当链表有元素时,就须要先循环链表,找到最后一项,而后将最后一项的next指针指向nodethis
如今让咱们来看看怎么从指定位置移除元素,移除元素也有两个场景,第一种是移除第一个元素,第二种是移除第一个之外的任意一个元素,来看具体实现:spa
this.removeAt = function (position) { if (position > -1 && position < length) { // 判断边界 let previous, index = 0, current = head if (position === 0) { // 移除第一项 head = current.next } else { while (index++ < position) { previous = current current = current.next } previous.next = current.next } length-- return current.element } else { return null } }
接下来一块儿来分析一下上面的代码,首先判断要删除的位置是否是有效的位置,而后来看第一种状况,当移除的元素是第一项是时,此时直接将head指向第二个元素就好了,第二种状况就会稍微复杂一点,首先须要一个index控制递增,previous记录前一个位置,移除当前元素,就是将前一个元素的next指向下一个元素,来看一个示意图:
所以在while循环中始终用previous记录上一个位置元素,current记录下一个元素,跳出循环时,上一个元素的next指针指向当前元素的next指针指向的元素,就将当前元素移出链表指针
接下来来看在任意位置插入的insert方法,这个方法一样须要考虑两种状况,插入位置在头部和插入位置不在头部,下面来看一下具体实现:code
this.insert = function (position, element) { if (position > -1 && position <= length) { let node = new Node(element),previous, index = 0, current = head if (position === 0) { node.next = current head = node } else { while (index++ < position) { previous = current current = current.next } previous.next = node node.next = current } length++ return true } else { return false } }
先来看第一种状况,链表起点添加一个元素,将node.next指向current,而后再将node的引用赋值给head,这样就再链表的起点添加了一个元素,第二种状况,在其余位置插入一个元素,previous是插入元素的前一个元素,current为插入元素的后一个元素,想要插入一个元素,就须要将前一个元素的next指向要插入的元素,要插入元素的next指向下一个元素,来看示意图:
如上图所示:将新项node插入到previous和current之间,须要将previous.next指向node,node.next指向current,这样就在链表中插入了一个新的项对象
toString方法会把LinkedList对象转换成一个字符串,下面来看具体实现:索引
this.toString = function () { let current = head, string = '' while (current) { string += current.element + (current.next ? 'n' : '') current = current.next } return string }
循环遍历全部元素,以head为起点,当存在下一个元素时,就将其拼接到字符串中,直到next为null
indexOf方法返回对应元素的位置,存在就返回对应的索引,不存在返回-1,来看具体的实现:
this.indexOf = function (element) { let current = head, index = 0 while (current) { if (current.element === element) { return index } index++ current = current.next } return -1 }
遍历链表,当前元素的值与目标值一致时返回元素的位置index,遍历完链表还没找到则返回-1
因为这几个方法实现比较简单,直接来看具体实现:
this.remove = function (element) { // 移除指定元素 let index = this.indexOf(element) return this.removeAt(index) } this.isEmpty = function () { // 判断链表是否为空 return length === 0 } this.size = function () { // 获取链表长度 return length } this.getHead = function () { // 获取链表头 return head }
这篇文章主要对链表作了简单介绍,对链表的简单实现。若是有错误或不严谨的地方,欢迎批评指正,若是喜欢,欢迎点赞。