数组是很常见的数据类型。得益于相当友好于开发者们对数据进行操作, 数组成了套路之王了!
当然,友好于开发者,肯定会失去很多的。它就不是很友好于性能优化了,当然,当今的硬件技术发展下,一般的小项目根本就跟前端层面的性能优化无瓜的。

数组的优点:

根据下标,赋值,取值,很方便,存储型的操作!查找,赋值,取值,性能强!

缺点:

扩容,很消耗性能!
数组在低层创建时是要声明长度的!JS视角只是表层,面向内存,数组声明时,内存就把相邻连续的一块区域划分给数组了,区域大小跟声明的数组长度挂钩。
问题来了,数组可以随意地push,shift,,,就需要扩容了,扩容等同于新创建一个数组,长度是扩容时设置的长度,然后把原本的数据依次拷贝过来,然后删除原数组的内存区块。太消耗性能了!
另外,
中间插入,中间删除,首部插入,首部删除,会导致操作位置后面的所有的数据在原内存地址上依次顺序改动。

我对数组的理解:

数组就是人心中的理想的数据类型,人的暴力美学,数组操作多简单!就算是扩容,就算是中间插入删除,又咋了?堂堂正正的阳谋!不就是性能差么?又不是不能用.jif!

针对优缺点,既可以思考:数组的使用场景!比如前端,,,数据量不大时,从来就没怂过!JSON.parse(JSON.stringify(xxx))都无所畏惧,,,

当然,理性地思考,数组适合于多查找,多编辑,少中间首部插入或删除的操作。偏向于读和写的操作场景了!

对数组不适用于中间,首部的插入删除,还有扩容问题,另一种链式数据结构算是跟它完全反过来了!

链表

完全是针对数组的么!
不需要扩容!随意任何位置的元素进行插入,删除!牛皮!偏向于频繁地增删的操作场景!
这么友好于性能,,,终究是无法打破必须不完美的铁律了。它的读写性能不高,查找不如数组快。

优点

  • 因为不必像数组一样是内存里的一块连续的内存区域,相对于内存很友好,充分榨取内存的剩余价值。源于链表的数据结构,存储的有下一个节点的地址。
  • 插入和删除操作性能快。
  • 理论上只要内存够,想存多少数据就存多少,扩啥容?

缺点

读取一个节点,就要遍历一遍链表,因为它的内存地址不连续。所以,性能不如数组直接靠下标来得快!

数据结构

截屏2020-03-29 上午11.48.27.png

很典型的因果论哲学模型!比如一个人的成长历史,从小学,到初中,高中,大学。讲究因果联系!

