学习JavaScript数据结构与算法(第3版)@www.java1234.com.pdf

我们知道,可以在数组的任意位置上删除或添加元素。然而,有时候还需要一种能在添加或删除元素时进行
更多控制的数据结构。有两种类似于数组的数据结构在添加和删除元素时更为可控,它们就是栈和队列。

栈是一种遵从后进先出( LIFO)原则的有序集合。新添加或待删除的元素都保存在栈的同
一端,称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。

在现实生活中也能发现很多栈的例子。例如,一摞书或者餐厅里叠放的盘子。
栈也被用在编程语言的编译器和内存中保存变量、方法调用等,也被用于浏览器历史记录
(浏览器的返回按钮)。

栈的简易实现

  1. // 最简单大 栈数据结构 实现
  2. class Stack {
  3. constructor() {
  4. this.count = 0;
  5. this.items = {};
  6. }
  7. push(element) {
  8. this.items[this.count] = element;
  9. this.count++;
  10. }
  11. pop() {
  12. if (this.isEmpty()) {
  13. return undefined;
  14. }
  15. this.count--;
  16. const result = this.items[this.count];
  17. delete this.items[this.count];
  18. return result;
  19. }
  20. peek() {
  21. if (this.isEmpty()) {
  22. return undefined;
  23. }
  24. return this.items[this.count - 1];
  25. }
  26. isEmpty() {
  27. return this.count === 0;
  28. }
  29. size() {
  30. return this.count;
  31. }
  32. clear() {
  33. /* while (!this.isEmpty()) {
  34. this.pop();
  35. } */
  36. this.items = {};
  37. this.count = 0;
  38. }
  39. toString() {
  40. if (this.isEmpty()) {
  41. return '';
  42. }
  43. let objString = `${this.items[0]}`;
  44. for (let i = 1; i < this.count; i++) {
  45. objString = `${objString},${this.items[i]}`;
  46. }
  47. return objString;
  48. }
  49. }

如何保护数据结构内部元素?

  • 下划线命令约定
  • 用ES2015的限定作用域Symbol
  • 使用ES2015的WeakMap实现类
  • ECMAScript 类私有属性提案 “#

    用途

  1. 从十进制到二进制

