JavaScript--数据结构算法之链表
数组的缺点:数组的长度固定,增删时比较困难要移动元素,而且数据填满再添加元素比较复杂。
js:数组有split(),可以任意的分割。不存在上述问题。
主要问题是:js数组都被实现成了对象,和其他语言的数组比较而言效率低。
一.单向链表:
有一组节点组成的集合,每一个节点都使用一个对象的引用指向它的后继。指向另一个节点的引用叫做链。
插入元素:只需修改前驱节点,使其指向新的节点,而新加入的节点指向原来前去指向的节点。
删除元素:将待删除元素的前驱节点指向待删除元素的后继节点。
插入,删除性能更高。
设计原理:
包含两个类:Node表示节点,LinkList类提供插入,删除,显示链表元素等方法,以及一些辅助方法。
~(function() {//创建节点,设置两个属性 function Node(ele) { this.ele = ele; this.next = null;//初始化时null,当有新元素插入时,next会指向新的元素。 } function LinkList() { this.head = new Node("head");//使用Node对象保存改链表的头节点 /!*this.find = find; this.insert = insert; this.remove = remove; this.display = display;*!/ } LinkList.prototype.find = function () {};//查找指定的元素,遍历链表 LinkList.prototype.insert = function () {}; LinkList.prototype.remove = function () {}; LinkList.prototype.findPrevious = function () {}; LinkList.prototype.display = function () {};//显示链表的元素 /!*1.插入元素:insert();向链表中插入一个节点。 在已知一个节点在后面插入元素时,首先要找到后面的节点,创建一个find()方法遍历链表查找节点。 *!/ LinkList.prototype.find =function (item) { var currNode = this.head; while(currNode.ele != item) { currNode = currNode.next; } return currNode; };//在链表上的移动,创建一个新节点,将链表的头节点赋给新创建的节点,然后在链表上循环当前的ele属性是否与item相当,否则当前结点移动到下一个节点,成功返回,否则null。 LinkList.prototype.insert = function (newEle,item) { var newNode = new Node(newEle); var current = this.find(item); newNode.next = current.next; current.next = newNode; };//找到后面的元素,插入链表,将新节点的next属性设置为后面节点的next对应值。然后设置后面节点的next属性指向新的节点。 LinkList.prototype.display = function () { var currNode = this.head;//变量记录头节点 while(!(currNode.next == null)) {//当前节点不为空遍历链表 console.log(currNode.next.ele); currNode = currNode.next;//指向下一个节点 } }; var cities = new LinkList(); cities.insert("BeiJing","head"); cities.insert("ShangHai","BeiJing"); cities.insert("ShenZhen","ShangHai"); cities.insert("GuangZhou","ShenZhen"); cities.insert("ChengDou","GuangZhou"); cities.display(); console.log("==================="); /!*2.删除元素:remove();向链表中删除一个节点。 * 删除节点时:需要找到待删除节点的前驱。只需修改他的后继next指向待删除的下一个节点。定义一个findPrevious()方法。遍历链表的节点,检查每一个节点的下一个节点是否存储这待删除的数据,如果有,返回该节点的前驱。 * *!/ LinkList.prototype.findPrevious = function (item) { var currNode = this.head; while(!(currNode.next == null) && (currNode.next.ele != item )) {//当前节点的后继不为空或者后继节点不为所要查找的元素时 currNode = currNode.next;//修改后继链 } return currNode;//找到时返回 }; LinkList.prototype.remove = function(item) { var prevNode = this.findPrevious(item);//找到删除元素的前一个元素 if(!(prevNode.next == null)) {//待删除元素不为空 prevNode.next = prevNode.next.next;//待删除元素的前驱的后继修改为待删除元素的后继的后继 } }; cities.remove("GuangZhou"); cities.display(); console.log("==================="); })();
二.双向链表
通过给Node节点添加一个前驱属性previous,指向前驱节点的链接。但插入时需要指明正确的前驱和后继。删除时就不再需要查找待删除节点的前驱节点。
~(function() { function Node(ele) { this.ele = ele; this.next = null; this.previous =null; } function LList() { this.head = new Node("head"); } LList.prototype.find = function () {}; LList.prototype.insert = function () {}; LList.prototype.display = function () {}; LList.prototype.remove = function () {}; LList.prototype.findLast = function () {}; LList.prototype.reverse = function () {}; //1.双向链表的插入 LList.prototype.find =function (item) { var currNode = this.head; while(currNode.ele != item) { currNode = currNode.next; } return currNode; }; LList.prototype.insert = function (newEle,item) { var newNode = new Node(newEle); var current = this.find(item); newNode.next = current.next;//当前节点的后继给新节点的后继 //newNode = current.next.previous; //?当前元素的后继的前驱元素没有指定 newNode.previous = current;//当前节点给新节点的前驱 current.next = newNode;//新节点给当前节点的后继 }; LList.prototype.display = function () { var currNode = this.head;//变量记录头节点 while(!(currNode.next == null)) {//当前节点不为空遍历链表 console.log(currNode.next.ele); currNode = currNode.next;//指向下一个节点 } }; var cities = new LList(); cities.insert("Beijing","head"); cities.insert("Shanghai","Beijing"); cities.insert("Guangzhou","Shanghai"); cities.insert("Chengdu","Guangzhou"); cities.display(); //2.双向链表的删除 //比单向链表的效率更高,不需要找前驱,首先先找到待删除的节点,修改前驱与后继就可以。 LList.prototype.remove = function (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;//待删除的元素的前驱和后继都设置null } }; console.log("----------------------"); cities.remove("Beijing"); cities.display(); console.log("----------------------"); cities.remove("Guangzhou"); cities.display(); //3.逆秩访问disReverse() //完成反序显示链表,需要查找到最后一个节点findLast(),这样免除了从前往后遍历带来的麻烦。 LList.prototype.findLast = function () { var currNode = this.head; while(!(currNode.next == null)) { currNode = currNode.next; } return currNode; }; LList.prototype.reverse = function () { var currNode = this.head; currNode = this.findLast(); while(!(currNode.previous == null)) { console.log(currNode.ele); currNode = currNode.previous;//改变前驱 } }; console.log("----------------------"); cities.reverse(); })();
三.循环链表
与单向链表是相似的,节点类型一样。区别是:让其头节点的next属性指向本身,head.next = head,这样会传导至链表的每一个节点,使得每一个节点的next属性指向链表的头节点,这样使得链表的尾节点指向了头节点,形成了一个环。性能高于双向链表;
~(function() { function Node(ele) { this.ele = ele; this.next = null; } function LList() { this.head = new Node("head"); this.head.next = this.head; } /*LList.prototype.find = function() {}; LList.prototype.insert = function() {}; LList.prototype.display = function() {}; LList.prototype.findPrevious = function() {}; LList.prototype.remove = function() {};*/ //与单向链表相似,只需修改display()方法即可;不然会成为死循环 LList.prototype.find =function (item) { var currNode = this.head; while(currNode.ele != item) { currNode = currNode.next; } return currNode; }; LList.prototype.insert = function (newEle,item) { var newNode = new Node(newEle); var current = this.find(item); newNode.next = current.next; current.next = newNode; }; LList.prototype.display = function() { var currNode = this.head; while(!(currNode.next == null) && !(currNode.next.ele == "head")) {//这点有区别当前节点的后继不为空或者当前节点的后继元素不为头节点 console.log(currNode.next.ele); currNode = currNode.next; } }; LList.prototype.findPrevious = function (item) { var currNode = this.head; while(!(currNode.next == null) && (currNode.next.ele != item )) {//当前节点的后继不为空或者后继节点不为所要查找的元素时 currNode = currNode.next;//修改后继链 } return currNode;//找到时返回 }; LList.prototype.remove = function(item) { var prevNode = this.findPrevious(item);//找到删除元素的前一个元素 if(!(prevNode.next == null)) {//待删除元素不为空 prevNode.next = prevNode.next.next;//待删除元素的前驱的后继修改为待删除元素的后继的后继 } }; var cities = new LList(); cities.insert("Beijing","head"); cities.insert("Shanghai","Beijing"); cities.insert("Guangzhou","Shanghai"); cities.insert("Chengdu","Guangzhou"); cities.display(); console.log("--------------------"); cities.remove("Guangzhou"); cities.display(); })();