链表存储有序的元素集合,链表中的元素在内存中不是连续放置的,每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。下图展
示了一个链表的结构:
相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素,只需要找到指针的位置。而数组添加元素和移除元素,需要移动元素的位置,成本比较高
链表
链表要实现的方法
append(element) :向列表尾部添加一个新的项。
insert(position, element) :向列表的特定位置插入一个新的项。
remove(element) :从列表中移除一项。
indexOf(element) :返回元素在列表中的索引。如果列表中没有该元素则返回 -1 。
removeAt(position) :从列表的特定位置移除一项。
isEmpty() :如果链表中不包含任何元素,返回 true ,如果链表长度大于0则返回 false 。
size() :返回链表包含的元素个数。与数组的 length 属性类似。
toString() :由于列表项使用了 Node 类,就需要重写继承自JavaScript对象默认的toString 方法,让其只输出元素的值。
function LinkedList() {
let Node = function(element){
this.element = element;
this.next = null;
};
let length = 0;
let head = null;
this.append = function(element){
let node = new Node(element),
current;
if (head === null){ //列表中第一个节点
head = node;
} else {
current = head; // current = {element: 'a', next: null}
// 从第一项开始循环列表,直到找到最后一项
while(current.next){
current = current.next;
}
//找到最后一项,将其next赋为node,建立链接
current.next = node;
}
length++; //更新列表的长度
};
this.removeAt = function(position) {
//检查越界值
if (position > -1 && position < length) {
let current = head,
previous,
index = 0;
//移除第一项
if (position === 0) {
head = current.next;
} else {
// 找到移除项的前一项和待移除项
while (index++ < position) {
previous = current;
current = current.next;
}
// 将previous与current的下一项链接起来:跳过current,从而移除它
previous.next = current.next;
}
length--;
return current.element; // 返回移除项
} else {
return null;
}
};
this.insert = function(position, element) {
//检查越界值
if (position >= 0 && position <= length) {
let node = new Node(element),
current = head,
previous,
index = 0;
if (position === 0) { //在第一个位置添加
node.next = current;
head = node;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
}
length++; //更新列表的长度
return true;
} else {
return false;
}
};
this.toString = function() {
let current = head,
string = '';
// 从第一项遍历到最后一项,拼接element的值
while (current) {
string += current.element + (current.next ? ',' : '');
current = current.next;
}
return string;
};
this.indexOf = function(element) {
let current = head,
index = -1;
while (current) {
// 找到链表中匹配项的位置
if (element === current.element) {
return index;
}
index++;
current = current.next;
}
return -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;
};
};
let l = new LinkedList();
l.append('a');
l.append('b');
console.log(l.size()); // 2
l.insert(1, 'aa');
console.log(l.size()); // 3
以上就是链表的实现方法,这里的属性和方法都定义在了函数内部而非原型对象上,这么做是为了保证私有属性不会暴露到外面
双向链表
链表有多种不同的类型,上面实现的是链表是单向的,即每个节点只有链向下一个节点的链接。而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素,如下图所示:
基于前面的链表稍加改动,便可以实现双向链表
function DoublyLinkedList() {
let Node = function(element) {
this.element = element;
this.next = null;
this.prev = null; // 新增
};
let length = 0;
let head = null; // 第一个节点
let tail = null; // 最后一个节点 新增
this.append = function(element) {
let node = new Node(element),
current;
if (head === null) { // 链表中的第一个节点
head = node;
tail = node; // 新增
} else {
// 新增
tail.next = node; // 尾节点的下一个节点指向新增节点
node.prev = tail; // 新节点的前一个节点指向尾节点
tail = node; // 更新尾节点
}
length++; // 更新链表长度
};
this.insert = function(position, element) {
// 边界值检测
if (position >= 0 && position <= length) {
let node = new Node(element),
current = head,
previous,
index = 0;
if (position === 0) { // 特殊情况:新节点要添加到头部
if (!head) { // 新增
head = node;
tail = node;
} else {
node.next = current;
current.prev = node; // 新增
head = node;
}
} else if (position === length) { // 特殊情况:新节点要添加到头部尾部 // 新增
current = tail;
current.next = node;
node.prev = current;
tail = node;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
current.prev = node; // 新增
node.prev = previous; // 新增
}
length++; // 更新链表长度
return true;
} else {
return false;
}
};
this.removeAt = function(position) {
// 边界值检测
if (position > -1 && position < length) {
let current = head,
previous,
index = 0;
// 移除第一个节点
if (position === 0) {
head = current.next;
// 如果链表只有一个节点,此时需要更新尾节点 // 新增
if (length === 1) {
tail = null;
} else {
head.prev = null;
}
} else if (position === length - 1) { // 移除最后一个节点 // 新增
current = tail;
tail = current.prev;
tail.next = null;
} else {
while (index++ < position) { // 移除中间节点
previous = current;
current = current.next;
}
// 将previous与current的下一项链接起来——跳过current
previous.next = current.next;
current.next.prev = previous; // 新增
}
length--;
return current.element;
} else {
return null;
}
};
this.remove = function(element) {
let index = this.indexOf(element);
return this.removeAt(index);
};
this.indexOf = function(element) {
let current = head,
index = -1;
// 第一个节点,无需遍历,节省时间
if (element == current.element) {
return 0;
}
index++;
// 中间节点,需要遍历才能找到位置
while (current.next) {
if (element == current.element) {
return index;
}
current = current.next;
index++;
}
// 最后一个节点
if (element == current.element) {
return index;
}
return -1;
};
this.isEmpty = function() {
return length === 0;
};
this.size = function() {
return length;
};
this.toString = function() {
let current = head,
s = current ? current.element : '';
while (current && current.next) {
current = current.next;
s += ', ' + current.element;
}
return s;
};
this.inverseToString = function() {
let current = tail,
s = current ? current.element : '';
while (current && current.prev) {
current = current.prev;
s += ', ' + current.element;
}
return s;
};
this.getHead = function() {
return head;
};
this.getTail = function() {
return tail;
}
}
节点移除示意图
移除链表头部节点
移除链表尾部节点
移除链表中间节点
节点新增示意图
链表头部新增节点
链表尾部新增节点
链表中间新增节点
循环链表
还有一种链表叫做循环链表,循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针再是null,而是指向第一个元素( head ),整体形成了一个环装,如下图所示
循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用,双向引用的叫双向循环链表。这种链表有指向 head 元素的 tail.next ,和指向 tail 元素的 head.prev,如图
循环链表的实现和链表类似,只是需要调整最后一个节点的指向为第一个节点
function CircularLinkedList() {
let Node = function(element) {
this.element = element;
this.next = null;
};
let length = 0;
let head = null;
this.append = function(element) {
let node = new Node(element),
current;
if (head === null) { // 链表中第一个节点
head = node;
} else {
current = head;
//找到链表中最后一个节点
while (current.next !== head) { //最后一个元素指向head而不是NULL
current = current.next;
}
//获取最后一项,并将其分配到添加项中以创建链接
current.next = node;
}
//设置 node.next 指向 head
node.next = head;
length++;
};
this.insert = function(position, element) {
if (position >= 0 && position <= length) {
let node = new Node(element),
current = head,
previous,
index = 0;
if (position === 0) {
if (!head) {
head = node;
node.next = head;
} else {
node.next = current;
// 更新最后一个节点
while (current.next !== head) { //最后一个元素指向head而不是NULL
current = current.next;
}
head = node;
current.next = head;
}
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
}
length++;
return true;
} else {
return false;
}
};
this.removeAt = function(position) {
if (position > -1 && position < length) {
let current = head,
previous,
index = 0;
if (position === 0) {
while (current.next !== head) { // 需要更新最后一个节点的指向
current = current.next;
}
head = head.next;
current.next = head;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
//把前一个节点和当前节点的next链接,删除当前节点
previous.next = current.next;
}
length--;
return current.element;
} else {
return null;
}
};
this.remove = function(element) {
let index = this.indexOf(element);
return this.removeAt(index);
};
this.indexOf = function(element) {
let current = head,
index = -1;
if (element == current.element) {
return 0;
}
index++;
while (current.next !== head) {
if (element == current.element) {
return index;
}
current = current.next;
index++;
}
if (element == current.element) {
return index;
}
return -1;
};
this.isEmpty = function() {
return length === 0;
};
this.size = function() {
return length;
};
this.getHead = function() {
return head;
};
this.toString = function() {
let current = head,
s = current.element;
while (current.next !== head) {
current = current.next;
s += ', ' + current.element;
}
return s.toString();
};
}
总结
链表相比数组最重要的优点,就是无需移动链表中的元素,就能轻松地添加和移除元素。因此,当需要添加和移除很多元素时,最好的选择就是链表,而非数组。