image.png

  1. 计算器实现 (中缀、前缀、后缀表达式)

    队列

    队列和栈非常类似,但是使用了与后进先出不同的原则。
    队列是遵循先进先出(FIFO,也称为先来先服务)原则的一~组有序的项。队列在尾部添加新
    元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。

    队列的简易实现

    ```javascript class Deque { constructor() { this.count = 0; this.lowestCount = 0; this.items = {}; }

    addFront(element) { if (this.isEmpty()) { this.addBack(element); } else if (this.lowestCount > 0) { this.lowestCount—; this.items[this.lowestCount] = element; } else { for (let i = this.count; i > 0; i—) {

    1. this.items[i] = this.items[i - 1];

    } this.count++; this.items[0] = element; } }

    addBack(element) { this.items[this.count] = element; this.count++; }

    removeFront() { if (this.isEmpty()) { return undefined; } const result = this.items[this.lowestCount]; delete this.items[this.lowestCount]; this.lowestCount++; return result; }

    removeBack() { if (this.isEmpty()) { return undefined; } this.count—; const result = this.items[this.count]; delete this.items[this.count]; return result; }

    peekFront() { if (this.isEmpty()) { return undefined; } return this.items[this.lowestCount]; }

    peekBack() { if (this.isEmpty()) { return undefined; } return this.items[this.count - 1]; }

    isEmpty() { return this.size() === 0; }

    clear() { this.items = {}; this.count = 0; this.lowestCount = 0; }

    size() { return this.count - this.lowestCount; }

    toString() { if (this.isEmpty()) { return ‘’; } let objString = ${this.items[this.lowestCount]}; for (let i = this.lowestCount + 1; i < this.count; i++) { objString = ${objString},${this.items[i]}; } return objString; } }

  1. <a name="QJr8K"></a>
  2. ## 双端队列
  3. 双端队列(deque,或称double- ended queue)是一种种允许我们同时从前端和后端添加和移除<br />元素的特殊队列。<br />双端队列在现实生活中的例子有电影院、餐厅中排队的队伍等。举个例子,一个刚买了票的<br />人如果,只是还需要再问-些简单的信息,就可以直接回到队伍的头部。另外,在队伍末尾的人如<br />果赶时间,他可以直接离开队伍。<br />在计算机科学中,双端队列的一个常见应用是存储- - 系列的撤销操作。每当用户在软件中进<br />行了一个操作,该操作会被存在一个双端队列中( 就像在一个栈里)。当用户点击撤销按钮时,<br />该操作会被从双端队列中弹出,表示它被从后面移除了。在进行了预先定义的---定数量的操作后,<br />最先进行的操作会被从双端队列的前端移除。由于双端队列同时遵守了先进先出和后进先出原<br />则,可以说它是把队列和栈相结合的一种数据结构。
  4. <a name="uRcgY"></a>
  5. ## 队列和双端队列的应用
  6. - 循环队列一击 鼓传花游戏
  7. - 回文检查器 (回文例子:madam)
  8. - javascript任务队列
  9. <a name="qv46m"></a>
  10. # 链表
  11. 要存储多个元素,数组(或列表)可能是最常用的数据结构。正如本书之前提到的,每种语言都实现了数组。这种数据结构非常方便,提供了一个便利的[]语法来访问其元素。然而,这种数据结构有一个缺点: (在大多数语言中)数组的大小是固定的,从数组的起点或中间插入或移除项的成本很高,因为需要移动元素。( 尽管我们已经学过,JavaScript 有来自Array 类的方法可以帮我们做这些事,但背后的情况同样如此。)<br />链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个<br />元素由一个存储元素本身的节点和一一个指向下一一个元素的引用(也称指针或链接)组成。下图展<br />示了一个链表的结构。<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/190178/1590933054487-6902f10e-2b03-4132-9f48-d97bade50bbb.png#align=left&display=inline&height=105&margin=%5Bobject%20Object%5D&name=image.png&originHeight=210&originWidth=946&size=43327&status=done&style=none&width=473)
  12. 现实中的例子: 康加舞队、寻宝游戏、火车
  13. <a name="OxPSs"></a>
  14. ## 链表的简易实现
  15. ```javascript
  16. class LinkedList {
  17. constructor(equalsFn = (a, b) => a === b) {
  18. this.equalsFn = equalsFn;
  19. this.count = 0;
  20. this.head = undefined;
  21. }
  22. push(element) {
  23. const node = new Node(element);
  24. let current;
  25. if (this.head == null) {
  26. // catches null && undefined
  27. this.head = node;
  28. } else {
  29. current = this.head;
  30. while (current.next != null) {
  31. current = current.next;
  32. }
  33. current.next = node;
  34. }
  35. this.count++;
  36. }
  37. getElementAt(index) {
  38. if (index >= 0 && index <= this.count) {
  39. let node = this.head;
  40. for (let i = 0; i < index && node != null; i++) {
  41. node = node.next;
  42. }
  43. return node;
  44. }
  45. return undefined;
  46. }
  47. insert(element, index) {
  48. if (index >= 0 && index <= this.count) {
  49. const node = new Node(element);
  50. if (index === 0) {
  51. const current = this.head;
  52. node.next = current;
  53. this.head = node;
  54. } else {
  55. const previous = this.getElementAt(index - 1);
  56. node.next = previous.next;
  57. previous.next = node;
  58. }
  59. this.count++;
  60. return true;
  61. }
  62. return false;
  63. }
  64. removeAt(index) {
  65. if (index >= 0 && index < this.count) {
  66. let current = this.head;
  67. if (index === 0) {
  68. this.head = current.next;
  69. } else {
  70. const previous = this.getElementAt(index - 1);
  71. current = previous.next;
  72. previous.next = current.next;
  73. }
  74. this.count--;
  75. return current.element;
  76. }
  77. return undefined;
  78. }
  79. remove(element) {
  80. const index = this.indexOf(element);
  81. return this.removeAt(index);
  82. }
  83. indexOf(element) {
  84. let current = this.head;
  85. for (let i = 0; i < this.size() && current != null; i++) {
  86. if (this.equalsFn(element, current.element)) {
  87. return i;
  88. }
  89. current = current.next;
  90. }
  91. return -1;
  92. }
  93. isEmpty() {
  94. return this.size() === 0;
  95. }
  96. size() {
  97. return this.count;
  98. }
  99. getHead() {
  100. return this.head;
  101. }
  102. clear() {
  103. this.head = undefined;
  104. this.count = 0;
  105. }
  106. toString() {
  107. if (this.head == null) {
  108. return '';
  109. }
  110. let objString = `${this.head.element}`;
  111. let current = this.head.next;
  112. for (let i = 1; i < this.size() && current != null; i++) {
  113. objString = `${objString},${current.element}`;
  114. current = current.next;
  115. }
  116. return objString;
  117. }
  118. }
  119. class Node {
  120. constructor(element, next) {
  121. this.element = element;
  122. this.next = next;
  123. }
  124. }