单链表的封装

  1. //链表的封装
  2. class LinkedList {
  3. constructor(list) {
  4. this.header = list || null;
  5. this.length = list ? 1 : 0;
  6. }
  7. append(data) {
  8. const list = new List(data);
  9. if (this.length === 0) {
  10. return this.header = list;
  11. }
  12. let current = this.header;
  13. while (current.next) {
  14. current = current.next;
  15. }
  16. current.next = list;
  17. this.length += 1;
  18. }
  19. insert(data, index) {
  20. if (index < 0 || index > this.length) {
  21. throw new Error("index参数不合理");
  22. }
  23. const list = new List(data);
  24. let oldList = this.header;
  25. if (index === 0) {
  26. oldList && (list.next = oldList);
  27. this.header = list;
  28. } else {
  29. let i = 0;
  30. while (i < index - 1) { //index-1是找的index-1的那个点,index点可以通过它找到
  31. oldList = oldList.next;
  32. i++;
  33. }
  34. let next = oldList.next;
  35. oldList.next = list;
  36. list.next = next;
  37. }
  38. this.length++;
  39. }
  40. get(position) {
  41. if (position > this.length - 1 || position < 0) {
  42. throw new Error("位置参数超出范围了!");
  43. }
  44. let current = this.header;
  45. if (position === 0) return current;
  46. let i = 0;
  47. while (i < position) {
  48. current = current.next;
  49. i++;
  50. }
  51. return current;
  52. }
  53. update(position, data) {
  54. this.get(position).data = data;
  55. return true;
  56. }
  57. indexOf(data,key) {
  58. let current = this.header;
  59. if (!current) return -1;
  60. let index;
  61. let i = 0;
  62. while (!index && current) {
  63. //这里是扩展了一下如果data是复杂类型的时候,凭关键字段匹配的情况:
  64. //在用哈希表的时候,我不得不扩展
  65. if ( (key ? current.data[key]:current.data) === data) {
  66. index = i;
  67. } else {
  68. current = current.next;
  69. i++;
  70. }
  71. }
  72. return index >= 0 ? index : -1;
  73. }
  74. removeAt(position) {
  75. if (position < 0 || position > this.length - 1 || this.length === 0) {
  76. throw new Error("参数超出范围!");
  77. }
  78. let current = this.header;
  79. if (position === 0) {
  80. this.length -= 1;
  81. this.header = current.next;
  82. return current;
  83. } else {
  84. let i = 0;
  85. while (i < position - 1) {
  86. current = current.next;
  87. i++;
  88. }
  89. const target = current.next;
  90. if (target) {
  91. current.next = target.next;
  92. this.length -= 1;
  93. return target;
  94. }
  95. }
  96. }
  97. remove(data) {
  98. const index = this.indexOf(data);
  99. return this.removeAt(index);
  100. }
  101. size() {
  102. return this.length;
  103. }
  104. isEmpty() {
  105. return this.length === 0;
  106. }
  107. toString() {
  108. let current = this.header;
  109. let res = "";
  110. while (current) {
  111. res += (res ? " -> " : "") + current.data;
  112. current = current.next;
  113. }
  114. return res;
  115. }
  116. }
  117. class List {
  118. constructor(data, next) {
  119. this.data = data;
  120. this.next = next ? next : null;
  121. }
  122. }
  123. const list = new List(1);
  124. const linkedList = new LinkedList(list);
  125. console.log(linkedList);
  126. linkedList.append(30);
  127. linkedList.insert("2", 1);
  128. linkedList.insert("0", 0);
  129. linkedList.insert("6", 3);
  130. linkedList.insert("20", 4);
  131. linkedList.insert("89", 6);
  132. console.log(linkedList, linkedList.toString(), 888, linkedList.get(3),);
  133. console.log(linkedList.indexOf(3), linkedList.update(3, 10), linkedList.get(3));
  134. console.log(linkedList.removeAt(3), linkedList.toString());
  135. console.log(linkedList.remove("20"), linkedList.toString(),linkedList.size());

单链表的数据模型很清晰,像舞龙队。舞龙一堆人,其实,隔了很远,如何心意相通,互相完美配合,就是后面的人只关心前面的那一个人。一条链下来。还有一个游戏,就是传话,几个人一个次序方向传话。
由此,看出局限性了,节点只知道下一个节点是谁,只能一个方向次序操作,,,如果遇到一个节点,想要反方向操作上一个节点,怎么办?
比如文本编辑器的光标一行一行地向上向下移动。
需求是,一个节点既可以拿到下一个节点,单链表实现了。又可以拿到它的上一个节点,双链表可以的!

双向链表

其实就是在单链表的优点缺点的基础上,增加了点增删复杂的操作,增加了上一个节点的操作。增加了内存的消耗,其实也就是添加了一个prev的字段,跟功能比起来,可以看不见。
性能一样的。魔改版的单链表。
当然,双链表在性能跟单链表相同的情况下,功能更加完善了。它成了主流了!
截屏2020-03-30 上午10.47.00.png
截屏2020-03-30 上午10.47.25.png
一看,就可以知道,增删的方法时,要修改的属性会相当的多的。
截屏2020-03-30 上午11.00.31.png

