参考文章:https://www.cnblogs.com/xiaohuochai/p/8175716.html#anchor4
1.链表
链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。下图展示了一个链表的结构:
2.创建链表
理解了链表是什么之后,现在就要开始实现我们的数据结构了。以下是我们的LinkedList 类的骨架:
function LinkedList() {
var Node = function(element){ // {1}
this.element = element;
this.next = null;
};
var length = 0; // {2}
var head = null; // {3}
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.toString = function(){};
this.print = function(){};
}
LinkedList数据结构还需要一个Node辅助类(行{1})。Node类表示要加入列表的项。它包含一个element属性,即要添加到列表的值,以及一个next属性,即指向列表中下一个节点项的指针。
LinkedList类也有存储列表项的数量的length属性(内部/私有变量)(行{2})。另一个重要的点是,我们还需要存储第一个节点的引用。为此,可以把这个引用存储在一个称为head的变量中(行{3})。
LinkedList类的方法:
append(element):向列表尾部添加一个新的项。
insert(position, element):向列表的特定位置插入一个新的项。
remove(element):从列表中移除一项。
indexOf(element):返回元素在列表中的索引。如果列表中没有该元素则返回-1。
removeAt(position):从列表的特定位置移除一项。
isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。
size():返回链表包含的元素个数。与数组的length属性类似。
toString() :由 于列表项使 用了 Node 类 ,就 需要重写 继承自 JavaScript对 象默认 的
toString方法,让其只输出元素的值。
1.append方法
向LinkedList对象尾部添加一个元素时,可能有两种场景:列表为空,添加的是第一个元素,或者列表不为空,向其追加元素
function LinkedList(){
//创建新的节点
//Node类表示要加入列表的项。它包含一个element属性,即要添加到列表的值,以及一个next属性,即指向列表中下一个节点项的指针
var Node = function(element){
this.element = element;;
this.next = null; //列表最后节点的下一个元素始终时null
}
var length = 0;
//我们还需要存储第一个节点的引用。为此,可以把这个引用存储在一个称为head的变量中
var head = null;
//向LinkedList对象尾部添加一个元素时,可能有两种场景:列表为空,添加的是第一个元素,或者列表不为空,向其追加元素
this.append = function(element){
//创建新的节点(Node项)
var node = new Node(element);
//指向列表中current项的变量
var current;
if(head === null){ //head为null说明链表为空
head = node; //链表为空时,我们就是让head指向了node,同时我们要知道列表最后节点的下一个元素始终时null
}else{
//我们只有第一个元素的引用
current = head;
//循环列表,知道找到最后一项,current.next为null时候,说明此时current是最后一个,此时的指向是最后一项
while(current.next){
current = current.next;
}
//找到最后一项,将其next赋为node,建立链接
current.next = node;
}
//更新列表的长度
length++;
}
}
先来实现第一个场景:向为空的列表添加一个元素。当我们创建一个LinkedList对象时,head会指向null:
第二个场景是向一个不为空的列表尾部添加元素
要向列表的尾部添加一个元素,首先需要找到最后一个元素。记住,我们只有第一个元素的引用(行{4}),因此需要循环访问列表,直到找到最后一项。为此,我们需要一个指向列表中current项的变量(行{2})。循环访问列表时,当current.next元素为null时,我们就知道已经到达列表尾部了。然后要做的就是让当前(也就是最后一个)元素的next指针指向想要添加到列表的节点(行{5})。下图展示了这个行为:
2.remove方法
现在,让我们看看如何从LinkedList对象中移除元素。移除元素也有两种场景:第一种是移除第一个元素,第二种是移除第一个以外的任一元素。我们要实现两种remove方法:第一种是从特定位置移除一个元素,第二种是根据元素的值移除元素(稍后会展示第二种remove方法)
//现在,让我们看看如何从LinkedList对象中移除元素。移除元素也有两种场景:第一种是移除第一个元素,第二种是移除第一个以外的任一元素。
this.removeAt = function(position){
//验证位置是有效的
if(position>-1 && position<length){
//我们将用current变量创建一个对列表中第一个元素的引用
//head是第一个节点的引用(说明哪一个是头)。current是代表当前节点(遍历或者移动时当时的那个节点current)
var current = head;
var previous;
//需要依靠一个细节来迭代列表,直到到达目标位置
var index = 0;
//移除第一项,因此,如果想移除第一个元素,要做的就是让head指向列表的第二个元素。
//我们将用current变量创建一个对列表中第一个元素的引用
//如果把 head 赋为current.next,就会移除第一个元素。
if(position === 0){
head = current.next;
}else{
while(index++ < position){
previous = current;
//找到删除元素位置的position,此时
current=current.next;
}
////将previous与current的下一项链接起来:跳过current,从而移除它
previous.next = current.next;
}
length--;
return current.element;
}else{
return null
}
}
3.insert方法
//接下来,我们要实现insert方法。使用这个方法可以在任意位置插入一个元素。
this.insert = function(position,element){
//检查越界值
if(position>=0 && position<=length){
var node = new Node(element);
var current = head;
var previous;
var index;
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;
}
}
3.双向链表
双向链表和普通链表的区别在于,在链表中,一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素, 另一个链向前一个元素,如下图所示:
先从实现DoublyLinkedList类所需的变动开始:
function DoubleLinkedList(){
let Node = function(element){
this.element = element;
this.next = null;
this.prev;
}
let length = 0;
let head = null;
let tail = null;
}
双向链表提供了两种迭代列表的方法:从头到尾,或者反过来。我们也可以访问一个特定节点的下一个或前一个元素。在单向链表中,如果迭代列表时错过了要找的元素,就需要回到列表起点,重新开始迭代。这是双向链表的一个优点.
1.insert
向双向链表中插入一个新项跟(单向)链表非常类似。区别在于,链表只要控制一个next指针,而双向链表则要同时控制next和prev(previous,前一个)这两个指针
这是向任意位置插入一个新元素的算法
function DoubleLinkedList(){
let Node = function(element){
this.element = element;
this.next = null;
this.prev;
}
let length = 0;
let head = null;
let tail = null;
this.insert = function(position,element){
//检查越界值
if(position>=0 && position<=length){
let node = new Node(element);
var current = head;
var previous;
var index;
if(position === 0){
if(!head){
head = node;
tail = node;
}else{
node.nexe = 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;
}
};
}
2.removeAt
从双向链表中移除元素跟链表非常类似。唯一的区别就是还需要设置前一个位置的指针。下面来看一下它的实现:
this.removeAt = function(position){
//检查越界值
if (position > -1 && position < length){
let current = head,
previous,
index = 0;
//移除第一项
if (position === 0){
head = current.next; // {1}
//如果只有一项,更新tail //新增的
if (length === 1){ // {2}
tail = null;
} else {
head.prev = null; // {3}
}
} else if (position === length-1){ //最后一项 //新增的
current = tail; // {4}
tail = current.prev;
tail.next = null;
} else {
while (index++ < position){ // {5}
previous = current;
current = current.next;
}
//将previous与current的下一项链接起来——跳过current
previous.next = current.next; // {6}
current.next.prev = previous; //新增的
}
length--;
return current.element;
} else {
return null;
}
};
4.循环链表
循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用。循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用null,而是指向第一个元素(head),如下图所示:
双向循环链表有指向head元素的tail.next,和指向tail元素的head.prev