定义

链表是一种物理存储但愿上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。链表的插入/删除效率较高,而访问效率较低;数组的访问效率较高,而插入效率较低。

优缺点

优点

  • 插入快和删除快,由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表要快
  • 可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。

    缺点

  • 查找慢,查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

  • 空间开销较大,原因是增加了节点的指针域
  • 不允许随机读取

    种类

    单向链表

    image.png
    其中,data中保存着数据,next保存着下一个链表的引用。上图中,我们说 data2 跟在 data1 后面,而不是说 data2 是链表中的第二个元素。上图,值得注意的是,我们将链表的尾元素指向了 null 节点,表示链接结束的位置。
    由于链表的起始点的确定比较麻烦,因此很多链表的实现都会在链表的最前面添加一个特殊的节点,称为 头节点,表示链表的头部。进过改造,链表就成了如下的样子:
    image.png
    向链表中插入一个节点的效率很高,需要修改它前面的节点(前驱),使其指向新加入的节点,而将新节点指向原来前驱节点指向的节点即可。下面我将用图片演示如何在 data2 节点 后面插入 data4 节点。
    image.png
    同样,从链表中删除一个节点,也很简单。只需将待删节点的前驱节点指向待删节点的,同时将待删节点指向null,那么节点就删除成功了。如下图image.png

    实现

    设计链表包含两个类,一个是 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; } }

  1. <a name="RDhJG"></a>
  2. ### 双向链表
  3. 尽管从链表的头节点遍历链表很简单,但是反过来,从后向前遍历却不容易。我们可以通过给Node类增加一个previous属性,让其指向前驱节点的链接,这样就形成了双向链表<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/305942/1599454754425-07338970-b7d9-4a86-b9f9-ef014cfe54a0.png#align=left&display=inline&height=178&margin=%5Bobject%20Object%5D&name=image.png&originHeight=178&originWidth=952&size=37187&status=done&style=none&width=952)<br />此时,向链表插入一个节点就要更改节点的前驱和后继了,但是删除节点的效率提高了,不再需要寻找待删除节点的前驱节点了。
  4. <a name="5etBK"></a>
  5. #### 实现
  6. ```javascript
  7. //节点类
  8. function Node(element) {
  9. this.element = element; //当前节点的元素
  10. this.next = null; //下一个节点链接
  11. this.previous = null; //上一个节点链接
  12. }
  13. //链表类
  14. function LList () {
  15. this.head = new Node( 'head' ); //头节点
  16. this.find = find; //查找节点
  17. this.insert = insert; //插入节点
  18. this.remove = remove; //删除节点
  19. this.findPrev = findPrev; //查找前一个节点
  20. this.display = display; //显示链表
  21. }
  22. // 查找给定节点
  23. function find ( item ) {
  24. var currNode = this.head;
  25. while ( currNode.element != item ){
  26. currNode = currNode.next;
  27. }
  28. return currNode;
  29. }
  30. // 向尾部添加节点
  31. function append(newElement){
  32. var newNode = new Node( newElement );
  33. var currNode = this.head;
  34. while (currNode.next) {
  35. currNode = currNode.next
  36. }
  37. currNode.next = newNode
  38. }
  39. //插入节点
  40. // 双向链表的 insert 方法与单链表相似,但需要设置新节点的 previous 属性,使其指向该节点的前驱
  41. function insert ( newElement , item ) {
  42. var newNode = new Node( newElement );
  43. var currNode = this.find( item );
  44. newNode.next = currNode.next;
  45. newNode.previous = currNode;
  46. currNode.next = newNode;
  47. }
  48. // 删除节点
  49. // 双向链表的删除 remove 方法比单链表效率高,不需要查找前驱节点,只要找出待删除节点,
  50. // 然后将该节点的前驱 next 属性指向待删除节点的后继,设置该节点后继 previous 属性,
  51. // 指向待删除节点的前驱即可
  52. function remove ( item ) {
  53. var currNode = this.find ( item );
  54. if( !( currNode.next == null ) ){
  55. currNode.previous.next = currNode.next;
  56. currNode.next.previous = currNode.previous;
  57. currNode.next = null;
  58. currNode.previous = null;
  59. }
  60. }
  61. //显示链表元素
  62. function display () {
  63. var currNode = this.head;
  64. while ( !(currNode.next == null) ){
  65. console.debug( currNode.next.element );
  66. currNode = currNode.next;
  67. }
  68. }
  69. //反向显示链表元素
  70. function dispReverse () {
  71. var currNode = this.findLast();
  72. while ( !( currNode.previous == null )){
  73. console.log( currNode.element );
  74. currNode = currNode.previous;
  75. }
  76. }

循环链表

循环链表和单链表相似,节点类型都是一样,唯一的区别是,在创建循环链表的时候,让其头节点的 next 属性执行它本身,即

  1. head.next = head;

这种行为会导致链表中每个节点的 next 属性都指向链表的头节点,换句话说,也就是链表的尾节点指向了头节点,形成了一个循环链表,如下图所示:
image.png

实现

  1. function DoublyLinkedList() {
  2. var Node = function(element) {
  3. this.element = element;
  4. this.next = null;
  5. this.prev = null;
  6. };
  7. var length = 0,
  8. head = null,
  9. tail = null;
  10. this.append = function(element){
  11. var node = Node(element),
  12. current,
  13. previous;
  14. if(!head){
  15. head = node;
  16. tail = node;
  17. }else{
  18. current = head;
  19. while(current){
  20. previous = current;
  21. current = current.next;
  22. }
  23. node.next = current;
  24. current.prev = node;
  25. previous.next = node;
  26. node.prev = previous;
  27. }
  28. length++;
  29. return true;
  30. }
  31. this.insert = function(position,element){
  32. if(position > -1 && position < length){
  33. var node = new Node(element),
  34. current = head,
  35. previous,
  36. index = 0;
  37. if(position === 0){
  38. if(!head){
  39. head = node;
  40. tail = node;
  41. }else{
  42. node.next = current;
  43. current.prev = node;
  44. head = node;
  45. }
  46. }else if (position === length -1){
  47. current = tail;
  48. current.next = node;
  49. node.prev = current;
  50. }else {
  51. while(index++ < position){
  52. previous = current;
  53. current = current.next;
  54. }
  55. node.next = current;
  56. previous.next = node;
  57. current.prev = node;
  58. node.prev = previous;
  59. }
  60. length++;
  61. return true;
  62. }else{
  63. return false;
  64. }
  65. };
  66. this.removeAt = function(position){
  67. if(position > -1 && position < length){
  68. var current = head,
  69. index = 0,
  70. previous;
  71. if (position === 0) {
  72. head = current.next;
  73. if(length === 1){
  74. tail = null;
  75. }else{
  76. head.prev = null;
  77. }
  78. }else if(position === length - 1){
  79. current = tail;
  80. tail = current.prev;
  81. tail.next = null;
  82. } else{
  83. while(index++ < position){
  84. previous = current;
  85. current = current.next;
  86. }
  87. previous.next = current.next;
  88. current.next.prev = previous;
  89. };
  90. length-- ;
  91. return current.element;
  92. }else{
  93. return false;
  94. }
  95. };
  96. this.remove = function(element){
  97. var current = head,
  98. previous;
  99. if(current.element === element){
  100. head = current.next;
  101. }
  102. previous = current;
  103. current = current.next;
  104. while(current){
  105. if (current.element = element) {
  106. previous.next = current.next;
  107. current.next.prev = previous;
  108. }else{
  109. previous = current;
  110. current = current.next;
  111. }
  112. }
  113. return false;
  114. };
  115. this.remove = function(){
  116. if (length === 0) {
  117. return false;
  118. };
  119. var current = head,
  120. previous;
  121. if(length === 1){
  122. head = null;
  123. tail = null;
  124. length--;
  125. return current.element;
  126. }
  127. while(current){
  128. previous = current;
  129. current = current.next;
  130. }
  131. previous.next = null;
  132. length--;
  133. return current.element;
  134. };
  135. this.indexOf = function(element){
  136. var current = head,
  137. index = 0;
  138. while(current && index++ < length){
  139. if (current.element === element) {
  140. return index;
  141. };
  142. current = current.next;
  143. }
  144. return false;
  145. };
  146. this.isEmpty = function(){
  147. return length === 0;
  148. };
  149. this.size = function(){
  150. return length;
  151. };
  152. this.toString = function(){
  153. var current = head,
  154. string = '';
  155. while(current){
  156. string += current.element;
  157. current = current.next;
  158. }
  159. return string;
  160. };
  161. this.getHead = function(){
  162. return head;
  163. };
  164. this.getTail = function(){
  165. return tail;
  166. };
  167. }

双向循环链表

将双向链表的头尾指针相连,就构成了双向循环链表。这种链表从任意一个节点都可以同时向两个方向进行节点遍历,查询节点的速度也是最快的。

实现

  1. function DoublyCircularLinkedList(){
  2. var Node = function(element){
  3. this.element = element;
  4. this.next = null;
  5. this.prev = null;
  6. };
  7. var length = 0,
  8. head = null,
  9. tail = null;
  10. this.append = function(element){
  11. var node = new Node(element),
  12. current,
  13. previous;
  14. if (!head) {
  15. head = node;
  16. tail = node;
  17. head.prev = tail;
  18. tail.next = head;
  19. }else{
  20. current = head;
  21. while(current.next !== head){
  22. previous = current;
  23. current = current.next;
  24. }
  25. current.next = node;
  26. node.next = head;
  27. node.prev = current;
  28. };
  29. length++;
  30. return true;
  31. };
  32. this.insert = function(position, element){
  33. if(position >= 0 && position <= length){
  34. var node = new Node(element),
  35. index = 0,
  36. current = head,
  37. previous;
  38. if(position === 0){
  39. if(!head){
  40. node.next = node;
  41. node.tail = node;
  42. head = node;
  43. tail = node;
  44. }else{
  45. current.prev = node;
  46. node.next = current;
  47. head = node;
  48. node.prev = tail;
  49. }
  50. }else if(position === length){
  51. current = tail;
  52. current.next = node;
  53. node.prev = current;
  54. tail = node;
  55. node.next = head;
  56. }else{
  57. while(index++ < position){
  58. previous = current;
  59. current = current.next;
  60. }
  61. current.prev = node;
  62. node.next = current;
  63. previous.next = node;
  64. node.prev = previous;
  65. }
  66. length++;
  67. return true;
  68. }else{
  69. return false;
  70. }
  71. };
  72. this.removeAt = function(position){
  73. if(position > -1 && position < length){
  74. var current = head,
  75. index = 0,
  76. previous;
  77. if(position === 0){
  78. current.next.previous = tail;
  79. head = current.next;
  80. }else if(position === length - 1){
  81. current = tail;
  82. current.prev.next = head;
  83. head.prev = current.prev;
  84. tail = current.prev;
  85. }else{
  86. while(index++ < position){
  87. previous = current;
  88. current = current.next;
  89. }
  90. previous.next = current.next;
  91. current.next.prev = previous;
  92. }
  93. length--;
  94. return true;
  95. }else{
  96. return false;
  97. }
  98. };
  99. this.remove = function(element){
  100. var current = head,
  101. previous,
  102. indexCheck = 0;
  103. while(current && indexCheck < length){
  104. if(current.element === element){
  105. if(indexCheck === 0){
  106. current.next.prev = tail;
  107. head = current.next;
  108. }else{
  109. current.next.prev = previous;
  110. previous.next = current.next;
  111. }
  112. length--;
  113. return true;
  114. }
  115. previous = current;
  116. current = current.next;
  117. indexCheck++;
  118. }
  119. return false;
  120. };
  121. this.remove = function(){
  122. if(length === 0){
  123. return false;
  124. }
  125. var current = head,
  126. previous,
  127. indexCheck = 0;
  128. if(length === 1){
  129. head = null;
  130. tail = null;
  131. length--;
  132. return current.element;
  133. }
  134. while(indexCheck++ < length){
  135. previous = current;
  136. current = current.next;
  137. }
  138. previous.next = head;
  139. tail = previous.next;
  140. length--;
  141. return current.element;
  142. };
  143. this.indexOf = function(element){
  144. var current = head,
  145. index = 0;
  146. while(current && index++ < length){
  147. if(current.element === element){
  148. return index;
  149. }
  150. current = current.next;
  151. }
  152. return false;
  153. };
  154. this.toString = function(){
  155. var current = head,
  156. indexCheck = 0,
  157. string = '';
  158. while(current && indexCheck < length){
  159. string += current.element;
  160. indexCheck++;
  161. current = current.next;
  162. }
  163. return string;
  164. };
  165. this.isEmpty = function(){
  166. return length === 0;
  167. };
  168. this.getHead = function(){
  169. return head;
  170. };
  171. this.getTail = function(){
  172. return tail;
  173. };
  174. this.size = function(){
  175. return length;
  176. };
  177. }

https://www.cnblogs.com/zhuwq585/p/6075117.html

参考自 https://www.jianshu.com/p/f254ec665e57 https://www.cnblogs.com/xbblogs/p/9897118.html