定义
链表是一种物理存储但愿上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。链表的插入/删除效率较高,而访问效率较低;数组的访问效率较高,而插入效率较低。
优缺点
优点
- 插入快和删除快,由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表要快
可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
缺点
查找慢,查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
- 空间开销较大,原因是增加了节点的指针域
- 不允许随机读取
种类
单向链表

其中,data中保存着数据,next保存着下一个链表的引用。上图中,我们说 data2 跟在 data1 后面,而不是说 data2 是链表中的第二个元素。上图,值得注意的是,我们将链表的尾元素指向了 null 节点,表示链接结束的位置。
由于链表的起始点的确定比较麻烦,因此很多链表的实现都会在链表的最前面添加一个特殊的节点,称为 头节点,表示链表的头部。进过改造,链表就成了如下的样子:
向链表中插入一个节点的效率很高,需要修改它前面的节点(前驱),使其指向新加入的节点,而将新节点指向原来前驱节点指向的节点即可。下面我将用图片演示如何在 data2 节点 后面插入 data4 节点。
同样,从链表中删除一个节点,也很简单。只需将待删节点的前驱节点指向待删节点的,同时将待删节点指向null,那么节点就删除成功了。如下图
实现
设计链表包含两个类,一个是 Node 类用来表示节点,另一个事 LinkedList 类提供插入节点、删除节点等一些操作。 ```javascript // 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.remove = remove; //删除节点 this.findPrev = findPrev; //查找前一个节点 this.display = display; //显示链表 }
// find:查找给定节点 // 创建一个新节点,将链表的头节点赋给这个新创建的节点,然后在链表上循环, // 如果当前节点的 element 属性和我们要找的信息不符,就将当前节点移动到下一个节点, // 如果查找成功,该方法返回包含该数据的节点;否则,就会返回null。 function find ( item ) { var currNode = this.head; while ( currNode.element != item ){ currNode = currNode.next; } return currNode; }
// insert插入 // 在一个已知节点后插入新节点,我们首先得用find方法找到该节点。 function insert ( newElement , item ) { var newNode = new Node( newElement ); var currNode = this.find( item ); newNode.next = currNode.next; currNode.next = newNode; }
// display显示链表 将头节点赋给一个新的变量,然后循环链表,直到当前节点的 next 属性为 null 时停止循环, // 我们循环过程中将每个节点的数据打印出来就好了。 function display () { var currNode = this.head; while ( !(currNode.next == null) ){ console.log( currNode.next.element ); currNode = currNode.next; } }
// remove:从链表中删除一个节点 // 链表中删除节点时,我们先要找个待删除节点的前一个节点,找到后,我们修改它的 next 属性,使其不在指向待删除的节点,而是待删除节点的下一个节点。 // 那么,我们就得需要定义一个 findPrevious 方法遍历链表,检查每一个节点的下一个节点是否存储待删除的数据。 // 如果找到,返回该节点,这样就可以修改它的 next 属性了。 findPrevious 的实现。 function findPrev( item ) { var currNode = this.head; while ( !( currNode.next == null) && ( currNode.next.element != item )){ currNode = currNode.next; } return currNode; }
function remove ( item ) { var prevNode = this.findPrev( item ); if( !( prevNode.next == null ) ){ prevNode.next = prevNode.next.next; } }
<a name="RDhJG"></a>### 双向链表尽管从链表的头节点遍历链表很简单,但是反过来,从后向前遍历却不容易。我们可以通过给Node类增加一个previous属性,让其指向前驱节点的链接,这样就形成了双向链表<br /><br />此时,向链表插入一个节点就要更改节点的前驱和后继了,但是删除节点的效率提高了,不再需要寻找待删除节点的前驱节点了。<a name="5etBK"></a>#### 实现```javascript//节点类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.remove = remove; //删除节点this.findPrev = findPrev; //查找前一个节点this.display = display; //显示链表}// 查找给定节点function find ( item ) {var currNode = this.head;while ( currNode.element != item ){currNode = currNode.next;}return currNode;}// 向尾部添加节点function append(newElement){var newNode = new Node( newElement );var currNode = this.head;while (currNode.next) {currNode = currNode.next}currNode.next = newNode}//插入节点// 双向链表的 insert 方法与单链表相似,但需要设置新节点的 previous 属性,使其指向该节点的前驱function insert ( newElement , item ) {var newNode = new Node( newElement );var currNode = this.find( item );newNode.next = currNode.next;newNode.previous = currNode;currNode.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;}}//显示链表元素function display () {var currNode = this.head;while ( !(currNode.next == null) ){console.debug( currNode.next.element );currNode = currNode.next;}}//反向显示链表元素function dispReverse () {var currNode = this.findLast();while ( !( currNode.previous == null )){console.log( currNode.element );currNode = currNode.previous;}}
循环链表
循环链表和单链表相似,节点类型都是一样,唯一的区别是,在创建循环链表的时候,让其头节点的 next 属性执行它本身,即
head.next = head;
这种行为会导致链表中每个节点的 next 属性都指向链表的头节点,换句话说,也就是链表的尾节点指向了头节点,形成了一个循环链表,如下图所示:
实现
function DoublyLinkedList() {var Node = function(element) {this.element = element;this.next = null;this.prev = null;};var length = 0,head = null,tail = null;this.append = function(element){var node = Node(element),current,previous;if(!head){head = node;tail = node;}else{current = head;while(current){previous = current;current = current.next;}node.next = current;current.prev = node;previous.next = node;node.prev = previous;}length++;return true;}this.insert = function(position,element){if(position > -1 && position < length){var node = new Node(element),current = head,previous,index = 0;if(position === 0){if(!head){head = node;tail = node;}else{node.next = current;current.prev = node;head = node;}}else if (position === length -1){current = tail;current.next = node;node.prev = current;}else {while(index++ < position){previous = current;current = current.next;}node.next = current;previous.next = node;current.prev = node;node.prev = previous;}length++;return true;}else{return false;}};this.removeAt = function(position){if(position > -1 && position < length){var current = head,index = 0,previous;if (position === 0) {head = current.next;if(length === 1){tail = null;}else{head.prev = null;}}else if(position === length - 1){current = tail;tail = current.prev;tail.next = null;} else{while(index++ < position){previous = current;current = current.next;}previous.next = current.next;current.next.prev = previous;};length-- ;return current.element;}else{return false;}};this.remove = function(element){var current = head,previous;if(current.element === element){head = current.next;}previous = current;current = current.next;while(current){if (current.element = element) {previous.next = current.next;current.next.prev = previous;}else{previous = current;current = current.next;}}return false;};this.remove = function(){if (length === 0) {return false;};var current = head,previous;if(length === 1){head = null;tail = null;length--;return current.element;}while(current){previous = current;current = current.next;}previous.next = null;length--;return current.element;};this.indexOf = function(element){var current = head,index = 0;while(current && index++ < length){if (current.element === element) {return index;};current = current.next;}return false;};this.isEmpty = function(){return length === 0;};this.size = function(){return length;};this.toString = function(){var current = head,string = '';while(current){string += current.element;current = current.next;}return string;};this.getHead = function(){return head;};this.getTail = function(){return tail;};}
双向循环链表
将双向链表的头尾指针相连,就构成了双向循环链表。这种链表从任意一个节点都可以同时向两个方向进行节点遍历,查询节点的速度也是最快的。
实现
function DoublyCircularLinkedList(){var Node = function(element){this.element = element;this.next = null;this.prev = null;};var length = 0,head = null,tail = null;this.append = function(element){var node = new Node(element),current,previous;if (!head) {head = node;tail = node;head.prev = tail;tail.next = head;}else{current = head;while(current.next !== head){previous = current;current = current.next;}current.next = node;node.next = head;node.prev = current;};length++;return true;};this.insert = function(position, element){if(position >= 0 && position <= length){var node = new Node(element),index = 0,current = head,previous;if(position === 0){if(!head){node.next = node;node.tail = node;head = node;tail = node;}else{current.prev = node;node.next = current;head = node;node.prev = tail;}}else if(position === length){current = tail;current.next = node;node.prev = current;tail = node;node.next = head;}else{while(index++ < position){previous = current;current = current.next;}current.prev = node;node.next = current;previous.next = node;node.prev = previous;}length++;return true;}else{return false;}};this.removeAt = function(position){if(position > -1 && position < length){var current = head,index = 0,previous;if(position === 0){current.next.previous = tail;head = current.next;}else if(position === length - 1){current = tail;current.prev.next = head;head.prev = current.prev;tail = current.prev;}else{while(index++ < position){previous = current;current = current.next;}previous.next = current.next;current.next.prev = previous;}length--;return true;}else{return false;}};this.remove = function(element){var current = head,previous,indexCheck = 0;while(current && indexCheck < length){if(current.element === element){if(indexCheck === 0){current.next.prev = tail;head = current.next;}else{current.next.prev = previous;previous.next = current.next;}length--;return true;}previous = current;current = current.next;indexCheck++;}return false;};this.remove = function(){if(length === 0){return false;}var current = head,previous,indexCheck = 0;if(length === 1){head = null;tail = null;length--;return current.element;}while(indexCheck++ < length){previous = current;current = current.next;}previous.next = head;tail = previous.next;length--;return current.element;};this.indexOf = function(element){var current = head,index = 0;while(current && index++ < length){if(current.element === element){return index;}current = current.next;}return false;};this.toString = function(){var current = head,indexCheck = 0,string = '';while(current && indexCheck < length){string += current.element;indexCheck++;current = current.next;}return string;};this.isEmpty = function(){return length === 0;};this.getHead = function(){return head;};this.getTail = function(){return tail;};this.size = function(){return length;};}
https://www.cnblogs.com/zhuwq585/p/6075117.html
参考自 https://www.jianshu.com/p/f254ec665e57 https://www.cnblogs.com/xbblogs/p/9897118.html