链表的变体

  • 双向链表
  • 循环链表
  • 有序链表
  • StackLinkedList类

    集合

    集合是由一组无序且唯一(即不能重复)的项组成的。该数据结构使用了与有限集合相同的数学概念,但应用在计算机科学的数据结构中。
    集合的运算:并集、交集、差集、子集

    集合的简易实现

    1. class Set {
    2. constructor() {
    3. this.items = {};
    4. }
    5. add(element) {
    6. if (!this.has(element)) {
    7. this.items[element] = element;
    8. return true;
    9. }
    10. return false;
    11. }
    12. delete(element) {
    13. if (this.has(element)) {
    14. delete this.items[element];
    15. return true;
    16. }
    17. return false;
    18. }
    19. has(element) {
    20. return Object.prototype.hasOwnProperty.call(this.items, element);
    21. }
    22. values() {
    23. return Object.values(this.items);
    24. }
    25. union(otherSet) {
    26. const unionSet = new Set();
    27. this.values().forEach(value => unionSet.add(value));
    28. otherSet.values().forEach(value => unionSet.add(value));
    29. return unionSet;
    30. }
    31. intersection(otherSet) {
    32. const intersectionSet = new Set();
    33. const values = this.values();
    34. const otherValues = otherSet.values();
    35. let biggerSet = values;
    36. let smallerSet = otherValues;
    37. if (otherValues.length - values.length > 0) {
    38. biggerSet = otherValues;
    39. smallerSet = values;
    40. }
    41. smallerSet.forEach(value => {
    42. if (biggerSet.includes(value)) {
    43. intersectionSet.add(value);
    44. }
    45. });
    46. return intersectionSet;
    47. }
    48. difference(otherSet) {
    49. const differenceSet = new Set();
    50. this.values().forEach(value => {
    51. if (!otherSet.has(value)) {
    52. differenceSet.add(value);
    53. }
    54. });
    55. return differenceSet;
    56. }
    57. isSubsetOf(otherSet) {
    58. if (this.size() > otherSet.size()) {
    59. return false;
    60. }
    61. let isSubset = true;
    62. this.values().every(value => {
    63. if (!otherSet.has(value)) {
    64. isSubset = false;
    65. return false;
    66. }
    67. return true;
    68. });
    69. return isSubset;
    70. }
    71. isEmpty() {
    72. return this.size() === 0;
    73. }
    74. size() {
    75. return Object.keys(this.items).length;
    76. }
    77. clear() {
    78. this.items = {};
    79. }
    80. toString() {
    81. if (this.isEmpty()) {
    82. return '';
    83. }
    84. const values = this.values();
    85. let objString = `${values[0]}`;
    86. for (let i = 1; i < values.length; i++) {
    87. objString = `${objString},${values[i].toString()}`;
    88. }
    89. return objString;
    90. }
    91. }

应用

  1. 在数学中,有一个叫作多重集的概念,它允许我们向集合中插入之前已经添加过的元素。多重集(或袋)在计算集合中元素的出现次数时很有用。它也在数据库系统中得到了广泛运用

