要存储多个元素 数组可能是最常用的数据结构 但是 这种数据结构有一个缺点 在大多数语言中 数组的大小是固定的 在数组中进行插入删除的成本很高 因为需要移动元素
链表存储有序的元素集合 但不同于数组 链表中的元素在内存中并不是连续放置的 每个元素由一个存储元素本身的节点和指向下一个元素的引用(也称指针或链接)组成
链表在添加或删除元素的时候不需要移动其它元素 然而 链表需要使用指针 数组的另一个细节是可以直接访问任何位置的任何元素 而要想访问链表中间的一个元素 需要从起点(表头)开始迭代列表直到找到所需的元素 也就是查找元素链表的时间复杂度大于数组 而删除或添加元素的时候链表不需要移动其它元素 所以链表在插入删除时的时间复杂度小于数组
1、创建链表
function LinkedList() {
let Node = function (element) { /* Node类表示要加入列表的项。
它包含一个element属性,即要添加到列表的值,以及一个next属性,
即指向列表中下一个节点项的指针。*/
this.element = element;
this.next = null;
};
let length = 0; // 存储列表项的数量的length属性
let head = null; /*我们还需要存储第一个节点的引用。
为此,可以把这个引用存储在一个称为head的变量中*/
//在链表尾部添加项
this.append = function (element) { };
//特定位置添加
this.insert = function (position, element) { };
//特定位置删除
this.removeAt = function (position) { };
//删除一项
this.remove = function (element) { };
//返回元素在链表中的索引 不存在返回-1
this.indexOf = function (element) { };
//链表是否为空
this.isEmpty = function () { };
//链表元素个数
this.size = function () { };
this.getHead = function () { };
/*由于列表项使用了Node类,就需要重写继承自JavaScript
对象默认的toString方法,让其只输出元素的值。*/
this.toString = function () { };
this.print = function () { };
}
2、具体方法:
①append 链表尾部追加元素
两种场景 一 链表为空 添加的是第一个元素 二 链表不为空 追加元素
this.append = function (element) {
let node = new Node(element), //把element作为值传入,创建Node项
current; /*需要循环访问列表,直到找到最后一项。
为此,我们需要一个指向列表中current项的变量 */
if (head === null) { //列表中第一个节点 为空时
head = node;
} else { //不为空时
current = head; /*要向列表的尾部添加一个元素,首先需要
找到最后一个元素。记住,我们只有第一个元素的引用*/
//循环列表,直到找到最后一项
while (current.next) {
current = current.next;
}
//找到最后一项,将其next赋为node,建立链接
current.next = node; /*循环访问列表时,当current.next元素为null时,
我们就知道已经到达列表尾部了。然后要做的就是让当前(也就是最后一个)
元素的next指针指向想要添加到列表的节点*/
}
length++; //更新列表的长度
};
//调用
let list = new LinkedList();
list.append(15);
list.append(10);
②移除元素 remove从链表中移除一项 removeAt从特定位置移除
this.removeAt = function (position) {
//检查越界值
if (position > -1 && position < length) {
/*该方法需要得到移除元素的位置 就需要验证这个位置是否是有效的
从0(包括0)到列表的长度都是有效的位置*/
let current = head, //current变量对第一个元素的引用
previous, // {3}
index = 0; // {4}
//移除第一项
if (position === 0) { /*如果位置为0 即移除第一个元素
我们要做的就是让head指向第二个元素 我们将用current变量
创建一个对链表中第一个元素的引用 上面已经引用 我们把
head赋为current.next 就会移除第一个元素*//**/
head = current.next;
} else {
while (index++ < position) { /*假设我们要移除列表的最
后一项或者中间某一项。为此,需要依靠一个细节来迭代列表,直到
到达目标位置——我们会使用一个用于内部控制和递增的index变量*/
previous = current; //对当前元素的前一个元素的引用
current = current.next; /*current变量总是为对所循
环列表的当前元素的引用 */
}
//将previous与current的下一项链接起来:跳过current,从而移除它
previous.next = current.next; /*要从列表中移除当前元素,要做
的就是将previous.next和current.next链接起来*/
}
length--;
return current.element;
} else {
return null; //越界return null
}
};
③插入元素 insert
this.insert = function (position, element) {
//检查越界值
if (position >= 0 && position <= length) { //{1}
let node = new Node(element),
current = head,
previous,
index = 0;
if (position === 0) { //在第一个位置添加
node.next = current; /*current变量是对
列表中第一个元素的引用。我们需要做的是把node.next
的值设为current(列表中第一个元素)。现在head和
node.next都指向了current。接下来要做的就是把head
的引用改为node,这样列表中就有了一个新元素。*/
head = node;
} else {
while (index++ < position) { /*现在来处理
第二种场景:在列表中间或尾部添加一个元素。首先,
我们需要循环访问列表,找到目标位置*/
previous = current;
current = current.next;
}
node.next = current; /*当跳出循环时,current
变量将是对想要插入新元素的位置之后一个元素的引用,而
previous将是对想要插入新元素的位置之前一个元素的引用。
在这种情况下,我们要在previous和current之间添加新项。
因此,首先需要把新项(node)和当前项链接起来*/
previous.next = node; /*然后需要改变previous
和current之间的链接。我们还需要让previous.next指向
node*/
}
length++; //更新列表的长度
return true;
} else {
return false;
}
};
如果我们试图向最后一个位置添加一个新元素,previous将是对列表最后一项的引用,而
current将是null。在这种情况下,node.next将指向current,而previous.next将指向
node,这样列表中就有了一个新的项。
何向列表中间添加一个新元素:
在这种情况下,我们试图将新的项(node)插入到previous和current元素之间。首先,
我们需要把node.next的值指向current。然后把previous.next的值设为node。这样列表中
就有了一个新的项。
④把链表转换为字符串
toString方法会把LinkedList对象转换成一个字符串。下面是toString方法的实现:
this.toString = function(){
let current = head, //把current变量当作索引 控制循环访问列表
string = ''; //初始化用于拼接元素值的变量
while (current) { //循环访问列表中的每个元素
string +=current.element +(current.next ? 'n' : '');/*我们要用
current来检查元素是否存在(如果列表为空,或是到达列表中最后一个元素的
下一位(null),while循环中的代码就不会执行)。然后我们就得到了元素的
内容,将其拼接到字符串中*/
current = current.next; //继续迭代下一个元素
}
return string; //返回列表内容的字符串
};
⑤indexOf 接收一个元素的值 如果在列表中找到它 就返回元素的位置 否则返回-1
this.indexOf = function(element){
let current = head, /*我们需要一个变量来帮助我们循环访问列表,
这个变量是current,它的初始值是head(列表的第一个元素——我们还需要
一个index变量来计算位置数*/
index = -1;
while (current) { /*然后循环访问元素*/
if (element === current.element) {
return index; /*检查当前元素是否是我们要找的。
如果是,就返回它的位置*/
}
index++; //如果不是,就继续计数
current = current.next; //检查列表中下一个节点
}
return -1; /*如果列表为空,或是到达列表的尾部
(current = current.next将是null),循环就不会执行。
如果没有找到值,就返回-1。*/
};
⑥isEmpty size getHead
this.isEmpty = function() {
return length === 0;
};
this.size = function() {
return length;
};
this.getHead = function(){
return head;
};
3、双向链表
双向链表和普通链表的区别在于,在链表中,一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素,如图: