链表是由一组节点组成的集合。每个节点都使用一个对象的引用指向它的后继,指向另一个节点的引用叫做链。数组元素靠它们的位置进行引用,链表元素则是靠相互之间的关系进行引用。
基于对象设计一个链表
Node类
Node类包含两个属性:element保存节点上的数据,next保存指向下一个节点的链接,使用构造函数来创建节点。
function Node(element){
this.element = element;
this.next = null;
}
LinkedList类
该类的功能包括插入删除节点,在列表中查找给定的值。该类也是一个构造函数,链表只有一个属性,使用一个Node对象来保存该链表的头结点。
function LList(){
this.head = new Node("head");
this.find = find;
this.insert = insert;
this.display = display;
}
插入新节点
向链表中插入新节点时,需要明确指出要在哪个节点前面或者后面插入,这里需要先创建一个辅助方法find()用来查找给定数据并返回给定数据对应的节点。
find()方法演示了如何在链表上进行移动。首先,创建一个新节点,并将链表的头节点赋给这个新创建的节点。然后在链表上进行循环,如果当前节点的 element属性和我们要找的信息不符,就从当前节点移动到下一个节点。如果查找成功,该方法返回包含该数据的节点,否则,返回null。
function find(item){
var currNode = this.head;
while(currNode.element!=item){
currNode = currNode.next;
}
return currNode;
}
通过find()找到“后面”的节点,就可以通过insert()方法将新节点插入链表中了,首先将新节点的next属性设置为“后面”节点的next属性对应的值,然后设置“后面”节点的next属性指向新节点。
function insert(newElement,item){
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
current.next = newNode;
}
定义一个display()方法显示链表中的元素:
function display(){
var currNode = this.head;
while(!(currNode.next == null)){
console.log(currNode.next.element);
currNode = currNode.next;
}
}
汇总以上全部代码并进行测试:
function Node(element){
this.element = element;
this.next = null;
}
function LList(){
this.head = new Node("head");
this.find = find;
this.insert = insert;
this.display = display;
}
function find(item){
var currNode = this.head;
while(currNode.element!=item){
currNode = currNode.next;
}
return currNode;
}
function insert(newElement,item){
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
current.next = newNode;
}
function display(){
var currNode = this.head;
while(!(currNode.next == null)){
console.log(currNode.next.element);
currNode = currNode.next;
}
}
var cities = new LList();
cities.insert("Conway","head");
cities.insert("Russellville","Conway");
cities.insert("Alma","Russellville");
cities.display();//Conway Russellville Alma
从链表中删除一个节点
当删除节点时,需要先找到待删除节点前面的节点。找到后修改它的next属性,使其执行待删除节点的下一个节点,通过一个方法findPrevious()来查找指定节点的前一个节点。
该方法遍历链表检查每一个节点的下一个节点是否是待删除节点,找到就返回该节点。
function findPrevious(item){
var currNode = this.head;
while(!(currNode.next == null) && (currNode.next.element != item)){
currNode = currNode.next;
}
return currNode;
}
接下来是删除方法remove()
function remove(item){
var prevNode = this.findPrevious(item);
if(!(prevNode.next == null)){
prevNode.next = prevNode.next.next;
}
}
修改LList类添加两个新加的方法
function LList(){
this.head = new Node("head");
this.find = find;
this.insert = insert;
this.display = display;
this.findPrevious = findPrevious;
this.remove = remove;
}
汇总以上全部代码并进行测试:
function Node(element){
this.element = element;
this.next = null;
}
function LList(){
this.head = new Node("head");
this.find = find;
this.insert = insert;
this.display = display;
this.findPrevious = findPrevious;
this.remove = remove;
}
function find(item){
var currNode = this.head;
while(currNode.element!=item){
currNode = currNode.next;
}
return currNode;
}
function insert(newElement,item){
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
current.next = newNode;
}
function display(){
var currNode = this.head;
while(!(currNode.next == null)){
console.log(currNode.next.element);
currNode = currNode.next;
}
}
function findPrevious(item){
var currNode = this.head;
while(!(currNode.next == null) && (currNode.next.element != item)){
currNode = currNode.next;
}
return currNode;
}
function remove(item){
var prevNode = this.findPrevious(item);
if(!(prevNode.next == null)){
prevNode.next = prevNode.next.next;
}
}
var cities = new LList();
cities.insert("Conway","head");
cities.insert("Russellville","Conway");
cities.insert("Carlisle","Russellville");
cities.insert("Alma","Carlisle");
cities.display();//Conway Russellville Carlisle Alma
cities.remove("Carlisle");
console.log('删除Carlisle==============')//删除Carlisle==============
cities.display();//Conway Russellville Alma
双向链表
尽管从链表的头节点遍历到尾节点很简单,但反过来,从后向前遍历则没那么简单。通过给 Node 对象增加一个属性,该属性存储指向前驱节点的链接,这样就容易多了。此时向链表插人一个节点需要更多的工作,我们需要指出该节点正确的前驱和后继。但是在从链表中删除节点时,效率提高了,不需要再查找待删除节点的前驱节点了。
为Node类增加一个previous属性:
function Node(element){
this.element = element;
this.next = null;
this.previous = null;
}
修改LList类,删除查找上一个节点的方法findPrevious:
function LList(){
this.head = new Node("head");
this.find = find;
this.insert = insert;
this.display = display;
this.remove = remove;
this.dispReverse = dispReverse;
this.findLast = findLast;
}
修改insert方法,赋值previous属性,设置该节点的前驱:
function insert(newElement,item){
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
newNode.previous = current;
current.next = newNode;
}
双向链表的 remove()方法比单向链表的效率更高,因为不需要再查找前驱节点了。首先需要在链表中找出存储待删除数据的节点,然后设置该节点前驱的 next 属性,使其指向待删除节点的后继,设置该节点后继的 previous 属性,使其指向待删除节点的前驱。
function remove(item){
var currNode = this.find(item);
if(!(currNode.next==null)){
currNode.previous.next = currNode.next;
currNode.next.previous = currNode.previous;
currNode.next = null;
currNode.previous = null;
}
}
设置findLast()方法查找链表中的最后一个节点,用来辅助反序显示链表元素这类任务:
function findLast(){
var currNode = this.head;
while(currNode.next!=null){
currNode = currNode.next;
}
return currNode;
}
反序显示链表中的元素:
function dispReverse(){
var currNode = this.head;
currNode = this.findLast();
while(!(currNode.previous == null)){
console.log(currNode.element);
currNode = currNode.previous;
}
}
汇总以上全部代码并进行测试:
function Node(element){
this.element = element;
this.next = null;
this.previous = null;
}
function LList(){
this.head = new Node("head");
this.find = find;
this.insert = insert;
this.display = display;
this.remove = remove;
this.dispReverse = dispReverse;
this.findLast = findLast;
}
function dispReverse(){
var currNode = this.head;
currNode = this.findLast();
while(!(currNode.previous == null)){
console.log(currNode.element);
currNode = currNode.previous;
}
}
function findLast(){
var currNode = this.head;
while(currNode.next!=null){
currNode = currNode.next;
}
return currNode;
}
function find(item){
var currNode = this.head;
while(currNode.element!=item){
currNode = currNode.next;
}
return currNode;
}
function insert(newElement,item){
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
newNode.previous = current;
current.next = newNode;
}
function display(){
var currNode = this.head;
while(!(currNode.next == null)){
console.log(currNode.next.element);
currNode = currNode.next;
}
}
function remove(item){
var currNode = this.find(item);
if(!(currNode.next==null)){
currNode.previous.next = currNode.next;
currNode.next.previous = currNode.previous;
currNode.next = null;
currNode.previous = null;
}
}
var cities = new LList();
cities.insert("Conway","head");
cities.insert("Russellville","Conway");
cities.insert("Carlisle","Russellville");
cities.insert("Alma","Carlisle");
cities.display();//Conway Russellville Carlisle Alma
cities.remove("Carlisle");
console.log('删除Carlisle==============')//删除Carlisle==============
cities.display();//Conway Russellville Alma
console.log('反序==============')//反序==============
cities.dispReverse();//Alma Russellville Conway
循环链表
循环链表和单向链表相似,区别是在创建循环链表的时候,每个头节点的next属性指向它的本身,即:
head.next = head
这样每个节点的next属性都指向链表的头节点,链表的尾节点也指向头节点,形成了循环链表。
注意修改判断条件不然会进入死循环。例如:
function display(){
var currNode = this.head;
while(!(currNode.next == null) && currNode.next.element != "head"){
console.log(currNode.next.element);
currNode = currNode.next;
}
}
全部代码
function Node(element){
this.element = element;
this.next = null;
}
function LList(){
this.head = new Node("head");
this.head.next = this.head;
this.find = find;
this.insert = insert;
this.display = display;
this.findPrevious = findPrevious;
this.remove = remove;
}
function find(item){
var currNode = this.head;
while(currNode.element!=item){
currNode = currNode.next;
}
return currNode;
}
function insert(newElement,item){
var newNode = new Node(newElement);
var current = this.find(item);
newNode.next = current.next;
current.next = newNode;
}
function display(){
var currNode = this.head;
while(!(currNode.next == null) && currNode.next.element != "head"){
console.log(currNode.next.element);
currNode = currNode.next;
}
}
function findPrevious(item){
var currNode = this.head;
while(!(currNode.next == null) && (currNode.next.element != item) && currNode.next.element != "head"){
currNode = currNode.next;
}
return currNode;
}
function remove(item){
var prevNode = this.findPrevious(item);
if(!(prevNode.next == null)){
prevNode.next = prevNode.next.next;
}
}
var cities = new LList();
cities.insert("Conway","head");
cities.insert("Russellville","Conway");
cities.insert("Carlisle","Russellville");
cities.insert("Alma","Carlisle");
cities.display();
cities.remove("Carlisle");
console.log('删除Carlisle==============')
cities.display();