链表:
-
存储多个元素,另外一个选择就是使用链表。
-
不同于数组,链表中的元素在内存中不必是连续的空间。
-
链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针)组成。
-
链表优点:
内存空间不必是连续的,可以充分利用计算机的内存,实现灵活的内存动态管理。
链表不必在创建时就确定大小,并且大小可以无限延伸下去。
链表在插入和删除数据时,时间复杂度可以达到 O(1),相对数组效率高很多。
-
链表缺点:
访问任何一个位置的元素时,需要从头开始访问。(无法跳过第一个元素访问任何一个元素)
无法通过下标值直接访问元素,需要从头开始一个个访问,直到找到对应的元素。
虽然可以轻松地到达下一个节点,但是回到前一个节点是很难的。
-
head 属性指向链表的第一个节点。 链表中的最后一个节点指向
null
。 当链表中一个节点也没有的时候,head 直接指向null
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 封装单向链表
function LinkedList(){
// 内部类:创建节点类
function Node(data){
this.data=data
// 因为如果就只有一个节点,那next就为null
this.next=null
}
// 属性
this.length=0//链表头部
this.head=null
// -----------------链表的常见操作-------------------
// append()---向链表尾部添加一个新的项。
LinkedList.prototype.append=function(data){//传入要添加的元素
// 1.创建新的节点
var newNode=new Node();
// 2.追加新的节点
// 2.1判断是否插入的是第一个节点(即原来该链表为空)
if(this.length===0){
// 当插入的是第一个节点的时候,就将head的next指向新的节点
this.head=newNode
}else{//2.2插入的不是第一个节点(即原来链表不为空)
// 则要查找最后一个元素,将要插入的元素,放在最后
// 定义一个数值,用于遍历整个链表
var cuurent=this.head
// 循环依次查找最后一个节点(最后一个节点的next为null)
while(cuurent.next){
// 将cuurent的next赋值给cuurent
cuurent=cuurent.next
}
// 3.记得length+1
this.length+=1
}
}
// toString方法-- 由于链表项使用了 Node 类,就需要重写继承自 JavaScript 对象默认的 toString 方法,让其只输出元素的值。
LinkedList.prototype.toString=function(){
// 1.定义一个变量,用于遍历整个链表
var cuurent=this.head
// 2.定义一个空的字符串
var listString=''
// 3.循环获取一个个节点
// 遍历所有的节点,拼接为字符串,直到节点为null
while(cuurent){//如果不为空,则进入
listString+=cuurent.data
cuurent=cuurent.next
}
return listString
}
// insert方法--向链表的特定位置插入一个新的项。
LinkedList.prototype.insert=function(position,data){
// position 新插入节点的位置
// position = 0 表示新插入后是第一个节点
// position = 1 表示新插入后是第二个节点,以此类推
// 1.对position进行越界判断:不能小于0或者大于链表长度
if(position < 0 || position>this.length)
return false
// 2.根据data创建newNode,创建一个新的节点
var newNode=new Node(data)
// 3.判断插入的位置是否是第一个
if(position===0){
// 让新节点的next指向 原来的第一个节点
newNode.next=this.head
// head重新赋值为newNode
this.head=newNode
}else{// 0 < position <= length 的情况
var index=0
// cuurent--用current不断遍历链表
var current=this.head
// prevoius--表示得到下标的前一个数值
var previous=null//设置为null,是因为当current指向第一个节点的时候,前面的一个是空的
while(index<position){
// 在 0 ~ position 之间遍历,不断地更新 currentNode 和 previousNode
// 直到找到要插入的位置
previous=current
current=current.next
}
// 在当前节点和当前节点的上一节点之间插入新节点
newNode=current
previous.next=newNode
}
// 4.length+1
this.length+=1
return true
}
// get方法--获取对应位置的元素。
LinkedList.prototype.get=function(position){
// 1.越界判断:不能小于0 或者大于链表长度
// 这里可以postition==this.length,包括最后一个元素
if(position<0 || position>this.length)
return null
// 2.获取指定position节点的data
var current=this.head
var index=0
while(index<position){
current=current.data
index++
}
// 返回data
return current.data
}
// indexOf方法--返回元素在链表中的索引。如果链表中没有该元素就返回-1。
LinkedList.prototype.indexOf=function(data){
// 1.定义2个变量
var current=this.head
var index=0
// 2.开始查找
while(current){
if(current.data==data){//当找到该元素的时候
return index
}
// 没有找到该元素的时候
current=current.next
index++
}
// 找到最后没有找到,返回-1
return -1
}
// updata方法--修改某个位置的元素。
LinkedList.prototype.updata=function (position,newData){
// 涉及到position都要进行越界判断
// 1.越界判断
if(position<0 || position>=this.length) return false
// 2.查找正确的节点
var current=this.head
var index=0
// 通过循环遍历,找到指定的position
while(index<position){
current=current.next
index++
}
// 3.将position位置的node的data修改为newData
current.data=newData
return current.data
}
// removeAt方法--从链表的特定位置移除一项。
LinkedList.prototype.removeAt=function (position){
// 1.越界判断
if(position < 0 || position>= this.length) return null
// 2.判断是否删除的是第一个节点
var current=this.head
if(position==0){//是第一个节点
// 将第二个节点的赋值给头节点
this.head=this.head.next
}else{
// position>0的情况
// 通过循环遍历,找到指定position的节点,赋值到current
var index=0
var previous=null
while(index++<position){
previous=current
// 让上一节点的next指向到当前节点的next,相当于删除
current=current.next
}
// 前一个节点的next指向,current的next即可
previous.next=current.next
}
// 3.length-1
this.length-=1
}
// remove移除元素--从链表中移除一项。
LinkedList.prototype.remove=function(data){
// 1.获取data在列表中的位置
var position=this.indexOf(data)
// 2.根据位置信息,删除节点
return this.removeAt(position)
}
// isEmpty方法--如果链表中不包含任何元素,返回 trun,如果链表长度大于 0 则返回 false。
LinkedList.prototype.isEmpty=function(){
return this.length==0
}
}
// size方法--返回链表包含的元素个数,与数组的 length 属性类似。
LinkedList.prototype.size=function(){
return this.size
}
// 测试方法
var linkedList=new LinkedList();
// 测试 append 方法
linkedList.append('AA');
linkedList.append('BB');
linkedList.append('CC');
console.log(linkedList);
// 测试toString
console.log(linkedList.toString());//AA BB CC
// 测试insert方法
linkedList.insert(0, "123");
linkedList.insert(2, "456");
console.log(linkedList.toString()); //--> 123
// 测试get方法
console.log(linkedList.get(0)); //--> 123
console.log(linkedList.get(1)); //--> AA
// 测试indexOf方法
console.log(linkedList.indexOf("AA")); //--> 1
console.log(linkedList.indexOf("ABC")); //--> -1
// 测试updata方法
linkedList.update(0, "12345");
console.log(linkedList.toString()); //--> 12345 AA 456 BB CC
linkedList.update(1, "54321");
console.log(linkedList.toString()); //--> 12345 54321 456 BB CC
// 测试 removeAt 方法
linkedList.removeAt(3);
console.log(linkedList.toString()); //--> 12345 54321 456 CC
// 测试 remove 方法
linkedList.remove("CC");
console.log(linkedList.toString()); //--> 12345 54321 456
// 测试 isEmpty 方法
console.log(linkedList.isEmpty()); //--> false
// 测试 size 方法
console.log(linkedList.size()); //--> 3
</script>
</body>
</html>
append图解:
当到达最后一个的时候:current.next=newNode
insert图解:
情况一:判断插入的位置是否是第一个
情况二:在链表中插入元素