字典和散列表

集合表示一组互不相同的元素(不重复的元素)。在字典中,存储的是[键,值]对,其中键名是用来查询特定元素的。字典和集合很相似,集合以[值,值]的形式存储元素,字典则是以[键,值]的形式来存储元素。字典也称作映射、符号表或关联数组。在计算机科学中,字典经常用来保存对象的引用地址。例如,打开Chrome |开发者工具中的Memory标签页,执行快照功能,我们就能看到内存中的一些对象和它们对应的地址引用

字典类的简易实现

  1. class Dictionary {
  2. constructor(toStrFn = defaultToString) {
  3. this.toStrFn = toStrFn;
  4. this.table = {};
  5. }
  6. set(key, value) {
  7. if (key != null && value != null) {
  8. const tableKey = this.toStrFn(key);
  9. this.table[tableKey] = new ValuePair(key, value);
  10. return true;
  11. }
  12. return false;
  13. }
  14. get(key) {
  15. const valuePair = this.table[this.toStrFn(key)];
  16. return valuePair == null ? undefined : valuePair.value;
  17. }
  18. hasKey(key) {
  19. return this.table[this.toStrFn(key)] != null;
  20. }
  21. remove(key) {
  22. if (this.hasKey(key)) {
  23. delete this.table[this.toStrFn(key)];
  24. return true;
  25. }
  26. return false;
  27. }
  28. values() {
  29. return this.keyValues().map(valuePair => valuePair.value);
  30. }
  31. keys() {
  32. return this.keyValues().map(valuePair => valuePair.key);
  33. }
  34. keyValues() {
  35. return Object.values(this.table);
  36. }
  37. forEach(callbackFn) {
  38. const valuePairs = this.keyValues();
  39. for (let i = 0; i < valuePairs.length; i++) {
  40. const result = callbackFn(valuePairs[i].key, valuePairs[i].value);
  41. if (result === false) {
  42. break;
  43. }
  44. }
  45. }
  46. isEmpty() {
  47. return this.size() === 0;
  48. }
  49. size() {
  50. return Object.keys(this.table).length;
  51. }
  52. clear() {
  53. this.table = {};
  54. }
  55. toString() {
  56. if (this.isEmpty()) {
  57. return '';
  58. }
  59. const valuePairs = this.keyValues();
  60. let objString = `${valuePairs[0].toString()}`;
  61. for (let i = 1; i < valuePairs.length; i++) {
  62. objString = `${objString},${valuePairs[i].toString()}`;
  63. }
  64. return objString;
  65. }
  66. }
  67. function defaultToString(item) {
  68. if (item === null) {
  69. return 'NULL';
  70. } else if (item === undefined) {
  71. return 'UNDEFINED';
  72. } else if (typeof item === 'string' || item instanceof String) {
  73. return `${item}`;
  74. }
  75. return item.toString();
  76. }
  77. class ValuePair {
  78. constructor(key, value) {
  79. this.key = key;
  80. this.value = value;
  81. }
  82. toString() {
  83. return `[#${this.key}: ${this.value}]`;
  84. }
  85. }

散列表(也称作HashMap、HashTable)

散列算法的作用是尽可能快地在数据结构中找到一个值。在之前的章节中,你已经知道如果
要在数据结构中获得一个值 (使用get方法),需要迭代整个数据结构来找到它。如果使用散列函数,就知道值的具体位置,因此能够快速检索到该值。散列函数的作用是给定一个键值, 然后返回值在表中的地址。
散列表有一些在计算机科学中应用的例子。因为它是字典的一种实现,所以可以用作关联数组。它也可以用来对数据库进行索引。当我们在关系型数据库(如MySQL、Microsoft SQL Server、Oracle,等等)中创建一个新的表时,一个不错的做法是同时创建一个索引来 更快地查询到记录的key。在这种情况下,散列表可以用来保存键和对表中记录的引用。另一个很常见的应用是使用散列表来表示对象。JavaScript语言内部就是使用散列表来表示每个对象。此时,对象的每个属性和方法(成员)被存储为key对象类型,每个key指向对应的对象成员。
继续以前一节中使用的电子邮件地址簿为例。我们将使用最常见的散列函数—lose lose散列函数,方法是简单地将每个键值中的每个字母的ASCII值相加,如下图所示。
image.png