双链表的实现封装:

  1. class DoublyLinkedList extends LinkedList {
  2. constructor(list) {
  3. super(list);
  4. this.tail = list || null;
  5. }
  6. append(data) {
  7. const list = new DoublyList(data);
  8. if (this.length === 0) {
  9. list.prev = null;
  10. this.header = list;
  11. this.tail = list;
  12. } else {
  13. const last = this.tail;
  14. last.next = list;
  15. list.prev = last;
  16. this.tail = list;
  17. }
  18. this.length += 1;
  19. }
  20. insert(data, index) {
  21. if (index < 0 || index > this.length) {
  22. throw new Error("插入的下标超出范围!");
  23. }
  24. const list = new DoublyList(data);
  25. if (index === 0) {
  26. const current = this.header;
  27. if (current) {
  28. this.header = list;
  29. list.next = current;
  30. current.prev = list;
  31. } else {
  32. this.tail = list;
  33. this.header = list;
  34. }
  35. } else if (index === this.length) {
  36. const last = this.tail;
  37. last.next = list;
  38. list.prev = last;
  39. this.tail = list;
  40. } else {
  41. const test = index * 2 > this.length - 1;
  42. let i = test ? this.length - 1 : 0;
  43. let current = test ? this.tail : this.header;
  44. let key = test ? "prev" : "next";
  45. if (test) {
  46. while (i > index) {
  47. current = current[key];
  48. i--;
  49. }
  50. } else {
  51. while (i < index) {
  52. current = current[key];
  53. i++;
  54. }
  55. }
  56. const last = current.prev;
  57. list.prev = last;
  58. last.next = list;
  59. list.next = current;
  60. current.prev = list;
  61. }
  62. this.length++;
  63. }
  64. get(index) {
  65. if (index < 0 || this.length === 0 || index > this.length - 1) {
  66. throw new Error("超出链表的长度范围");
  67. }
  68. const test = index * 2 > this.length - 1;
  69. let current = test ? this.tail : this.header;
  70. let i = test ? this.length - 1 : 0;
  71. const key = test ? "prev" : "next";
  72. if (test) {
  73. while (i > index) {
  74. current = current[key];
  75. i--;
  76. }
  77. } else {
  78. while (i < index) {
  79. current = current[key];
  80. i++;
  81. }
  82. }
  83. return current;
  84. }
  85. removeAt(index) {
  86. if (index < 0 || this.length === 0 || index > this.length - 1) {
  87. throw new Error("操作的下标超出范围!");
  88. }
  89. const current = this.get(index);
  90. const last = current.prev;
  91. const next = current.next;
  92. if (last) {
  93. if (next) {
  94. last.next = next;
  95. next.prev = last;
  96. } else {
  97. this.tail = last;
  98. last.next = null;
  99. }
  100. } else {
  101. if (next) {
  102. this.header = next;
  103. next.prev = null;
  104. } else {
  105. this.header = null;
  106. this.tail = null;
  107. }
  108. }
  109. this.length -= 1;
  110. return current;
  111. }
  112. remove(data) {
  113. // const index = this.indexOf(data);
  114. // return this.removeAt(index);
  115. //这里可以简单套用上面的逻辑,但是我又重温了一下,各种逻辑的套路:
  116. let current = this.header;
  117. let ind, i = 0;
  118. while (ind === undefined && current) {
  119. if (current.data === data) {
  120. ind = i;
  121. }
  122. current = current.next;
  123. i++;
  124. }
  125. if (ind === undefined) {
  126. throw new Error("暂无此节点,无法删除!");
  127. }
  128. const test = ind * 2 > this.length - 1;
  129. let cur = test ? this.tail : this.header;
  130. const key = test ? "prev" : "next";
  131. let Ind = test ? this.length - 1 : 0;
  132. if (test) {
  133. while (Ind > ind) {
  134. cur = cur[key];
  135. Ind -= 1;
  136. }
  137. } else {
  138. while (Ind < ind) {
  139. cur = cur[key];
  140. Ind += 1;
  141. }
  142. }
  143. const last = cur.prev;
  144. const next = cur.next;
  145. if (last) {
  146. if (next) {
  147. last.next = next;
  148. next.prev = last;
  149. } else {
  150. this.tail = last;
  151. last.next = null;
  152. }
  153. } else {
  154. if (next) {
  155. this.header = next;
  156. next.prev = null;
  157. } else {
  158. this.header = null;
  159. this.tail = null;
  160. }
  161. }
  162. this.length--;
  163. return cur;
  164. }
  165. update(index, data) {
  166. const list = this.get(index);
  167. list.data = data;
  168. return list;
  169. }
  170. backwardString() {
  171. return this.toString();
  172. }
  173. forwardString() {
  174. let current = this.tail;
  175. let res = "";
  176. while (current) {
  177. res += (res ? " <- " : "") + current.data;
  178. current = current.prev;
  179. }
  180. return res;
  181. }
  182. }
  183. const doublyList = new DoublyList(1);
  184. const doublyLinkedList = new DoublyLinkedList(doublyList);
  185. doublyLinkedList.append(2);
  186. doublyLinkedList.append(3);
  187. doublyLinkedList.append(4);
  188. doublyLinkedList.insert("哈哈", 2);
  189. doublyLinkedList.update(3, "test");
  190. console.log(doublyLinkedList,
  191. doublyLinkedList.backwardString(),
  192. doublyLinkedList.forwardString(),
  193. doublyLinkedList.get(3),
  194. doublyLinkedList.indexOf("哈哈")
  195. );
  196. console.log(
  197. doublyLinkedList.removeAt(3),
  198. doublyLinkedList.remove(4),
  199. doublyLinkedList.toString(),
  200. );