链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有的语言称为指针或连接)组成。类似于火车头,一节车厢载着乘客(数据),通过节点连接另一节车厢。
- head属性指向链表的第一个节点;
- 链表中的最后一个节点指向null;
- 当链表中一个节点也没有的时候,head直接指向null;
- 数组的创建通常需要申请一段连续的内存空间(一整块内存),并且大小是固定的。所以当原数组不能满足容量需求时,需要扩容(一般情况下是申请一个更大的数组,比如2倍,然后将原数组中的元素复制过去)。
- 在数组的开头或中间位置插入数据的成本很高,需要进行大量元素的位移。
链表中的元素在内存中不必是连续的空间,可以充分利用计算机的内存,实现灵活的内存动态管理。
链表不必在创建时就确定大小,并且大小可以无限地延伸下去。
链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组效率高很多。
- 链表访问任何一个位置的元素时,都需要从头开始访问(无法跳过第一个元素访问任何一个元素)。
- 无法通过下标值直接访问元素,需要从头开始一个个访问,直到找到对应的元素。
- 虽然可以轻松地到达下一个节点,但是回到前一个节点是很难的。
- append(element):向链表尾部添加一个新的项;
- insert(position,element):向链表的特定位置插入一个新的项;
- get(position):获取对应位置的元素;
- indexOf(element):返回元素在链表中的索引。如果链表中没有该元素就返回-1;
- update(position,element):修改某个位置的元素;
- removeAt(position):从链表的特定位置移除一项;
- remove(element):从链表中移除一项;
- isEmpty():如果链表中不包含任何元素,返回trun,如果链表长度大于0则返回false;
- size():返回链表包含的元素个数,与数组的length属性类似;
- toString():由于链表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值;
首先需要弄清楚:下文中的position指的是两个节点之间,并且与index的关系如下图所示:
position的值一般表示position所指位置的下一个节点。当position的值与index的值相等时,比如position = index = 1,那么它们都表示Node2。
先创建单向链表类Linklist,并添加基本属性,再实现单向链表的常用方法:
// 封装单向链表类function LinkList(){// 封装一个内部类:节点类function Node(data){this.data = data;this.next = null;}// 属性// 属性head指向链表的第一个节点this.head = null;this.length = 0;}
// 封装单向链表类function LinkList() {// 封装一个内部类:节点类function Node(data) {this.data = data;this.next = null;}// 属性// 属性head指向链表的第一个节点this.head = null;this.length = 0;// 1.追加方法LinkList.prototype.append=function(data){// 1.创建新的节点var newNode=new Node(data)// 2.判断是否添加的是第一个节点if(this.length===0){ //2.1 是第一个节点// 将head指向newNodethis.head=newNode}else{// 先设置一个变量指向当前链表的最后一个节点var current=this.headwhile(current.next){//当不是最后一个节点,则不断向下指下去current=current.next}// 最后节点的next指向新的节点current.next=newNode}// 3.长度加1this.length+=1}}
//测试代码//1.创建LinkListlet list = new LinkList()//2.测试append方法list.append('aaa')list.append('bbb')list.append('ccc')console.log(list);
// 实现toString方法LinkList.prototype.toString = () => {// 1.定义变量let current = this.headlet listString = ""// 2.循环获取一个个的节点while(current){ listString += current.data + " "current = current.next//千万不要忘了拼接完一个节点数据之后,让current指向下一个节点}return listString}
//测试代码//1.创建LinkListlet list = new LinkList()//2.插入数据list.append('aaa')list.append('bbb')list.append('ccc')//3.测试toString方法console.log(list.toString());
// 实现insert方法LinkList.prototype.insert = (position, data) => {//理解positon的含义:position=0表示新界点插入后要成为第1个节点,position=2表示新界点插入后要成为第3个节点//1.对position进行越界判断:要求传入的position不能是负数且不能超过LinkList的lengthif(position < 0 || position > this.length){return false}//2.根据data创建newNodelet newNode = new Node(data)//3.插入新节点//情况1:插入位置position=0if(position == 0){// 让新节点指向第一个节点newNode.next = this.head// 让head指向新节点this.head = newNode//情况2:插入位置position>0(该情况包含position=length)} else{let index = 0let previous = nulllet current = this.head//步骤1:通过while循环使变量current指向position位置的后一个节点(注意while循环的写法)while(index++ < position){//步骤2:在current指向下一个节点之前,让previous指向current当前指向的节点previous = currentcurrent = current.next}// 步骤3:通过变量current(此时current已经指向position位置的后一个节点),使newNode指向position位置的后一个节点newNode.next = current//步骤4:通过变量previous,使position位置的前一个节点指向newNodeprevious.next = newNode/*启示:1.我们无法直接操作链表中的节点,但是可以通过变量指向这些节点,以此间接地操作节点(替身使者);比如current指向节点3,想要节点3指向节点4只需要:current.next = 4即可。2.两个节点间是双向的,想要节点2的前一个节点为节点1,可以通过:1.next=2,来实现;*/}//4.新节点插入后要length+1this.length += 1;return true}
inset方法实现的过程:根据插入节点位置的不同可分为多种情况:
通过: newNode.next = this.head,建立连接1;
通过: this.head = newNode,建立连接2;(不能先建立连接2,否则this.head不再指向Node1)
首先定义两个变量previous和curent分别指向需要插入位置pos = X的前一个节点和后一个节点;
然后,通过:newNode.next = current,改变指向3;
最后,通过:previous.next = newNode,改变指向4;
情况2也包含了pos = length的情况,该情况下current和newNode.next都指向null;建立连接3和连接4的方式与情况2相同。
//测试代码//1.创建LinkListlet list = new LinkList()//2.插入数据list.append('aaa')list.append('bbb')list.append('ccc')//3.测试insert方法list.insert(0, '在链表最前面插入节点');list.insert(2, '在链表中第二个节点后插入节点');list.insert(5, '在链表最后插入节点');alert(list);console.log(list);
//实现get方法LinkList.prototype.get = (position) => {//1.越界判断// 当position = length时,取到的是null所以0 =< position < lengthif(position < 0 || position >= this.length){return null}//2.获取指定的positon位置的后一个节点的data//同样使用一个变量间接操作节点let current = this.headlet index = 0while(index++ < position){current = current.next}return current.data}
get方法的实现过程:以获取position = 2为例,如下图所示:
//测试代码//1.创建LinkListlet list = new LinkList()//2.插入数据list.append('aaa')list.append('bbb')list.append('ccc') //3.测试get方法console.log(list.get(0));console.log(list.get(1));
//实现indexOf方法LinkList.prototype.indexOf = data => {//1.定义变量let current = this.headlet index = 0//2.开始查找:只要current不指向null就一直循环while(current){if(current.data == data){return index}current = current.nextindex += 1} //3.遍历完链表没有找到,返回-1return -1}
indexOf方法的实现过程:
//测试代码//1.创建LinkListlet list = new LinkList()//2.插入数据list.append('aaa')list.append('bbb')list.append('ccc') //3.测试indexOf方法console.log(list.indexOf('aaa'));console.log(list.indexOf('ccc'));
//实现update方法LinkList.prototype.update = (position, newData) => {//1.越界判断//因为被修改的节点不能为null,所以position不能等于lengthif(position < 0 || position >= this.length){return false}//2.查找正确的节点let current = this.headlet index = 0while(index++ < position){current = current.next}//3.将position位置的后一个节点的data修改成newDatacurrent.data = newData//返回true表示修改成功return true}
//测试代码//1.创建LinkListlet list = new LinkList()//2.插入数据list.append('aaa')list.append('bbb')list.append('ccc') //3.测试update方法list.update(0, '修改第一个节点')list.update(1, '修改第二个节点')console.log(list);console.log(list.update(3, '能修改么'));
//实现removeAt方法LinkList.prototype.removeAt = position => {//1.越界判断if (position < 0 || position >= this.length) {//position不能为lengthreturn null}//2.删除元素//情况1:position = 0时(删除第一个节点)let current = this.headif (position ==0 ) {//情况2:position > 0时this.head = this.head.next}else{let index = 0let previous = nullwhile (index++ < position) {previous = currentcurrent = current.next}//循环结束后,current指向position后一个节点,previous指向current前一个节点//再使前一个节点的next指向current的next即可previous.next = current.next}//3,length-1this.length -= 1//返回被删除节点的data,为此current定义在最上面return current.data}
removeAt方法的实现过程:删除节点时存在多种情况:
通过:this.head = this.head.next,改变指向1即可;
虽然Node1的next仍指向Node2,但是没有引用指向Node1,则Node1会被垃圾回收器自动回收,所以不用处理Node1指向Node2的引用next。
注意:position = length时position后一个节点为null不能删除,因此position != length;
首先,定义两个变量previous和curent分别指向需要删除位置pos = x的前一个节点和后一个节点;
然后,通过:previous.next = current.next,改变指向1即可;
随后,没有引用指向Node3,Node3就会被自动回收,至此成功删除Node3 。
//测试代码//1.创建LinkListlet list = new LinkList()//2.插入数据list.append('aaa')list.append('bbb')list.append('ccc')//3.测试removeAt方法console.log(list.removeAt(0));console.log(list.removeAt(0));console.log(list);
其他方法包括:remove(element)、isEmpty()、size()
/*-------------其他方法的实现--------------*///一.实现remove方法LinkList.prototype.remove = (data) => {//1.获取data在列表中的位置let position = this.indexOf(data)//2.根据位置信息,删除结点return this.removeAt(position)}//二.实现isEmpty方法LinkList.prototype.isEmpty = () => {return this.length == 0}//三.实现size方法LinkList.prototype.size = () => {return this.length}
//测试代码//1.创建LinkListlet list = new LinkList()//2.插入数据list.append('aaa')list.append('bbb')list.append('ccc')/*---------------其他方法测试----------------*///remove方法console.log(list.remove('aaa'));console.log(list);//isEmpty方法console.log(list.isEmpty());//size方法console.log(list.size());
// 封装链表类function LinkList(){// 封装一个内部类:节点类function Node(data){this.data = data;this.next = null;}// 属性// 属性head指向链表的第一个节点this.head = null;this.length = 0;// 一.实现append方法LinkList.prototype.append = data => {//1.创建新节点let newNode = new Node(data)//2.添加新节点//情况1:只有一个节点时候if(this.length == 0){this.head = newNode//情况2:节点数大于1,在链表的最后添加新节点 }else { //让变量current指向第一个节点let current = this.head//当current.next(下一个节点不为空)不为空时,一直循环,直到current指向最后一个节点while (current.next){current = current.next}// 最后节点的next指向新的节点current.next = newNode}//3.添加完新结点之后length+1this.length += 1}// 二.实现toString方法LinkList.prototype.toString = () => {// 1.定义变量let current = this.headlet listString = ""// 2.循环获取一个个的节点while(current){ listString += current.data + " "current = current.next//千万不要忘了拼接完一个节点数据之后,让current指向下一个节点}return listString}// 三.实现insert方法LinkList.prototype.insert = (position, data) => {//理解positon的含义:position=0表示新界点插入后要成为第1个节点,position=2表示新界点插入后要成为第3个节点//1.对position进行越界判断:要求传入的position不能是负数且不能超过LinkList的lengthif(position < 0 || position > this.length){return false}//2.根据data创建newNodelet newNode = new Node(data)//3.插入新节点//情况1:插入位置position=0if(position == 0){// 让新节点指向第一个节点newNode.next = this.head// 让head指向新节点this.head = newNode//情况2:插入位置position>0(该情况包含position=length)} else{let index = 0let previous = nulllet current = this.head//步骤1:通过while循环使变量current指向position位置的后一个节点(注意while循环的写法)while(index++ < position){//步骤2:在current指向下一个节点之前,让previous指向current当前指向的节点previous = currentcurrent = current.next}// 步骤3:通过变量current(此时current已经指向position位置的后一个节点),使newNode指向position位置的后一个节点newNode.next = current//步骤4:通过变量previous,使position位置的前一个节点指向newNodeprevious.next = newNode//我们无法直接操作链表中的节点,但是可以通过变量指向这些节点,以此间接地操作节点;}//4.新节点插入后要length+1this.length += 1;return true}//四.实现get方法LinkList.prototype.get = (position) => {//1.越界判断// 当position = length时,取到的是null所以0 =< position < lengthif(position < 0 || position >= this.length){return null}//2.获取指定的positon位置的后一个节点的data//同样使用一个变量间接操作节点let current = this.headlet index = 0while(index++ < position){current = current.next}return current.data}//五.实现indexOf方法LinkList.prototype.indexOf = data => {//1.定义变量let current = this.headlet index = 0//2.开始查找:只要current不指向null就一直循环while(current){if(current.data == data){return index}current = current.nextindex += 1} //3.遍历完链表没有找到,返回-1return -1}//六.实现update方法LinkList.prototype.update = (position, newData) => {//1.越界判断//因为被修改的节点不能为null,所以position不能等于lengthif(position < 0 || position >= this.length){return false}//2.查找正确的节点let current = this.headlet index = 0while(index++ < position){current = current.next}//3.将position位置的后一个节点的data修改成newDatacurrent.data = newData//返回true表示修改成功return true}//七.实现removeAt方法LinkList.prototype.removeAt = position => {//1.越界判断if (position < 0 || position >= this.length) {return null}//2.删除元素//情况1:position = 0时(删除第一个节点)let current = this.headif (position ==0 ) {//情况2:position > 0时this.head = this.head.next}else{let index = 0let previous = nullwhile (index++ < position) {previous = currentcurrent = current.next}//循环结束后,current指向position后一个节点,previous指向current前一个节点//再使前一个节点的next指向current的next即可previous.next = current.next}//3,length-1this.length -= 1//返回被删除节点的data,为此current定义在最上面return current.data}/*-------------其他方法的实现--------------*///八.实现remove方法LinkList.prototype.remove = (data) => {//1.获取data在列表中的位置let position = this.indexOf(data)//2.根据位置信息,删除结点return this.removeAt(position)}//九.实现isEmpty方法LinkList.prototype.isEmpty = () => {return this.length == 0}//十.实现size方法LinkList.prototype.size = () => {return this.length}}
双向链表:既可以从头遍历到尾,又可以从尾遍历到头。也就是说链表连接的过程是双向的,它的实现原理是:一个节点既有向前连接的引用,也有一个向后连接的引用。
双向链表的缺点:
双向链表的结构:
- 双向链表不仅有head指针指向第一个节点,而且有tail指针指向最后一个节点;
- 每一个节点由三部分组成:item储存数据、prev指向前一个节点、next指向后一个节点;
- 双向链表的第一个节点的prev指向null;
- 双向链表的最后一个节点的next指向null;
先创建双向链表类DoubleLinklist,并添加基本属性,再实现双向链表的常用方法:
//封装双向链表类function DoubleLinklist(){//封装内部类:节点类function Node(data){this.data = datathis.prev = nullthis.next = null}//属性this.head = nullthis.tail ==nullthis.length = 0}
//append方法DoubleLinklist.prototype.append = data => {//1.根据data创建新节点let newNode = new Node(data)//2.添加节点//情况1:添加的是第一个节点if (this.length == 0) {this.tail = newNodethis.head = newNode //情况2:添加的不是第一个节点}else {newNode.prev = this.tailthis.tail.next = newNodethis.tail = newNode}//3.length+1this.length += 1}
添加节点时分为多种情况:
情况2:添加的不是第一个节点,如下图所示:只需要改变相关引用的指向即可。
要注意改变变量指向的顺序,最后修改tail指向,这样未修改前tail始终指向原链表的最后一个节点。
//测试代码//1.创建双向链表let list = new DoubleLinklist()//2.测试append方法list.append('aaa')list.append('bbb')list.append('ccc')console.log(list);
//将链表转变为字符串形式//一.toString方法DoubleLinklist.prototype.toString = () => {return this.backwardString()}//二.forwardString方法DoubleLinklist.prototype.forwardString = () => {//1.定义变量let current =this.taillet resultString = ""//2.依次向前遍历,获取每一个节点while (current) {resultString += current.data + "--"current = current.prev }return resultString}//三.backwardString方法DoubleLinklist.prototype.backwardString = () => {//1.定义变量let current = this.headlet resultString = ""//2.依次向后遍历,获取每一个节点while (current) {resultString += current.data + "--"current = current.next}return resultString}
三种获取字符串的方法:toString()、forwardString()、backwardString()实现原理相似,仅以backWardString方法为例:
//测试代码//1.创建双向链表let list = new DoubleLinklist()//2.测试字符串方法 list.append('aaa')list.append('bbb')list.append('ccc')console.log(list.toString());console.log(list.forwardString());console.log(list.backwardString());
//insert方法DoubleLinklist.prototype.insert = (position, data) => {//1.越界判断if (position < 0 || position > this.length) return false//2.根据data创建新的节点let newNode = new Node(data)//3.插入新节点//原链表为空//情况1:插入的newNode是第一个节点if (this.length == 0) {this.head = newNodethis.tail = newNode//原链表不为空}else {//情况2:position == 0if (position == 0) {this.head.prev = newNodenewNode.next = this.headthis.head = newNode//情况3:position == this.length } else if(position == this.length){this.tail.next = newNodenewNode.prev = this.tailthis.tail = newNode//情况4:0 < position < this.length}else{let current = this.headlet index = 0while(index++ < position){current = current.next}//修改pos位置前后节点变量的指向newNode.next = currentnewNode.prev = current.prevcurrent.prev.next = newNodecurrent.prev = newNode}}//4.length+1this.length += 1return true//返回true表示插入成功}
插入节点可分为多种情况:
当原链表为空时:
当原链表不为空时:
首先,通过:this.head.prev = newNode,改变指向1;
然后,通过:newNode.next = this.head,改变指向2;
最后,通过:this.head = newNode,改变指向3;
首先,通过:this.tail.next = newNode,改变指向1;(注意这里使用this.tail指向原链表最后一个节点,而不是this.head。因为当length>1时,this.head != this.tail。)
然后,通过:newNode.prev = this.tail,改变指向2;
最后,通过:this.tail = newNode,改变指向3;
首先,需要定义变量current按照之前的思路,通过while循环找到position位置的后一个节点,循环结束后index = position
如下图所示:当position = 1时,current就指向了Node2。这样操作current就等同于间接地操作Node2,还可以通过current.prev间接获取Node1。得到了newNode的前一个节点和后一个节点就可以通过改变它们的prev和next变量的指向来插入newNode了。
通过:newNode.next = current,改变指向1;
通过:newNode.prev = current.prev,改变指向2;
通过:current.prev.next = newNode,改变指向3;
注意必须最后才修改current.prev的指向,不然就无法通过current.prev获取需要操作的Node1了。
通过:current.prev = current,改变指向4;
//测试代码//1.创建双向链表let list = new DoubleLinklist()//2.测试insert方法list.insert(0, '插入链表的第一个元素')list.insert(0, '在链表首部插入元素')list.insert(1, '在链表中间插入元素')list.insert(3, '在链表尾部插入元素')console.log(list);alert(list)
//update方法DoubleLinklist.prototype.update = (position, newData) => {//1.越界判断if (position < 0 || position >= this.length) {return false}//2.寻找正确的节点let current = this.headlet index = 0//this.length / 2 > position:从头开始遍历if (this.length / 2 > position) {while(index++ < position){current = current.next}//this.length / 2 =< position:从尾开始遍历}else{current = this.tailindex = this.length - 1while (index -- > position) {current = current.prev}}//3.修改找到节点的datacurrent.data = newDatareturn true//表示成功修改}
以(index++ < position)为条件,通过while循环遍历链表中的节点(停止条件为index = position)。循环结束后,current指向需要修改的节点。
//测试代码//1.创建双向链表let list = new DoubleLinklist()//2.测试update方法list.append('a')list.append('b')console.log(list.update(1, 'c'));console.log(list);
//removeAt方法DoubleLinklist.prototype.removeAt = position => {//1.越界判断if (position < 0 || position >= this.length) {return null}//2.删除节点//当链表中length == 1//情况1:链表只有一个节点let current = this.head//定义在最上面方便以下各种情况返回current.dataif (this.length == 1) {this.head = nullthis.tail = null//当链表中length > 1} else{//情况2:删除第一个节点if (position == 0) {this.head.next.prev = nullthis.head = this.head.next//情况3:删除最后一个节点}else if(position == this.length - 1){current = this.tail//该情况下返回被删除的最后一个节点this.tail.prev.next = nullthis.tail = this.tail.prev}else{//情况4:删除链表中间的节点let index = 0while(index++ < position){current = current.next}current.next.prev = current.prevcurrent.prev.next = current.next}}//3.length -= 1this.length -= 1return current.data//返回被删除节点的数据}
删除节点时有多种情况:
当链表的length = 1时:
当链表的length > 1时:
情况2:删除链表中的第一个节点:
通过:this.head.next.prev = null,改变指向1;
通过:this.head = this.head.next,改变指向2;
虽然Node1有引用指向其它节点,但是没有引用指向Node1,那么Node1会被自动回收。
情况3:删除链表中的最后一个节点:
通过:this.tail.prev.next = null,修改指向1;
通过:this.tail = this.tail.prev,修改指向2;
通过while循环找到需要删除的节点,比如position = x,那么需要删除的节点就是Node(x+1),如下图所示:
通过:current.next.prev = current.prev,修改指向1;
通过:current.prev.next = current.next,修改指向2;
这样就没有引用指向Node(x+1)了(current虽指向Node(x+1),但current时临时变量,该方法执行完就会被销毁),随后Node(x+1)就会被自动删除。
//测试代码//1.创建双向链表let list = new DoubleLinklist() //2.测试removeAt方法list.append('a')list.append('b')list.append('c')console.log(list.removeAt(1));console.log(list);
其他方法包括:remove(element)、isEmpty()、size()、getHead()、getTail()
/*--------------------其他方法-------------------*///八.remove方法DoubleLinklist.prototype.remove = data => {//1.根据data获取下标值let index = this.indexOf(data)//2.根据index删除对应位置的节点return this.removeAt(index)}//九.isEmpty方法DoubleLinklist.prototype.isEmpty = () => {return this.length == 0}//十.size方法DoubleLinklist.prototype.size = () => {return this.length}//十一.getHead方法:获取链表的第一个元素DoubleLinklist.prototype.getHead = () => {return this.head.data}//十二.getTail方法:获取链表的最后一个元素DoubleLinklist.prototype.getTail = () => {return this.tail.data}
//测试代码//1.创建双向链表let list = new DoubleLinklist() /*------------其他方法的测试--------------*/list.append('a')list.append('b')list.append('c')list.append('d')//remove方法console.log(list.remove('a'));console.log(list);//isEmpty方法console.log(list.isEmpty());//size方法console.log(list.size());//getHead方法console.log(list.getHead());//getTead方法console.log(list.getTail());
//封装双向链表
function DoubleLinklist(){//封装内部类:节点类function Node(data){this.data = datathis.prev = nullthis.next = null}//属性this.head = nullthis.tail ==nullthis.length = 0//常见的操作:方法//一.append方法DoubleLinklist.prototype.append = data => {//1.根据data创建新节点let newNode = new Node(data)//2.添加节点//情况1:添加的是第一个节点if (this.length == 0) {this.tail = newNodethis.head = newNode //情况2:添加的不是第一个节点}else {newNode.prev = this.tailthis.tail.next = newNodethis.tail = newNode}//3.length+1this.length += 1}//二.将链表转变为字符串形式//2.1.toString方法DoubleLinklist.prototype.toString = () => {return this.backwardString()}//2.2.forwardString方法DoubleLinklist.prototype.forwardString = () => {//1.定义变量let current =this.taillet resultString = ""//2.依次向前遍历,获取每一个节点while (current) {resultString += current.data + "--"current = current.prev }return resultString}//2.3.backwardString方法DoubleLinklist.prototype.backwardString = () => {//1.定义变量let current = this.headlet resultString = ""//2.依次向后遍历,获取每一个节点while (current) {resultString += current.data + "--"current = current.next}return resultString}//三.insert方法DoubleLinklist.prototype.insert = (position, data) => {//1.越界判断if (position < 0 || position > this.length) return false//2.根据data创建新的节点let newNode = new Node(data)//3.插入新节点//原链表为空//情况1:插入的newNode是第一个节点if (this.length == 0) {this.head = newNodethis.tail = newNode//原链表不为空}else {//情况2:position == 0if (position == 0) {this.head.prev = newNodenewNode.next = this.headthis.head = newNode//情况3:position == this.length } else if(position == this.length){this.tail.next = newNodenewNode.prev = this.tailthis.tail = newNode//情况4:0 < position < this.length}else{let current = this.headlet index = 0while(index++ < position){current = current.next}//修改pos位置前后节点变量的指向newNode.next = currentnewNode.prev = current.prevcurrent.prev.next = newNodecurrent.prev = newNode}}//4.length+1this.length += 1return true//返回true表示插入成功}//四.get方法DoubleLinklist.prototype.get = position => {//1.越界判断if (position < 0 || position >= this.length) {//获取元素时position不能等于lengthreturn null}//2.获取元素let current = nulllet index = 0//this.length / 2 > position:从头开始遍历if ((this.length / 2) > position) {current = this.headwhile(index++ < position){current = current.next}//this.length / 2 =< position:从尾开始遍历}else{current = this.tailindex = this.length - 1while(index-- > position){current = current.prev}}return current.data}//五.indexOf方法DoubleLinklist.prototype.indexOf = data => {//1.定义变量let current = this.headlet index = 0//2.遍历链表,查找与data相同的节点while(current){if (current.data == data) {return index}current = current.nextindex += 1}return -1} //六.update方法DoubleLinklist.prototype.update = (position, newData) => {//1.越界判断if (position < 0 || position >= this.length) {return false}//2.寻找正确的节点let current = this.headlet index = 0//this.length / 2 > position:从头开始遍历if (this.length / 2 > position) {while(index++ < position){current = current.next}//this.length / 2 =< position:从尾开始遍历}else{current = this.tailindex = this.length - 1while (index -- > position) {current = current.prev}}//3.修改找到节点的datacurrent.data = newDatareturn true//表示成功修改}//七.removeAt方法DoubleLinklist.prototype.removeAt = position => {//1.越界判断if (position < 0 || position >= this.length) {return null}//2.删除节点//当链表中length == 1//情况1:链表只有一个节点let current = this.head//定义在最上面方便以下各种情况返回current.dataif (this.length == 1) {this.head = nullthis.tail = null//当链表中length > 1} else{//情况2:删除第一个节点if (position == 0) {this.head.next.prev = nullthis.head = this.head.next//情况3:删除最后一个节点}else if(position == this.length - 1){current = this.tail//该情况下返回被删除的最后一个节点this.tail.prev.next = nullthis.tail = this.tail.prev}else{//情况4:删除链表中间的节点let index = 0while(index++ < position){current = current.next}current.next.prev = current.prevcurrent.prev.next = current.next}}//3.length -= 1this.length -= 1return current.data//返回被删除节点的数据}/*--------------------其他方法-------------------*///八.remove方法DoubleLinklist.prototype.remove = data => {//1.根据data获取下标值let index = this.indexOf(data)//2.根据index删除对应位置的节点return this.removeAt(index)}//九.isEmpty方法DoubleLinklist.prototype.isEmpty = () => {return this.length == 0}//十.size方法DoubleLinklist.prototype.size = () => {return this.length}//十一.getHead方法:获取链表的第一个元素DoubleLinklist.prototype.getHead = () => {return this.head.data}//十二.getTail方法:获取链表的最后一个元素DoubleLinklist.prototype.getTail = () => {return this.tail.data}}
单向链表有head和next两个属性,双向链表有head、tail、next、prev四个属性。处理好它们的指向,相当于将它们正确地连接在一起,这样就组成了一条链,这就是简单链表的实现。
在实际开发中链表使用得非常多,比如Java中的LinkList就是双向链表。
- 在链表中current = current.next 可以从左往右看,看成是current --> current.next,即current指向current的下一个节点。
- 删除节点的原理:只要没有引用指向该对象,无论该对象是否有引用指向其他对象,该对象都会被回收(删除)。
- 参数中凡是有position的都要进行越界判断。
以双向链表为例:链表的增删改查无非就是获取链表中相应的节点改变其中的prev和next两个变量的指向。
情况一:只需要head和tail两个变量就可以获取需要操作的变量(这里指的是能够轻松获取,当然你想通过head.next.next...或tail.prev.prev...来获取想要的节点也可以),在这种情况下链表的长度length:0 <= length <=2。
情况二:不能靠tail和head来获取到需要操作的变量时,可采用while循环遍历的方式,找到需要操作的节点:
在这种情况下,如果我们想要在链表的position = x的位置插入新节点,那么可以通过current获取position的后一个节点Node(x+1),通过current.prev获取position位置的前一个节点Node(x);之后修改Node(x+1)和Node(x)中的prev和next两个变量的指向即可在pos=x 的位置插入新节点。
应先修改newNode引用的指向,再修改其他引用
循环结束后index = position = x,变量current就指向了Node(x+1),变量index的值为Node(x+1)的索引值x。
当current.next = null时停止循环,此时current指向链表的最后一个节点。
上一篇: 国庆节简单祝福语
下一篇:十七、网上商城项目(1)