散列表的简易实现

  1. class HashTable {
  2. constructor(toStrFn = defaultToString) {
  3. this.toStrFn = toStrFn;
  4. this.table = {};
  5. }
  6. loseloseHashCode(key) {
  7. if (typeof key === 'number') {
  8. return key;
  9. }
  10. const tableKey = this.toStrFn(key);
  11. let hash = 0;
  12. for (let i = 0; i < tableKey.length; i++) {
  13. hash += tableKey.charCodeAt(i);
  14. }
  15. return hash % 37;
  16. }
  17. /* djb2HashCode(key) {
  18. const tableKey = this.toStrFn(key);
  19. let hash = 5381;
  20. for (let i = 0; i < tableKey.length; i++) {
  21. hash = (hash * 33) + tableKey.charCodeAt(i);
  22. }
  23. return hash % 1013;
  24. } */
  25. hashCode(key) {
  26. return this.loseloseHashCode(key);
  27. }
  28. put(key, value) {
  29. if (key != null && value != null) {
  30. const position = this.hashCode(key);
  31. this.table[position] = new ValuePair(key, value);
  32. return true;
  33. }
  34. return false;
  35. }
  36. get(key) {
  37. const valuePair = this.table[this.hashCode(key)];
  38. return valuePair == null ? undefined : valuePair.value;
  39. }
  40. remove(key) {
  41. const hash = this.hashCode(key);
  42. const valuePair = this.table[hash];
  43. if (valuePair != null) {
  44. delete this.table[hash];
  45. return true;
  46. }
  47. return false;
  48. }
  49. getTable() {
  50. return this.table;
  51. }
  52. isEmpty() {
  53. return this.size() === 0;
  54. }
  55. size() {
  56. return Object.keys(this.table).length;
  57. }
  58. clear() {
  59. this.table = {};
  60. }
  61. toString() {
  62. if (this.isEmpty()) {
  63. return '';
  64. }
  65. const keys = Object.keys(this.table);
  66. let objString = `{${keys[0]} => ${this.table[keys[0]].toString()}}`;
  67. for (let i = 1; i < keys.length; i++) {
  68. objString = `${objString},{${keys[i]} => ${this.table[keys[i]].toString()}}`;
  69. }
  70. return objString;
  71. }
  72. }
  73. function defaultToString(item) {
  74. if (item === null) {
  75. return 'NULL';
  76. } else if (item === undefined) {
  77. return 'UNDEFINED';
  78. } else if (typeof item === 'string' || item instanceof String) {
  79. return `${item}`;
  80. }
  81. return item.toString();
  82. }
  83. class ValuePair {
  84. constructor(key, value) {
  85. this.key = key;
  86. this.value = value;
  87. }
  88. toString() {
  89. return `[#${this.key}: ${this.value}]`;
  90. }
  91. }

处理散列表的冲突

  • 分离链接
  • 线性探查
  • 双散列法

应用

  1. 在计算机科学中,字典经常用来保存对象的引用地址。例如,打开 Chrome | 开发者工具中 的 Memory 标签页,执行快照功能,我们就能看到内存中的一些对象和它们对应的地址引用(用 @<数>表示)。
  2. 散列表应用
    1. 对应数据库索引
    2. 用散列表来表示对象

      树数据结构

      树是一种分层数据的抽象模型。现实生活中最常见的树的例子是家谱,或是公司的组织架构
      图,如下图所示。
      image.png

二叉树和二叉搜索树(BST)

