单链表
特点
- 元素在逻辑关系上相邻,在物理位置上不相邻,不像线性表的元素在逻辑和物理上均相邻,链表在执行插入和删除操作时不需要移动大量元素,但也失去了顺序表可随机存取的优点
- 链表的存储单元不连续,因此为了表示每个元素与其直接后继元素的关系,还需存储一个指示其直接后继的信息
- 单链表的结点包括两个域:数据域和指针域。数据域存储元素信息,指针域存储其直接后继的存储位置
- 单链表的存取只能从头指针开始
- 链表中数据的逻辑关系由结点中的指针指示,指针是数据元素之间逻辑关系的映像
- 在归并两个链表为一个链表时,不需要建立额外的新表的空间结点,只需要将原来两个链表中结点之间的关系解除,按归并规则将所有结点链接成一个链表即可。
功能需求
- 插入节点:头插法、尾插法、给定插入的位置
- 删除节点:删除指定位置的节点、删除指定数据域的节点
- 获得节点:获得第一个节点、获得最后一个节点、获得指定位置的节点、获得指定数据域的节点
- 获得链表的节点数
- 链表的遍历
设计实现
使用es5
-
用工厂模式创建节点对象
//采用工厂模式创建节点对象 function createNode(data){ var node=new Object(); node.data=data;//es的变量是松散类型的,可以保存任何类型的数据 node.nextNode=null; return node; }
-
创建链表,返回一个头节点head,以head作为链表的引用,head节点的数据域不保存数据,不计入链表的节点,以head.nextNode作为链表的第一个节点,在链表中的位置是0
//创建链表 function createLink(){ var head=createNode(null); return head; }
-
插入节点
-
头插法
//插入节点(头插法) function insertNodeTop(head,data){ //头插法实际是在特定位置插入节点,可以通过调用insertNodeIndex()函数实现,实现代码的复用 //insertNodeIndex(head,data,0); var node=createNode(data); node.nextNode=head.nextNode; head.nextNode=node; //return head; //js参数是按值传递的,对象是引用类型按引用访问变量,函数内部的head与外部的head按引用访问的是同一个对象,因此这里可以不用设置 return }
-
尾插法
//插入节点(尾插法) function insertNodeLast(head,data){ //尾插法同头插法是在指定位置插入节点,可以通过调用insertNodeIndex()函数实现,区别在于要获得链表的长度 //insertNodeIndex(head,data,getLinkLength(head)); var p=head; while(p.nextNode!==null){//用全等判定,因为 undefined==null 为 true p=p.nextNode; } p.nextNode=createNode(data); }
-
插入指定的位置
//插入指定位置,头节点head不计入节点数,节点位置从0开始 function insertNodeIndex(head,data,index){ //index允许传入字符串或数字,如 "1" 或 1 if((typeof index)=="string"||(typeof index)=="number"){ if((typeof index)=="string"){ index=Number(index);//String转Number,后面涉及到与index有关的“+”,避免出错 } var length=getLinkLength(head); //index=0表示在表头插入,index=length表示插入最后一个节点的后面,当index>=length时表示在末端插入 //当index<0时,在length+index处插入,如果length+index<0 则报错,前面已经进行过index的string转number,不然要注意“+”的自动类型转换 if(index<0){ index=length+index; if(index<0){ //length+index仍然小于 0 ,报错 } } //q是p的前置节点,设置q的原因是当退出循环p===null时,只设置一个p会失去对链表的链接 var q=head; var p=q.nextNode; var pos=0;//记录p所指节点的位置,从 0 开始,第一个节点在链表中的位置是 0 while(p!==null&&pos<index){ q=q.nextNode; p=p.nextNode; pos++; } var node=createNode(data); q.nextNode=node; node.nextNode=p; } else{ //index既不是字符串也不是数字,报错 } }
-
-
删除节点
-
删除指定位置的节点
//删除指定位置的节点 function deleteNodeIndex(head,index){ if(index>=getLinkLength(head)||index<0){//可能涉及到自动类型转换,index可以为字符串 //删除的指定位置的节点不存在,报错 } else{ var q=head; var p=q.nextNode; var pos=0; while(p!==null&&pos<index){ q=q.nextNode; p=p.nextNode; pos++; } q.nextNode=p.nextNode; p=null;//解除对对象节点的引用,垃圾自动回收 } }
-
删除指定数据域的节点
//删除指定数据的节点 function deleteNodeData(head,data){ var q=head; var p=q.nextNode; while(p!==null){ if(p.data===data){//data可能是基本数据类型也可能是引用类型 //删除找到的第一个符合与给定数据相等的节点 q.nextNode=p.nextNode; p=null; return; //删除链表中所有的与给定数据相等的节点 //q.nextNode=p.nextNode; //p=q.nextNode; } else{ q=q.nextNode; p=p.nextNode; } } }
-
-
获得节点
-
获得链表中的第一个节点
//获得链表的第一个节点 function getNodeTop(head){ return head.nextNode;//若链表之中还没有节点则返回null }
-
获得链表的最后一个节点
//获得链表最末尾的节点 function getNodeLast(head){ var p=head; while(p.nextNode!==null){ p=p.nextNode; } //若链表之中还没有节点则返回null if(p===head){ return null; } else{ return p; } }
-
获得指定位置的节点
//获得链表指定位置的节点 function getNodeIndex(head,index){//返回找到的节点的引用,没找到则返回null if(index>=getLinkLength(head)||index<0){//可能涉及到自动类型转换,index可以为字符串 //要获得的指定位置的节点不存在,报错 } else{ var p=head; var pos=-1; while(p.nextNode!==null&&pos<index){ p=p.nextNode; pos++; } if(p===head){ return null;//若链表之中还没有节点则返回null } return p; } }
-
获得指定数据域的节点
//获得指定数据域的节点 function getNodeData(head,data){//返回找到的节点的引用,没找到则返回null var p=head.nextNode; while(p!==null){ if(p.data===data){//data可能是基本数据类型也可能是引用类型 //返回找到的第一个符合与给定数据相等的节点的引用 return p; } else{ p=p.nextNode; } } return null; }
-
-
获得链表的节点数
//获得链表的节点数 function getLinkLength(head){ var length=0; var p=head.nextNode; while(p!==null){ length++; p=p.nextNode; } return length; }
-
链表的遍历
//链表遍历 function traverse(head){ var p=head.nextNode; while(p!==null){ console.log(p.data); p=p.nextNode; } }
测试
//测试
function test(){
var strs=[1,2,3,4,5,6,7,8,9];
var head=createLink();
strs.forEach(function(value,index,array){
insertNodeTop(head,value);//头插法
})
strs.forEach(function(value,index,array){
insertNodeLast(head,value);//尾插法
})
traverse(head);
console.log("--------");
insertNodeIndex(head,-1,"0");//在头部插入,index为字符串
traverse(head);
console.log("----------");
insertNodeIndex(head,-2,getLinkLength(head));//在尾部插入
traverse(head);
console.log("----------");
insertNodeIndex(head,-3,50);//插入位置超出链表的长度,在尾部插入
traverse(head);
console.log("-----------");
insertNodeIndex(head,-4,4);//在中部插入
traverse(head);
console.log("-----------");
insertNodeIndex(head,-5,-1);//插入位置为负数
traverse(head);
console.log("-----------");
deleteNodeIndex(head,0);//删除第一个节点
traverse(head);
console.log("-----------");
deleteNodeIndex(head,getLinkLength(head)-1);//删除尾部节点
traverse(head);
console.log("-----------");
deleteNodeIndex(head,1);//删除第二个节点
traverse(head);
console.log("-----------");
console.log(getNodeTop(head).data);//获得第一个节点
console.log(getNodeIndex(head,0).data);
console.log("-----------");
console.log(getNodeLast(head).data);//获得最后一个节点
console.log(getNodeIndex(head,getLinkLength(head)-1).data);
console.log("-----------");
console.log(getNodeIndex(head,5).data);//获得第二个节点
}