偶然一次接触到双向链表结构,有点懵,前端不都是用数组存储数据么,既然没听过就翻翻书,涨涨见识,哈哈
单向链表
![](https://i-blog.csdnimg.cn/blog_migrate/42b4abb6d71bfd7bdd20a113a8ef3b57.png)
如图,链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个 元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。
链表与数组的区别在于:
- 数组的大小是固定的,从数组的起点或中间插入 或移除项的成本很高,因为需要移动元素。而链表添加或移除元素的时候不需要移动其他元素。
- 数组可以直接访问任何位置的任何元素,而要想访问链表中间的一个元素,需要从起点(表头)开始迭代列表直到找到 所需的元素
如此,是不是可以理解为,如果是查询操作较多的数据,则以数组存储;如果是增删改等操作较多的,则以链表存储 ??(未经验证)
以下为链表结构的代码实现,以及添加,删除,查找,获取长度等方法的实现
function LinkedList (){
// Node类表示要加入列表的项
let Node = function(element){
this.element = element;
this.next = null;
}
let length = 0; //列表项的数量的
let head = null; //第一个节点的引用
// 1.向列表尾部添加一个新的项
this.append = function(element){
let node = new Node(element),
current;
if(head === null){
// 列表为空,添加的是第一个元素
head = node;
}else{
// 列表不为空,循环遍历,找到最后一个元素
current = head;
while(current.next){
current = current.next;
}
current.next = node;
}
length++; // 更新长度
};
// 2.向列表的特定位置插入一个新的项
this.insert = function(position,element){
// 索引边界检查
if(position < 0 || position > length-1){
return false
}
let node = new Node(element),
current = head,
pre,index=0;
// 插入在第一个位置
if(position === 0){
node.next = current;
head = node;
}else{
// 从head开始循环,找到指定位置的元素
while(index<position){
pre = current;
current = current.next;
index++;
}
// 插入
pre.next = node;
node.next = current;
}
length++;
return true;
};
// 3.从列表的特定位置移除一项
this.removeAt = function(position){
// 索引边界检查
if(position < 0 || position > length-1){
return null
}
let current = head,
pre,index=0;
if(position ===0){
// 移除第一项
head = current.next;
}else{
// 从head开始循环,找到指定位置的元素
while(index < position){
pre = current;
current = current.next;
index++;
}
// 将pre与current的下一项链接起来:跳过current,从而移除它
pre.next = current.next;
}
length--;
return current.element;
};
// 4.输出元素的值。
this.toString = function(){
let current = head;
str = '';
while(current){
str += current.element + (current.next ? ',' :'');
current = current.next;
}
return str;
};
// 5.返回元素在列表中的索引。如果列表中没有该元素则返回-1
this.indexOf = function(element){
let current = head,
index = -1;
while(current){
index ++;
if(current.element === element){
return index;
}
current = current.next;
}
return -1;
};
// 6.从列表中移除一项
this.remove = function(element){
let index = this.indexOf(element);
return this.removeAt(index);
};
// 7.如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false
this.isEmpty = function(){
return length === 0;
};
// 8.返回链表包含的元素个数
this.size = function(){
return length;
};
// 9.获取head
this.getHead = function(){
return head;
};
// 10.打印
this.print = function() {
let curr_node = head;
while (curr_node) {
console.log(curr_node.element);
curr_node = curr_node.next;
}
}
}
let list = new LinkedList();
console.log(list.isEmpty());
list.append(15);
list.append(10);
list.append(7);
list.insert(1,12);
list.insert(0,24);
list.removeAt(3);
console.log(list.indexOf(12));
console.log(list.toString());
list.remove(7);
console.log(list.toString());
console.log(list.isEmpty());
console.log(list.size());
console.log(list.getHead());
list.print();
双向链表
双向链表和普通链表的区别在于,在链表中, 一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素, 另一个链向前一个元素,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/5c5a58a340d0bb0b38b983b0c3984755.png)
如下为双向链表的代码实现,这里采用ES6的语法。
备注:toString,toString2,indexOf,remove方法为自己扩展的
class Node {
constructor(element) {
this.element = element;
this.next = null;
this.pre = null;
}
}
class DoublyLinkedList {
constructor(element) {
this.node = new Node(element);
this.length = 0;
this.head = null; //第一个节点的引用
this.tail = null; //列表最后一项的引用
}
// 1.在任意位置插入新元素
insert(position, element) {
// 边界判断
if (position < 0 || position > this.length) {
return false
}
let node = new Node(element),
curent = this.head,
pre, index = 0;
if (position === 0) {
// 在第一项添加
if (this.head) {
node.next = curent;
curent.pre = node;
this.head = node;
} else {
this.head = node;
this.tail = node;
}
} else if (position === this.length) {
// 在最后一项添加
curent = this.tail;
curent.next = node;
node.pre = curent;
this.tail = node;
} else {
// 中间的其他位置
while (index < position) {
pre = curent;
curent = curent.next;
index++;
}
node.next = curent;
node.pre = pre;
}
this.length++;
return true;
}
// 2.从任意位置移除元素
removeAt(position) {
// 边界判断
if (position < 0 || position > this.length - 1) {
return false
}
let curent = this.head,
preEle, nextEle, index = 0;
if (position === 0) {
this.head = curent.next;
if (this.length === 1) {
this.tail = null;
} else {
this.head.pre = null;
}
} else if (position === this.length - 1) {
curent = this.tail;
this.tail = curent.pre;
this.tail.next = null;
console.log(this.tail);
} else {
while (index < position) {
curent = curent.next;
index++;
}
preEle = curent.pre;
nextEle = curent.next;
preEle.next = nextEle;
nextEle.pre = preEle;
}
this.length--;
return curent.element;
}
// 3.输出元素的值(顺序)
toString() {
let curent = this.head,
str = '';
while (curent) {
str += (str ? ',' : '') + curent.element;
curent = curent.next;
}
return str
}
// 4.输出元素的值(倒序)
toString2() {
let curent = this.tail,
str = '';
while (curent) {
str += curent.element + ',';
curent = curent.pre;
}
return str
}
// 5.返回元素在列表中的索引。如果列表中没有该元素则返回-1
indexOf(element) {
let node = new Node(element);
let index = -1,
curent = this.head;
while (curent) {
index++;
if (curent.element === element) {
return index
}
curent = curent.next;
}
return -1
}
// 6.从列表中删除一项指定元素
remove(element) {
let node = new Node(element);
let curent = this.head,
preEle, nextEle;
while (curent) {
if (curent.element === element) {
if (curent.pre === null) {
// 删除第一个元素
nextEle = curent.next;
this.head = nextEle;
this.head.pre = null;
curent.next = null;
return true
} else if (curent.next === null) {
// 删除最后一个元素
preEle = curent.pre;
this.tail = preEle;
this.tail.next = null;
curent.pre = null;
console.log(this.tail);
return true
} else {
// 删除中间的元素
preEle = curent.pre;
nextEle = curent.next;
preEle.next = nextEle;
nextEle.pre = preEle;
return true
}
}
curent = curent.next
}
return false
}
}
let list = new DoublyLinkedList();
list.insert(0, 10)
list.insert(1, 11)
list.insert(2, 12)
list.insert(3, 13)
list.insert(4, 14)
console.log(list.toString());
console.log(list.toString2());
list.removeAt(2);
console.log(list.toString());
console.log(list.indexOf(13));
console.log(list.remove(13));
console.log(list.toString());
循环链表
循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用。循环链表和链 表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用null, 而是指向第一个元素(head),如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/fe6973686099416438402c9e1cd239b7.png)
双向循环链表有指向head元素的tail.next,和指向tail元素的head.prev。
![](https://i-blog.csdnimg.cn/blog_migrate/4823118376fbb50fccb31b4c43183e69.png)
参考书籍:《学习JavaScript数据结构与算法》第2版