二叉搜索树简易实现

  1. class BinarySearchTree {
  2. constructor(compareFn = (a, b) => a === b) {
  3. this.compareFn = compareFn;
  4. this.root = undefined;
  5. }
  6. insert(key) {
  7. // special case: first key
  8. if (this.root == null) {
  9. this.root = new Node(key);
  10. } else {
  11. this.insertNode(this.root, key);
  12. }
  13. }
  14. insertNode(node, key) {
  15. if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
  16. if (node.left == null) {
  17. node.left = new Node(key);
  18. } else {
  19. this.insertNode(node.left, key);
  20. }
  21. } else if (node.right == null) {
  22. node.right = new Node(key);
  23. } else {
  24. this.insertNode(node.right, key);
  25. }
  26. }
  27. getRoot() {
  28. return this.root;
  29. }
  30. search(key) {
  31. return this.searchNode(this.root, key);
  32. }
  33. searchNode(node, key) {
  34. if (node == null) {
  35. return false;
  36. }
  37. if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
  38. return this.searchNode(node.left, key);
  39. } else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
  40. return this.searchNode(node.right, key);
  41. }
  42. return true;
  43. }
  44. inOrderTraverse(callback) {
  45. this.inOrderTraverseNode(this.root, callback);
  46. }
  47. inOrderTraverseNode(node, callback) {
  48. if (node != null) {
  49. this.inOrderTraverseNode(node.left, callback);
  50. callback(node.key);
  51. this.inOrderTraverseNode(node.right, callback);
  52. }
  53. }
  54. preOrderTraverse(callback) {
  55. this.preOrderTraverseNode(this.root, callback);
  56. }
  57. preOrderTraverseNode(node, callback) {
  58. if (node != null) {
  59. callback(node.key);
  60. this.preOrderTraverseNode(node.left, callback);
  61. this.preOrderTraverseNode(node.right, callback);
  62. }
  63. }
  64. postOrderTraverse(callback) {
  65. this.postOrderTraverseNode(this.root, callback);
  66. }
  67. postOrderTraverseNode(node, callback) {
  68. if (node != null) {
  69. this.postOrderTraverseNode(node.left, callback);
  70. this.postOrderTraverseNode(node.right, callback);
  71. callback(node.key);
  72. }
  73. }
  74. min() {
  75. return this.minNode(this.root);
  76. }
  77. minNode(node) {
  78. let current = node;
  79. while (current != null && current.left != null) {
  80. current = current.left;
  81. }
  82. return current;
  83. }
  84. max() {
  85. return this.maxNode(this.root);
  86. }
  87. maxNode(node) {
  88. let current = node;
  89. while (current != null && current.right != null) {
  90. current = current.right;
  91. }
  92. return current;
  93. }
  94. remove(key) {
  95. this.root = this.removeNode(this.root, key);
  96. }
  97. removeNode(node, key) {
  98. if (node == null) {
  99. return undefined;
  100. }
  101. if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
  102. node.left = this.removeNode(node.left, key);
  103. return node;
  104. } else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
  105. node.right = this.removeNode(node.right, key);
  106. return node;
  107. }
  108. // key is equal to node.item
  109. // handle 3 special conditions
  110. // 1 - a leaf node
  111. // 2 - a node with only 1 child
  112. // 3 - a node with 2 children
  113. // case 1
  114. if (node.left == null && node.right == null) {
  115. node = undefined;
  116. return node;
  117. }
  118. // case 2
  119. if (node.left == null) {
  120. node = node.right;
  121. return node;
  122. } else if (node.right == null) {
  123. node = node.left;
  124. return node;
  125. }
  126. // case 3
  127. const aux = this.minNode(node.right);
  128. node.key = aux.key;
  129. node.right = this.removeNode(node.right, aux.key);
  130. return node;
  131. }
  132. }
  133. const Compare = {
  134. LESS_THAN: -1,
  135. BIGGER_THAN: 1,
  136. EQUALS: 0
  137. };
  138. class Node {
  139. constructor(key) {
  140. this.key = key;
  141. this.left = undefined;
  142. this.right = undefined;
  143. }
  144. toString() {
  145. return `${this.key}`;
  146. }
  147. }

树的遍历

  • 中序: 排序
  • 先序: 打印结构化文档
  • 后序:(计算一个目录及子目录所有文件所占大小)

自平衡树 (AVL 与红黑树)

