一. 二叉树
二叉树的性质:
1.树是 非线性数据结构
2.度:
3.完全二叉树
4.堆是完全二叉树
5.普通二叉树 性质
6.完全二叉树的性质:计算 叶子节点的个数
一般二叉树:
n = n0 + n1 + n2;
n - 1 = n1 + 2 n2; // -1 是去掉跟节点
=> n0 = n2 + 1; // n0 和 n2 相差很少
*完全二叉树:
1)n0 = ?
n0 = n2 + 1; // n0 和 n2 相差很少
由 n = n0 + n1 + n2;
=> n = n0 + n1 + (n0 - 1);
=> n0 = ((n + 1) - n1) / 2;
n 为奇数,n0 = (n + 1) / 2;
n 为偶数,n0 = n / 2;
2)n1 为 0, n 为奇数
3)n1 为 1,n 为偶数
n 为 3, n0 为 2
n 为 4, n0 为 2
n 为 5, n0 为 3
n 为 6, n0 为 3
=> n0 = Math.ceil(n / 2);
n 为 3, 非 n0 为 1
n 为 4, 非 n0 为 2
n 为 5, 非 n0 为 2
n 为 6, 非 n0 为 3
=> n1 + n2 = Math.floor(n / 2);
二叉搜索树:http://caibaojian.com/js-bst.html
二. 堆
堆 是 完全二叉树,在构建堆时,应用到了 完全二叉树的性质,比如说 非叶子结点的下标是
[0, Math.floor(n / 2)]。
另外构建堆,分为两步:(参考:https://zhuanlan.zhihu.com/p/89600623)
1)从末尾非叶子几点遍历
2)以每个非叶子节点为根节点递归
1.构建最大堆
function createHeap(arr) {let n = arr.length;const maxHeapify = function(rootIndex) {let leftIndex = 2 * rootIndex + 1;let rightIndex = 2 * rootIndex + 2;let largest = rootIndex; // 流动红旗// 左右子树先后竞争if (leftIndex < n && arr[leftIndex] > arr[largest]) {largest = leftIndex;}if (rightIndex < n && arr[rightIndex] > arr[largest]) {largest = rightIndex;}// 检查竞争结果,察觉红旗是否最终改动到子树上if (largest !== rootIndex) {// 交换let tmp = arr[rootIndex];arr[rootIndex] = arr[largest];arr[largest] = tmp;// 递归maxHeapify(largest);}};// 遍历 非叶子节点for (let i = Math.floor(n / 2); i >= 0; i--) {maxHeapify(i);}}
2.最大堆排序
function createHeap(arr, heapSize) { // 添加heapsize参数:在不复制数组的情况下,对原数组精确操作let n = heapSize;const maxHeapify = function(rootIndex) {let leftIndex = 2 * rootIndex + 1;let rightIndex = 2 * rootIndex + 2;let largest = rootIndex; // 流动红旗// 左右子树先后竞争if (leftIndex < n && arr[leftIndex] > arr[largest]) {largest = leftIndex;}if (rightIndex < n && arr[rightIndex] > arr[largest]) {largest = rightIndex;}// 检查竞争结果,察觉红旗是否最终改动到子树上if (largest !== rootIndex) {// 交换let tmp = arr[rootIndex];arr[rootIndex] = arr[largest];arr[largest] = tmp;// 递归maxHeapify(largest);}};for (let i = Math.floor(n / 2); i >= 0; i--) {maxHeapify(i);}}console.log(createHeap([3, 4, 5, 1, 5, 7, 2]));function swap(arr, i, j) {let tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}// function heapSort(arr) {// let n = arr.length;// for (let i = 0; i < n - 1; i++) {// createHeap(arr.slice(0, n - i)); // 避免陷入怎么传入数组的困惑,也避免传入起始下标,因为开始下标是固定的,只需要传入堆的长度即可// swap(arr, 0, n - 1 - i);// }// return arr;// }function heapSort(arr) {let n = arr.length;for (let i = 0; i < n - 1; i++) {createHeap(arr, n - i); // 避免陷入怎么传入数组的困惑,也避免传入起始下标,因为开始下标是固定的,只需要传入堆的长度即可swap(arr, 0, n - i - 1);}return arr;}console.log(heapSort([3, 4, 5, 1, 5, 7, 2]));
3.最大堆 插入/删除 新值
堆 在这里 是以数组存储。
插入:在已经生成的最大堆插入新值,是将 新值 放到数组末尾,然后重复生成最大堆的动作。
删除,是将数组末尾的数 覆盖 root 之后,然后重复生成最大堆的动作。
图例
function deleteHeap(arr) {let n = arr.length;arr[0] = arr[n - 1];arr.length = n - 1; // 缩减数组长度createHeap(arr, arr.length);}function addHeap(arrm, num) {arr.push(num);createHeap(arr, arr.length);}
3.最小堆
很好改:
function createHeap(arr, heapSize) {let n = heapSize;const minHeapify = function(rootIndex) {let leftIndex = 2 * rootIndex + 1;let rightIndex = 2 * rootIndex + 2;let samllest = rootIndex; // 流动红旗 ---> new// 左右子树先后竞争if (leftIndex < n && arr[leftIndex] < arr[samllest]) { // ---> newsamllest = leftIndex;}if (rightIndex < n && arr[rightIndex] < arr[samllest]) { // ---> newsamllest = rightIndex;}// 检查竞争结果,察觉红旗是否最终改动到子树上if (samllest !== rootIndex) {// 交换let tmp = arr[rootIndex];arr[rootIndex] = arr[samllest];arr[samllest] = tmp;// 递归minHeapify(samllest);}};for (let i = Math.floor(n / 2); i >= 0; i--) {minHeapify(i);}}
这样的排序,最终出来是升序。
4.最大堆在react里的使用
react 算法中的 堆,这个在讲 自己 看多react 源码时,最好 也能手写出来。
以下是 react 的实现:
export function push(heap: Heap, node: Node): void {const index = heap.length;heap.push(node);siftUp(heap, node, index);}export function peek(heap: Heap): Node | null {const first = heap[0];return first === undefined ? null : first;}export function pop(heap: Heap): Node | null {const first = heap[0];if (first !== undefined) {const last = heap.pop();if (last !== first) {heap[0] = last;siftDown(heap, last, 0);}return first;} else {return null;}}function siftUp(heap, node, i) {let index = i;while (true) {const parentIndex = (index - 1) >>> 1;const parent = heap[parentIndex];if (parent !== undefined && compare(parent, node) > 0) {// The parent is larger. Swap positions.heap[parentIndex] = node;heap[index] = parent;index = parentIndex;} else {// The parent is smaller. Exit.return;}}}function siftDown(heap, node, i) {let index = i;const length = heap.length;while (index < length) {const leftIndex = (index + 1) * 2 - 1;const left = heap[leftIndex];const rightIndex = leftIndex + 1;const right = heap[rightIndex];// If the left or right node is smaller, swap with the smaller of those.if (left !== undefined && compare(left, node) < 0) {if (right !== undefined && compare(right, left) < 0) {heap[index] = right;heap[rightIndex] = node;index = rightIndex;} else {heap[index] = left;heap[leftIndex] = node;index = leftIndex;}} else if (right !== undefined && compare(right, node) < 0) {heap[index] = right;heap[rightIndex] = node;index = rightIndex;} else {// Neither child is smaller. Exit.return;}}}function compare(a, b) {// Compare sort index first, then task id.const diff = a.sortIndex - b.sortIndex;return diff !== 0 ? diff : a.id - b.id;}
我的实现:(简洁一些,不考虑细枝末节)
// 不用递归,用 while 循环function push(heap = [], v) {heap.push(v);shiftUp(heap, v);}function shiftUp(heap, v) {let index = heap.length;while (index >= 0) {let parentIndex = index >>> 1;let parent = heap[parentIndex];if (compare(parent, v) > 0) {heap[parentIndex] = v;heap[index] = parent;index = parentIndex;} else {return;}}}function siftDown(heap) { // 斩首,用末尾元素替代头颅,然后规整秩序,记住我们不管左右子节点之间的关系let index = 0;// while (true) { // 不知道规定什么条件好,先来个 truewhile (index < heap.length) {let node = heap[index];let leftIndex = 2 * index + 1;let rightIndex = 2 * leftIndex + 2;let left = heap[leftIndex];let right = heap[rightIndex];if (compare(left, node) < 0) { // 取最小即可if (compare(left, right) < 0) { // left 最小,只能和 left 换heap[leftIndex] = node;heap[index] = left;index = leftIndex;} else {heap[rightIndex] = node;heap[index] = right;index = rightIndex;}} else if (compare(right, node) < 0) {heap[rightIndex] = node;heap[index] = right;index = rightIndex;} else {// 不动return;}}}function compare(a, b) {return a - b;}
react 里最多的数据结构:链表相关(单链表 双向链表)
判断单链表是否有环、翻转链表、插入、删除。
判断单链表是否有环,也可以扩展到 对象里是否有循环结构,lodash 是如何解决的?
参考:https://juejin.im/post/6844903482432962573
includes 是 严格的等!
从这篇文章我想明白为什么要用栈的结构:因为一个对象可能有并列的多个环,另外还要考虑值类型不能是基本类型,只能是指向地址的对象。
https://www.jianshu.com/p/a2bb29aa4fce
三. 链表
1. 链表结构
class ListNode {constructor(value) {this.next = null;this.value = value;}}class List {constructor(arr) {this.head = this.tail = null;this.length = 0;if (Array.isArray(arr)) {for (let i of arr) {let node = new ListNode(i);this.append(node);}}}append(node) {if (this.head) {this.tail = this.tail.next = node;} else {this.head = this.tail = node;}this.length++;}deleteByValue(targetValue) {let p = this.head;let parent = this.head;let nodeToDelete = null;while (p) {let { value } = p;if (value === targetValue) {nodeToDelete = p;break;} else {parent = p;p = p.next;}}if (nodeToDelete) {parent.next = nodeToDelete.next;this.length--;}}}let l = new List([1, 2, 3, 4, 5]);console.log('创建链表:', l);l.deleteByValue(3);console.log('删除值为3的节点', l);
2. 合并两个有序链表
// 合并有序数组(最简单情况,数组长度相同)function mergeList(list1, list2) {let p = list1.head;let q = list2.head;let newListHead = null;let current = null;while (p && q) {// 1) 获取最小节点赋值 给c, 并更新 p qlet c = null; // 一开始的误区就是少定义了这个变量if (p.value < q.value) { // p 和 q 两者取最小c = p;p = p.next;} else {c = q;q = q.next;}// 2)构造新链表的节点关系if (newListHead) {current.next = c;current = current.next; // current 要变化才行!} else {newListHead = c;current = c;}}return newListHead;}let l1 = new List([1, 2, 9, 10]);let l2 = new List([3, 4, 11, 12]);console.log(`合并l1和l2的结果:`, mergeList(l1, l2));console.log();
3. 逆转单链表
function inverseList(list) {let o = null;let p = list.head;let q = p.next;while(q) { // 首先要有三个 o p q 指针,另外要同时保证在 每轮循环 移动 o p q 三个指针的同时,并且修正相互指代的关系p.next = o;o = p;p = q;q = p.next;p.next = o;}return p;}console.log(inverseList(new List([1, 2, 3, 4])));
逆转单链表,从来都是最绕的,没有之一:(下面是第二次写,给的写法)
class List {// ...invert() {// 定义三个相邻的指针let x = null;let p = this.headlet y = this.head.next;while (p) { // while循环的条件只要定为,循环体不报错就好// 逆转的关键p.next = x;// 移动三个指针x = p;p = y;y = y && y.next;}this.head = x;return this;}// ...}
四、图
1. 拓扑排序
可用来检测 图里是否有环!(判断是否有环,常规的做法也可以借助数据结构,比如说一个数组,来存放已经被访问过的元素)。
参考:https://blog.csdn.net/weixin_42001089/article/details/84327306
toposort 是 npm 包,提供给你做 图的拓扑排序。
https://hotttao.github.io/2018/11/19/alog/graph_use2/ 来讲了 拓扑排序 在编译器领域的使用。
拓扑排序的题目:
答案:
山的那边海的那边有一群小精灵!