对AVL书插人和移除节点可能会造成旋转,所以我们需要一个包含多次插人和删除的自平衡树,红黑树是比较好的。如果插人和删除频率较低(我们更需要多次进行搜索操作), 那么AVL树比红黑树更好。

二叉堆(一种特殊的二叉树)

  • 它是一棵完全二二叉树,表示树的每-层都有左侧和右侧子节点(除了最后一层的叶节点), 并且最后一层的叶节点尽可能都是左侧子节点,这叫作结构特性
  • 二叉堆不是最小堆就是最大堆。最小堆允许你快速导出树的最小值,最大堆允许你快速导出树的最大值。所有的节点都大于等于( 最大堆)或小于等于( 最小堆)每个它的子节点。这叫做堆特性

    二叉最小堆的简易实现

    ```javascript class MinHeap { constructor(compareFn = (a, b) => a === b) { this.compareFn = compareFn; this.heap = []; } getLeftIndex(index) { return (2 index) + 1; } getRightIndex(index) { return (2 index) + 2; } getParentIndex(index) { if (index === 0) {
    1. return undefined;
    } return Math.floor((index - 1) / 2); } size() { return this.heap.length; } isEmpty() { return this.size() <= 0; } clear() { this.heap = []; } findMinimum() { return this.isEmpty() ? undefined : this.heap[0]; } insert(value) { if (value != null) {
    1. const index = this.heap.length;
    2. this.heap.push(value);
    3. this.siftUp(index);
    4. return true;
    } return false; } siftDown(index) { let element = index; const left = this.getLeftIndex(index); const right = this.getRightIndex(index); const size = this.size(); if (
    1. left < size &&
    2. this.compareFn(this.heap[element], this.heap[left]) === Compare.BIGGER_THAN
    ) {
    1. element = left;
    } if (
    1. right < size &&
    2. this.compareFn(this.heap[element], this.heap[right]) === Compare.BIGGER_THAN
    ) {
    1. element = right;
    } if (index !== element) {
    1. swap(this.heap, index, element);
    2. this.siftDown(element);
    } } siftUp(index) { let parent = this.getParentIndex(index); while (
    1. index > 0 &&
    2. this.compareFn(this.heap[parent], this.heap[index]) === Compare.BIGGER_THAN
    ) {
    1. swap(this.heap, parent, index);
    2. index = parent;
    3. parent = this.getParentIndex(index);
    } } extract() { if (this.isEmpty()) {
    1. return undefined;
    } if (this.size() === 1) {
    1. return this.heap.shift();
    } const removedValue = this.heap[0]; this.heap[0] = this.heap.pop(); this.siftDown(0); return removedValue; } heapify(array) { if (array) {
    1. this.heap = array;
    } const maxIndex = Math.floor(this.size() / 2) - 1; for (let i = 0; i <= maxIndex; i++) {
    1. this.siftDown(i);
    } return this.heap; } getAsArray() { return this.heap; } } class MaxHeap extends MinHeap { constructor(compareFn = defaultCompare) { super(compareFn); this.compareFn = compareFn; this.compareFn = reverseCompare(compareFn); } }

function swap(array, a, b) { [array[a], array[b]] = [array[b], array[a]]; } function reverseCompare(compareFn) { return (a, b) => compareFn(b, a); } const Compare = { LESS_THAN: -1, BIGGER_THAN: 1, EQUALS: 0 }; ```

应用

  1. 堆排序
  2. DOM, AST 等应用都是对人脑分层分类认知的建模, 都是树
  3. 文件压缩 (霍夫曼树)

  • 图的概念

顶点、边、有向图、无向图、加权图、 无环图连通图

  • 图的表示

邻接矩阵、邻接表、关联矩阵

创建Graph类

图的遍历

  • 广度优先
  • 深度优先

应用

  1. 最小生成树
    1. Prim 算法 对应现实生活中的修路问题
    2. KrusKal算法 对应现实用说中的 公交站问题
  2. 最短路径算法
    1. Dikstra算法 (属于贪心算法)
    2. Floyd-Warshall算法 (属于动态规划算法)