前端算法
面试一旦公司算法少,则网络,linux 会增加,统计概率会增加
多练!!!
数据结构与算法
数据结构与算法有什么关系?
可以容纳数据的结构称为数据结构
算法是用来对数据结构进行处理的方法 ;数据结构是静态的,算法是动态的
线性数据结构之数组
一维数据结构:(线性数据结构)
线性的数据结构强调存储与顺序
数组:底层上数组定长
数组特性:
- 存储在物理空间上是连续的
- 底层的数组长度是不可变的 (JS 引擎做出优化导致学的是可变的)
- 数组的变量指向的是数组第一个元素的位置
var a={1,2,3,4,5,6,7,8}
a[1], a[2], a[3] //方括号表示的是存储地址的偏移。
- 操作系统小知识:通过偏移查询数据性能最好
优点:查询性能最好。指定查询某个位置。
缺点:- 因为空间必须连续,所以如果数组比较大,当系统的空间碎片较多的时候,容易存不下
- 因为数组的长度是固定的,所以数组的内容难以添加和删除
深究操作系统课程
var a = [1, 2, 3, 4, 5];
var arr = new Array(10); //初始化数组长度
线性数据结构之链表
计算机四门专业课自学
演示 demo
var b = {
value: 2,
next: null,
};
// 实验得知:b要定义在a声明的前面
var a = {
value: 1,
next: b, //不是嵌套关系,而是引用关系
};
console.log(a.next == b); //两个对象比较,比较的是地址
链表的特点:
- 空间上不是连续的。
- 每存放一个值,都要多开销一个引用空间。
优点:
单链表。我想传递一个链表,我必须传递链表的根节点。
**每一个节点都认为自己是根节点(因为每个节点都记录自己和 next)**。
- 只要内存足够大,就能存的下,不用担心空间碎片的问题
- 链表的添加和删除非常容易
缺点:
- 查询速度慢。(查询某个位置)
- 链表每一个节点都需要创建一个指向 next 的引用,浪费一些空间。
- 当节点内数据越多的时候(即 value 越多),这部分多开销(即 next)的内存影响较少。
创建链表
function Node(value) {
this.value = value;
this.next = null;
}
var a = new Node(1);
var b = new Node(2);
var c = new Node(3);
var d = new Node(4);
a.next = b;
b.next = c;
c.next = d;
d.next = null;
console.log(a.value);
console.log(a.next.value);
console.log(a.next.next.value);
console.log(a.next.next.next.value);
线性数据结构之遍历
遍历:将集合中的每一个元素进行获取并查看
数组遍历
var arr = [1, 2, 3, 4, 5, 6, 7, 8];
function bianArr(arr) {
if (arr == null) {
// undefined==null也会跳出,容错性较好
return;
} //算法只要报错就没分 严谨性判断
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
}
bianArr(arr);
链表遍历
function Node(value) {
this.value = value;
this.next = null;
}
var node1 = new Node(1);
var node2 = new Node(2);
var node3 = new Node(3);
var node4 = new Node(4);
var node5 = new Node(5);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
function bianLink(root) {
var temp = root; // 寄存
while (true) {
//不知道循环次数,用while(true)
if (temp != null) {
console.log(temp.value);
} else {
break;
}
temp = temp.next;
}
}
bianLink(node1);
线性数据结构的递归遍历
数组的递归遍历 不推荐
var arr = [1,2,3,4,5,6,7,8];
function bianArr(arr, i){
if(arr == null || arr.length <= i) return;
//考虑数组长度和是否为空
console.log(arr[i]);
// bianArr(arr,i++);为什么不行
bianArr(arr,i+1);
}
bianArr(arr, 0);
链表的递归遍历 推荐
function Node(value){
this.value = value;
this.next = null;
}
var node1 = new Node(1);
var node2 = new Node(2);
var node3 = new Node(3);
var node4 = new Node(4);
var node5 = new Node(5);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
//递归遍历,必须有出口
function bianLink(root){//
if(root == null) {
return ;
}
console.log(root.value);
bianLink(root.next);
}
bianLink(node1);
链表的逆置
function Node(value) {
this.value = value;
this.next = null;
}
var node1 = new Node(1);
var node2 = new Node(2);
var node3 = new Node(3);
var node4 = new Node(4);
var node5 = new Node(5);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
/*
找到最后一个节点
function nizhi(root){
if(root.next != null){
return nizhi(root.next);
}else{
return root;
}
console.log(nizhi(node1))//找到了最后一个节点
}
// 以上行为有缺陷:每个节点都认为自己是根节点,不能实现最后一个节点指向上一个节点
*/
// 思想:递归先找出口
function nizhi(root) {
if (root.next.next == null) {
//代表当前节点是倒数第二个节点
root.next.next = root; //让最后一个节点指向自己(倒数第二个节点)
return root.next; //return 最后一个节点是root.next
} else {
// 当前节点不是倒数第二个节点
var result = nizhi(root.next); // 不是的话就递归
root.next.next = root; //老规矩:下一个的指向指向自己
root.next = null; //第一个必须有指向,指向空
return result;
}
}
// console.log(nizhi(node1))
//输出逆置后的顺序
var newRoot = nizhi(node1);
function bianLink(root) {
if (root == null) return;
console.log(root.value);
bianLink(root.next);
}
bianLink(newRoot);
冒泡排序
背景:乱序数组排序
var arr = [4, 1, 6, 9, 3, 2, 7, 8];
function getMin(arr) {
if (arr == null || arr.length == 0) return;
var index = -1;
for (var i = 0; i < arr.length; i++) {
if (
(arr[i] != null && arr[i] < arr[index]) ||
(arr[i] != null && index == -1)
) {
index = i;
}
}
var result = arr[index];
arr[index] = null;
return result;
}
function sort(arr) {
var newArr = new Array(arr.length);
for (var i = 0; i < newArr.length; i++) {
newArr[i] = getMin(arr);
}
return newArr;
}
console.log(sort(arr));
冒泡排序:每一次循环都把最大的排在最后面
var arr = [4, 1, 6, 9, 3, 2, 7, 8];
// 排序不是比较大小,本质是比较和交换
function compare(a, b) {
//比较之后需要得出是否需要交换
if (b < a) return true;
else return false;
}
function exchange(arr, a, b) {
//将数组ab位置里面的值交换
var temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
function sort(arr) {
for (var i = 0; i < arr.length; i++) {
//要进行n次筛选
for (var j = 0; j < arr.length - 1 - i; j++) {
//少i圈 里面的for是进行一次筛选选出最大的
if (compare(arr[j], arr[j + 1])) {
exchange(arr, j, j + 1);
}
}
}
}
sort(arr);
console.log(arr);
选择排序
var arr = [4, 1, 6, 9, 3, 2, 7, 8];
function compare(a, b) {
if (a < b) return true;
else return false;
}
function exchange(arr, a, b) {
var temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
function sort(arr) {
for (var i = 0; i < arr.length; i++) {
// 循环全部圈数
var maxIndex = 0; //最大的数的序号
for (var j = 0; j < arr.length - i; j++) {
//一圈下来谁是最大的
if (compare(arr[maxIndex], arr[j])) {
maxIndex = j; //j是最大的
}
}
exchange(arr, maxIndex, arr.length - 1 - i);
}
}
sort(arr);
console.log(arr);
// 任何一种排序算法,都没有优劣之分,只有是否适合的场景 越乱,越适合选择排序;越有序,越适合冒泡排序
简单快速排序
var arr = [4, 1, 6, 9, 3, 2, 8, 7];
function quickSort(arr) {
if (arr == null || arr.length == 0) return []; //如果不是空数组,则没有对应的push方法,导致报错
//选班长,小的站在左边,大的站在右边
var leader = arr[0];
var left = [];
var right = [];
for (var i = 1; i < arr.length; i++) {
if (arr[i] < leader) left.push(arr[i]);
else right.push(arr[i]);
}
left = quickSort(left);
right = quickSort(right);
left.push(leader);
return left.concat(right);
}
console.log(quickSort(arr));
标准快速排序
默认左闭右开
var arr = [4, 1, 6, 9, 3, 2, 8, 7];
function swap(arr, a, b) {
var temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
function quickSort2(arr, begin, end) {
if (begin >= end - 1) return;
var left = begin;
var right = end;
do {
do left++;
while (left < right && arr[left] < arr[begin]);
do right--;
while (right > left && arr[right] > arr[begin]);
if (left < right) swap(arr, left, right);
} while (left < right);
var swapPoint = left == right ? right - 1 : right;
swap(arr, begin, swapPoint);
quickSort2(arr, begin, swapPoint);
quickSort2(arr, swapPoint + 1, end);
}
function quickSort(arr) {
quickSort2(arr, 0, arr.length); //取到0 娶不到arr.length
}
quickSort(arr);
console.log(arr);
栈和队列
栈结构:先入后出,类比成一个箱子
队列结构:先入先出,类比成一个管道
栈
function Stack() {
this.arr = [];
this.push = function (value) {
this.arr.push(value);
};
this.pop = function () {
return this.arr.pop();
};
}
var stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.arr);
stack.pop();
console.log(stack.arr);
队列
function Queue() {
this.arr = [];
this.push = function (value) {
this.arr.push(value);
};
this.pop = function () {
return this.arr.shift();
};
}
var queue = new Queue();
queue.push(1);
queue.push(2);
queue.push(3);
console.log(queue.arr);
queue.pop();
console.log(queue.arr);
双向链表
function Node(value) {
this.value;
this.next = null;
this.pre = null;
}
var node1 = new Node(1);
var node2 = new Node(2);
var node3 = new Node(3);
var node4 = new Node(4);
var node5 = new Node(5);
node1.next = node2;
node2.pre = node1;
node2.next = node3;
node3.pre = node2;
node3.next = node4;
node4.pre = node3;
node4.next = node5;
node5.pre = node4;
// 双向链表的优点,无论给出哪一个节点,都能对整个链表进行遍历。
// 双向链表的缺点,多耗费一个引用的空间,而且构建双向链表比较复杂。
二维数据结构
二维数组
二维拓扑结构(图)—离散数学图论
var arr = new Array(4);
for (var i = 0; i < arr.length; i++) {
arr[i] = new Array(8);
}
function Node(value) {
this.value = value;
this.neighbor = [];
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
a.neighbor.push(b);
a.neighbor.push(c);
a.neighbor.push(f);
b.neighbor.push(a);
b.neighbor.push(d);
b.neighbor.push(e);
c.neighbor.push(a);
d.neighbor.push(b);
e.neighbor.push(b);
树形结构
特殊的二维拓扑结构——树形结构
树形结构——有向无环图
树是图的一种
树形结构有一个根节点,树形结构没有回路
叶子节点:下面没有其他节点
节点:既不是根节点,又不是叶子节点的普通节点
树的度:这棵树有最多叉的节点有多少个叉,就有多少度
树的深度:树最深有几层,树的深度就为几
满二叉树
树的度最多为 2 的树形结构
特性
- 二叉树的根节点 A
- 子节点:某个节点下面的节点
- 父节点:上级节点
- 叶子节点:CDE
- 节点:B
满二叉树:
- 所有的叶子节点都在最底层
- 每个非叶子节点都有两个子节点
完全二叉树
国内:
- 叶子节点都在最后一层或倒数第二层
- 叶子节点都向左聚拢
国际:
- 叶子节点都在最后一层或倒数第二层
- 如果有叶子节点,必然有两个叶子节点
子树的概念
子树:(树枝)二叉树中,每一个节点或叶子节点,都是一棵树的根节点
链表每一个节点都认为自己是根节点
二叉树中每一个节点都认为自己是根节点
左子树、右子树:相对谁而言。
遍历
传递二叉树要传根节点
前序遍历:(先根次序遍历)——根左(左边的子树)右
中序遍历:(中根次序遍历)——左根右
后序遍历:(后根次序遍历)——左右根
前序遍历
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");
a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;
function f1(root) {
//传入根节点
if (root == null) return;
console.log(root.value);
f1(root.left);
f1(root.right);
}
f1(a);
二叉树的中序遍历
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");
a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;
function f1(root) {
//传入根节点
if (root == null) return;
f1(root.left);
console.log(root.value);
f1(root.right);
}
f1(a);
- 给出二叉树,写出前中后序遍历
- 写出代码
- 给出前中序还原二叉树,写出后序遍历
- 给出后中序还原二叉树,写出前序遍历
- 代码实现
必须有中序才能还原二叉树
前中序还原二叉树
前序:ACFGBDE —— A CFGBDE(A 是根节点)— A CFG BDE(C B 是根节点)
中序:FCGADBE ——- FCG(左子树 3) A DBE(右子树 3)
后序:FGCDEBA
后中序还原二叉树
中序:FCGADBE —— FCG(左子树 3) A DBE(右子树 3)
后序:FGCDEBA ——- FGCDEB A(A 是根节点)
前序:ACFGBDE
代码前中序还原二叉树
var qian = ["a", "c", "f", "g", "b", "d", "e"];
var zhong = ["f", "c", "g", "a", "d", "b", "e"];
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
function f1(qian, zhong) {
if (
qian == null ||
zhong == null ||
qian.length == 0 ||
zhong.length == 0 ||
qian.length != zhong.length
)
return null;
var root = new Node(qian[0]);
var index = zhong.indexOf(root.value); //找到根节点在中序遍历中的位置
// indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。\
//slice左闭右开
var qianLeft = qian.slice(1, 1 + index); //前序遍历的左子树
var qianRight = qian.slice(1 + index, qian.length); //前序遍历的右子树
var zhongLeft = qian.slice(0, index); //中序遍历的左子树
var zhongRight = qian.slice(index + 1, zhong.length); //中序遍历的右子树
root.left = f1(qianLeft, zhongLeft);
root.right = f1(qianRight, zhongRight);
return root;
}
var root = f1(qian, zhong);
console.log(root.left);
console.log(root.right);
代码后中序还原二叉树
var zhong = ["f", "c", "g", "a", "d", "b", "e"];
var hou = ["f", "g", "c", "d", "e", "b", "a"];
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
function f1(zhong, hou) {
if (
hou == null ||
zhong == null ||
hou.length == 0 ||
zhong.length == 0 ||
hou.length != zhong.length
)
return null;
var root = new Node(hou[hou.length - 1]);
var index = zhong.indexOf(root.value);
var leftZhong = zhong.slice(0, index);
var rightZhong = zhong.slice(index + 1, zhong.length);
var leftHou = hou.slice(0, index);
var rightHou = hou.slice(index, hou.length - 1);
root.left = f1(leftZhong, leftHou);
root.right = f1(rightZhong, rightHou);
return root;
}
var root = f1(zhong, hou);
console.log(root.left);
console.log(root.right);
二叉树的深搜和广搜
二叉树的搜索:树的搜索、图的搜索、爬虫的逻辑、搜索引擎的爬虫算法
深度优先搜索:更适合探索未知
广度优先搜索:更适合探索局域
代码实现二叉树的深度优先搜索
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");
a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;
//对于二叉树来说,深度优先搜素和前序遍历的顺序一样
function deepSearch(root, target) {
if (root == null) return false;
if (root.value == target) return true;
var left = deepSearch(root.left, target);
var right = deepSearch(root.right, target);
return left || right;
}
console.log(deepSearch(a, "f"));
代码实现二叉树的广度优先搜索
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");
a.left = c;
a.right = b;
c.left = f;
c.right = g;
b.left = d;
b.right = e;
function f1(rootList, target) {
if (rootList == null || rootList.length == 0) return false;
var childList = [];
//当前层所有节点的子节点都在这个list中,这样传入下一层级的时候,就可以遍历整个层级的节点
for (i = 0; i < rootList.length; i++) {
console.log(rootList[i].value);
if (rootList[i] != null && rootList[i].value == target) {
return true;
} else {
childList.push(rootList[i].left);
childList.push(rootList[i].right);
}
}
return f1(childList, target);
}
console.log(f1([a], "f"));
二叉树的比较
二叉树左右子树互换后不是一颗二叉树
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a1 = new Node("a");
var b1 = new Node("b");
var c1 = new Node("c");
var d1 = new Node("d");
var e1 = new Node("e");
var f1 = new Node("f");
var g1 = new Node("g");
a1.left = c1;
a1.right = b1;
c1.left = f1;
c1.right = g1;
b1.left = d1;
b1.right = e1;
var a2 = new Node("a");
var b2 = new Node("b");
var c2 = new Node("c");
var d2 = new Node("d");
var e2 = new Node("e");
var f2 = new Node("f");
var g2 = new Node("g");
a2.left = c2;
a2.right = b2;
c2.left = f2;
// c2.right = g2;
b2.left = d2;
b2.right = e2;
function compareTree(root1, root2) {
//都是空算相等吗?算
if (root1 == root2) return true; //是同一颗树
if ((root1 == null && root2 != null) || (root2 == null && root1 != nul))
return false;
if (root1.value != root2.value) return false; //相同位置的值不相等
var leftBool = compareTree(root1.left, root2.left); //左子树是否相等
var rightBool = compareTree(root1.right, root2.right); //右子树是否相等
return leftBool && rightBool; //左右都相等才相等
//以上三句简化:
//return compareTree(root1.left, root2.left) && compareTree(root1.right, root2.right)
}
console.log(compareTree(a1, a2));
//遇到二叉树比较问题,必须确定,左右两颗子树交换位置,即左右互换算不算同一颗
//以上:不算
//笔试没有特殊说明默认互换不是同一颗树;面试问一下
二叉树左右子树互换后的比较
二叉树左右子树互换后是一颗二叉树
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a1 = new Node("a");
var b1 = new Node("b");
var c1 = new Node("c");
var d1 = new Node("d");
var e1 = new Node("e");
var f1 = new Node("f");
var g1 = new Node("g");
a1.left = c1;
a1.right = b1;
c1.left = f1;
c1.right = g1;
b1.left = d1;
b1.right = e1;
var a2 = new Node("a");
var b2 = new Node("b");
var c2 = new Node("c");
var d2 = new Node("d");
var e2 = new Node("e");
var f2 = new Node("f");
var g2 = new Node("g");
// a2.left = c2;
// a2.right = b2;
a2.right = c2;
a2.left = b2;
c2.left = f2;
c2.right = g2;
b2.left = d2;
b2.right = e2;
function compareTree(root1, root2) {
if (root1 == root2) return true;
if ((root1 == null && root2 != null) || (root2 == null && root1 != nul))
return false;
if (root1.value != root2.value) return false;
return (
(compareTree(root1.left, root2.left) &&
compareTree(root1.right, root2.right)) ||
(compareTree(root1.left, root2.right) &&
compareTree(root1.right, root2.left))
);
}
console.log(compareTree(a1, a2));
二叉树的 diff 算法
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a1 = new Node("a");
var b1 = new Node("b");
var c1 = new Node("c");
var d1 = new Node("d");
var e1 = new Node("e");
var f1 = new Node("f");
var g1 = new Node("g");
a1.left = c1;
a1.right = b1;
c1.left = f1;
c1.right = g1;
b1.left = d1;
b1.right = e1;
var a2 = new Node("a");
var b2 = new Node("b");
var c2 = new Node("c");
var d2 = new Node("x");
var e2 = new Node("e");
var f2 = new Node("f");
var g2 = new Node("g");
a2.left = c2;
a2.right = b2;
c2.left = f2;
// c2.right = g2;
b2.left = d2;
b2.right = e2;
f2.right = g2;
//新增了什么,修改了什么,删除了什么
// {type:"新增",origin:null,now :c2},
// {type:"修改",origin:c1,now:c2},
// {type:"删除".origin:c2,now:null}
function diffTree(root1, root2, diffList) {
if (root1 == root2) return diffList;
if (root1 == null && root2 != null) {
//新增了节点
diffList.push({ type: "新增", origin: null, now: root2 });
} else if (root1 != null && root2 == null) {
//删除了节点
diffList.push({ type: "删除", origin: root1, now: null });
} else if (root1.value != root2.value) {
//相同位置节点值不同了,修改了节点
diffList.push({ type: "修改", origin: root1, now: root2 });
diffTree(root1.left, root2.left, diffList); //修改不一定改儿孙
diffTree(root1.right, root2.right, diffList);
} else {
diffTree(root1.left, root2.left, diffList);
diffTree(root1.right, root2.right, diffList);
}
}
var diffList = [];
diffTree(a1, a2, diffList);
console.log(diffList);
图的最小生成树问题
希望将所有村庄联通,花费最小
树:有向无环图
普利姆算法——加点法
克鲁斯卡尔算法——加边法
普利姆算法
- 选一个点作为起点
- 找到以当前选中点为起点路径最短的边
- 如果这个边的另一端没有被联通起来,那么就联结
- 如果这个边的另一端也早就被连进来了,则看倒数第二短的边
- 重复 2-4 直到所有的点都联通为止
克鲁斯卡尔算法
- 选择最短的边进行连接
- 要保证边连接的两端至少有一个点是新的要点
- 或者这个边是将两个部落进行连接的
- 重复 1-3 直到将所有的点都连接到一起
代码实现
表示一个图,可以使用点集合和边集合
点集合:[a,b,c,d,e,f]
A | B | C | D | E | |
---|---|---|---|---|---|
A | 0 | 4 | 7 | max | max |
B | 4 | 0 | 8 | 6 | max |
C | 7 | 8 | 0 | 5 | max |
D | max | 6 | 5 | 0 | 7 |
E | max | max | 7 | 0 |
var max = 1000000;
var pointSet = [];
var distance = [
[0, 4, 7, max, max],
[4, 0, 8, 6, max],
[7, 8, 0, 5, max],
[max, 6, 5, 0, 7],
[max, max, max, 7, 0],
];
function Node(value) {
this.value = value;
this.neighbor = [];
}
var a = new Node("A");
var b = new Node("B");
var c = new Node("C");
var d = new Node("D");
var e = new Node("E");
pointSet.push(a);
pointSet.push(b);
pointSet.push(c);
pointSet.push(d);
pointSet.push(e);
//得到当前str对应的那一列的行
function getIndex(str) {
for (var i = 0; i < pointSet.length; i++) {
if (str == pointSet[i].value) return i;
}
return -1; //愚蠢的错误,要写在for外面
}
//需要传入点的集合,边的集合,当前已经连接进入的集合
// 此方法,根据当前已经有的节点,来进行判断,获取到距离最短的点
function getMinDisNode(pointSet, distance, nowPointSet) {
var fromNode = null; //线段的起点
var minDisNode = null; //线段的终点
var minDis = max;
//根据当前已有的这些点为起点,依次判断连接其他的点的距离是多少
for (var i = 0; i < nowPointSet.length; i++) {
var nowPointIndex = getIndex(nowPointSet[i].value); //获取当前节点的序号
for (var j = 0; j < distance[nowPointIndex].length; j++) {
var thisNode = pointSet[j]; //thisNode表示distance中的点,但是这个点不是对象。
if (
nowPointSet.indexOf(thisNode) < 0 && //首先这个点不能是已经接入的点
distance[nowPointIndex][j] < minDis
) {
//其次点之间的距离得是目前的最短距离
fromNode = nowPointSet[i];
minDisNode = thisNode;
minDis = distance[nowPointIndex][j]; //更新最小距离
}
}
}
fromNode.neighbor.push(minDisNode); //起点和终点相连:即互相是邻居
minDisNode.neighbor.push(fromNode);
return minDisNode;
}
function prim(pointSet, distance, start) {
var nowPointSet = [];
nowPointSet.push(start);
//获取最小代价的边
while (true) {
var minDisNode = getMinDisNode(pointSet, distance, nowPointSet);
nowPointSet.push(minDisNode); //又连进来一个点
if (nowPointSet.length == pointSet.length) {
//所有的点都连进来了
break;
}
}
}
prim(pointSet, distance, pointSet[2]); //'C'
console.log(pointSet);
var max = 1000000;
var pointSet = [];
var distance = [
[0, 4, 7, max, max],
[4, 0, 8, 6, max],
[7, 8, 0, 5, max],
[max, 6, 5, 0, 7],
[max, max, max, 7, 0],
];
function Node(value) {
this.value = value;
this.neighbor = [];
}
var a = new Node("A");
var b = new Node("B");
var c = new Node("C");
var d = new Node("D");
var e = new Node("E");
pointSet.push(a);
pointSet.push(b);
pointSet.push(c);
pointSet.push(d);
pointSet.push(e);
function canLink(resultList, tempBegin, tempEnd) {
var beginIn = null;
var endIn = null;
for (var i = 0; i < resultList.length; i++) {
if (resultList[i].indexOf(tempBegin) > -1) {
beginIn = resultList[i];
}
if (resultList[i].indexOf(tempEnd) > -1) {
endIn = resultList[i];
}
}
//两个点都是新的点(都不在任何部落里)——可以连接,产生新的部落
// begin在A部落,end没有部落 —— A部落扩张一个村庄
// end在A部落,begin没有部落 ——A部落扩张一个村庄
// begin在A部落,end在B部落 ——将AB两个部落合并
// begin和end在同一个部落,——不可以连接
if (beginIn != null && endIn != null && beginIn == endIn) {
return false;
}
return true;
}
function link(resultList, tempBegin, tempEnd) {
var beginIn = null;
var endIn = null;
for (var i = 0; i < resultList.length; i++) {
if (resultList[i].indexOf(tempBegin) > -1) {
beginIn = resultList[i];
}
if (resultList[i].indexOf(tempEnd) > -1) {
endIn = resultList[i];
}
}
if (beginIn == null && endIn == null) {
// 两个点都是新的点(都不在任何部落里)——可以连接,产生新的部落
var newArr = [];
newArr.push(tempBegin);
newArr.push(tempEnd);
resultList.push(newArr);
} else if (beginIn != null && endIn == null) {
// begin在A部落,end没有部落 —— A部落扩张一个村庄
beginIn.push(tempEnd);
} else if (beginIn == null && endIn != null) {
// end在A部落,begin没有部落 ——A部落扩张一个村庄
endIn.push(tempBegin);
} else if (beginIn != null && endIn != null && beginIn != endIn) {
// begin在A部落,end在B部落 ——将AB两个部落合并
var allIn = beginIn.concat(endIn);
var needRemove = resultList.indexOf(endIn);
resultList.splice(needRemove, 1);
needRemove = resultList.indexOf(beginIn);
resultList.splice(needRemove, 1);
resultList.push(allIn);
}
tempBegin.neighbor.push(tempEnd);
tempEnd.neighbor.push(tempBegin);
}
function kruskal(pointSet, distance) {
var resultList = []; //这里是二维数组,此数组代表的是有多少个"部落"
while (true) {
var minDis = max;
var begin = null;
var end = null;
for (var i = 0; i < distance.length; i++) {
for (var j = 0; j < distance[i].length; j++) {
//遍历二维数组
var tempBegin = pointSet[i];
var tempEnd = pointSet[j];
if (
i != j && //去掉自己到自己的距离,因为都为0
distance[i][j] < minDis &&
canLink(resultList, tempBegin, tempEnd)
) {
minDis = distance[i][j];
begin = tempBegin;
end = tempEnd;
}
}
}
link(resultList, begin, end);
if (resultList.length == 1 && resultList[0].length == pointSet.length) {
//只存在一个部落并且部落里的村庄数=所有
break;
}
}
}
kruskal(pointSet, distance);
console.log(pointSet);
二叉搜索树
问题:有一万个数,写一个方法,进行查找,查找给定的数,返回是否存在。尽可能性能好
var arr = [];
for (var i = 0; i < 10000; i++) {
arr[i] = Math.floor(Math.random() * 10000);
}
var num = 0;
function search(arr, target) {
for (var i = 0; i < arr.length; i++) {
num += 1;
if (arr[i] == target) return true;
}
return false;
}
console.log(search(arr, 1000));
console.log(num);
性能差:1.数据结构;2.算法
问题出在数据结构上
二叉搜索树(二叉排序树)
- 二叉树
- 有排序的效果,左子树节点都比当前节点小,右子树节点都比当前节点大
二叉搜索树
var arr = [];
for (var i = 0; i < 10000; i++) {
arr[i] = Math.floor(Math.random() * 10000);
}
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var num = 0;
function search(arr, target) {
for (var i = 0; i < arr.length; i++) {
num += 1;
if (arr[i] == target) return true;
}
return false;
}
function addNode(root, num) {
if (root == null) return;
if (root.value == num) return; //重复的数
if (root.value < num) {
//目标值比当前节点大
if (root.right == null) root.right = new Node(num);
//如果右侧为空,则创建节点
else addNode(root.right, num); //如果右侧不为空,则向右侧进行递归
} else {
//目标值比当前节点小
if (root.left == null) root.left = new Node(num);
else addNode(root.left, num);
}
}
function buildSearchTree(arr) {
if (arr == null || arr.length == 0) return null;
var root = new Node(arr[0]);
for (var i = 1; i < arr.length; i++) {
addNode(root, arr[i]);
}
return root;
}
var num2 = 0;
function searchByTree(root, target) {
//二叉搜索树查找过程 前序遍历
if (root == null) return false;
num2 += 1;
if (root.value == target) return true;
if (root.value > target) return searchByTree(root.left, target);
else return searchByTree(root.right, target);
}
console.log(search(arr, 1000));
console.log(num);
var root = buildSearchTree(arr);
console.log(searchByTree(root, 1000));
console.log(num2);
平衡二叉树
根节点的左子树与右子树高度差不超过 1;
这颗二叉树的每个子树都符合第一条
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
var g = new Node("g");
var h = new Node("h");
var j = new Node("j");
a.left = b;
a.right = c;
b.left = d;
// b.right = e;
c.left = f;
c.right = g;
d.right = h;
// e.right = j;
function getDeep(root) {
//获取深度
if (root == null) return 0;
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
return Math.max(leftDeep, rightDeep) + 1; //加上当前一层
}
function isBalance(root) {
if (root == null) return true;
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
if (Math.abs(leftDeep - rightDeep) > 1) {
//不平衡
return false;
} else {
return isBalance(root.left) && isBalance(root.right); //两边同时平衡
}
}
console.log(isBalance(b));
二叉树的单旋
不平衡变成平衡
某一节点不平衡,如果左边浅,右边深,进行左单旋
旋转节点:不平衡的节点为旋转节点(2)
新根:旋转之后称为根节点的节点(5)
变化分支:父级节点发生变化的那个分支
不变分支:父级节点不变的那个分支
左单旋时:
旋转节点:当前不平衡的节点
新根:右子树的根节点
变化分支:旋转节点的右子树的左子树
不变分支:旋转节点的右子树的右子树
右单旋时:
旋转节点:当前不平衡的节点
新根:左子树的根节点
变化分支:旋转节点的左子树的右子树
不变分支:旋转节点的右子树的左子树
左单旋:
- 找到新根 5
- 找到变化分支
- 当前旋转节点的右子树为变化分支
- 新根的左孩子为旋转节点
- 返回新的根节点
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var node2 = new Node("2");
var node5 = new Node("5");
var node3 = new Node("3");
var node6 = new Node("6");
node2.right = node5;
node5.left = node3;
node5.right = node6;
function getDeep(root) {
if (root == null) return 0;
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
return Math.max(leftDeep, rightDeep) + 1;
}
function isBalance(root) {
if (root == null) return true;
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
if (Math.abs(leftDeep - rightDeep) > 1) {
//不平衡
return false;
} else {
return isBalance(root.left) && isBalance(root.right);
}
}
function leftRotate(root) {
// 找到新根
var newRoot = root.right;
// 找到变化分支
var changeTree = root.right.left;
// 当前旋转节点的右孩子为变化分支
root.right = changeTree;
// 新根的左孩子为旋转节点
newRoot.left = root;
// 返回新的根节点
return newRoot;
}
function rightRotate(root) {
// 找到新根
var newRoot = root.left;
// 找到变化分支
var changeTree = root.left.right;
// 当前旋转节点的左孩子为变化分支
root.left = changeTree;
// 新根的右孩子为旋转节点
newRoot.right = root;
// 返回新的根节点
return newRoot;
}
function change(root) {
//返回平衡之后的根节点
if (isBalance(root)) return root;
// 推荐用后序的方式来判断,先变子节点,后变根节点
if (root.left != null) root.left = change(root.left); // change后根节点root就变了
if (root.right != null) root.right = change(root.right);
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
if (Math.abs(leftDeep - rightDeep) < 2) {
return true;
} else if (leftDeep > rightDeep) {
//不平衡,左边深,需要右旋
return rightRotate(root);
} else {
//不平衡,右边深,需要左旋
return leftRotate(root);
}
}
console.log(isBalance(node2));
var newRoot = change(node2);
console.log(isBalance(newRoot));
console.log(newRoot);
二叉树的双旋
单旋不能满足所有
变化分支不可是唯一的变化分支
如果是 要先进行反向旋转
二叉树的双旋(左右双旋,右左双旋)
右左双旋:当要对某个节点进行左单旋,如果变化分支是唯一的最深分支,那么要对新根进行右单旋,然后左单旋
反之左右双旋
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var node8 = new Node("8");
var node7 = new Node("7");
var node6 = new Node("6");
var node5 = new Node("5");
var node2 = new Node("2");
node8.left = node7;
node7.left = node6;
node6.left = node5;
node5.left = node2;
function getDeep(root) {
if (root == null) return 0;
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
return Math.max(leftDeep, rightDeep) + 1;
}
function isBalance(root) {
if (root == null) return true;
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
if (Math.abs(leftDeep - rightDeep) > 1) {
//不平衡
return false;
} else {
return isBalance(root.left) && isBalance(root.right);
}
}
function leftRotate(root) {
// 找到新根
var newRoot = root.right;
// 找到变化分支
var changeTree = root.right.left;
// 当前旋转节点的右孩子为变化分支
root.right = changeTree;
// 新根的左孩子为旋转节点
newRoot.left = root;
// 返回新的根节点
return newRoot;
}
function rightRotate(root) {
// 找到新根
var newRoot = root.left;
// 找到变化分支
var changeTree = root.left.right;
// 当前旋转节点的左孩子为变化分支
root.left = changeTree;
// 新根的右孩子为旋转节点
newRoot.right = root;
// 返回新的根节点
return newRoot;
}
function change(root) {
//返回平衡之后的根节点
if (isBalance(root)) return root;
if (root.left != null) root.left = change(root.left);
if (root.right != null) root.right = change(root.right);
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
if (Math.abs(leftDeep - rightDeep) < 2) {
return root;
} else if (leftDeep > rightDeep) {
//不平衡,左边深,需要右旋
var changeTreeDeep = getDeep(root.left.right);
var noChangeTreeDeep = getDeep(root.left.left);
if (changeTreeDeep > noChangeTreeDeep) {
//变化树比较深
root.left = leftRotate(root.left); //左子树进行左单旋
}
return rightRotate(root);
} else {
//不平衡,右边深,需要左旋
var changeTreeDeep = getDeep(root.right.left);
var noChangeTreeDeep = getDeep(root.right.right);
if (changeTreeDeep > noChangeTreeDeep) {
root.right = rightRotate(root.right);
}
return leftRotate(root);
}
return root;
}
function addNode(root, num) {
if (root == null) return;
if (root.value == num) return;
if (root.value < num) {
//目标值比当前节点大
if (root.right == null) root.right = new Node(num);
//如果右侧为空,则创建节点
else addNode(root.right, num); //如果右侧不为空,则向右侧进行递归
} else {
//目标值比当前节点小
if (root.left == null) root.left = new Node(num);
else addNode(root.left, num);
}
}
function buildSearchTree(arr) {
if (arr == null || arr.length == 0) return null;
var root = new Node(arr[0]);
for (var i = 1; i < arr.length; i++) {
addNode(root, arr[i]);
}
return root;
}
var num2 = 0;
function searchByTree(root, target) {
if (root == null) return false;
num2 += 1;
if (root.value == target) return true;
if (root.value > target) return searchByTree(root.left, target);
else return searchByTree(root.right, target);
}
var arr = [];
for (var i = 0; i < 10000; i++) {
arr.push(Math.floor(Math.random() * 10000));
}
var root = buildSearchTree(arr);
// console.log(searchByTree(root, 1000));
// console.log(num2);
var newRoot = change(root);
// num2 = 0;
// console.log(searchByTree(newRoot, 1000));
// console.log(num2);
console.log(isBalance(newRoot));
// console.log(isBalance(node8));
//
// var newRoot = change(node8);
//
// console.log(isBalance(newRoot));//目前还是不能平衡,因为还有特殊情况 需要左左双旋,右右双旋
// console.log(newRoot);
左 左 双 旋 与 右 右 双 旋
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
var node8 = new Node("8");
var node7 = new Node("7");
var node6 = new Node("6");
var node5 = new Node("5");
var node2 = new Node("2");
node8.left = node7;
node7.left = node6;
node6.left = node5;
node5.left = node2;
function getDeep(root) {
if (root == null) return 0;
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
return Math.max(leftDeep, rightDeep) + 1;
}
function isBalance(root) {
if (root == null) return true;
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
if (Math.abs(leftDeep - rightDeep) > 1) {
//不平衡
return false;
} else {
return isBalance(root.left) && isBalance(root.right);
}
}
function leftRotate(root) {
// 找到新根
var newRoot = root.right;
// 找到变化分支
var changeTree = root.right.left;
// 当前旋转节点的右孩子为变化分支
root.right = changeTree;
// 新根的左孩子为旋转节点
newRoot.left = root;
// 返回新的根节点
return newRoot;
}
function rightRotate(root) {
// 找到新根
var newRoot = root.left;
// 找到变化分支
var changeTree = root.left.right;
// 当前旋转节点的左孩子为变化分支
root.left = changeTree;
// 新根的右孩子为旋转节点
newRoot.right = root;
// 返回新的根节点
return newRoot;
}
function change(root) {
//返回平衡之后的根节点
if (isBalance(root)) return root;
if (root.left != null) root.left = change(root.left);
if (root.right != null) root.right = change(root.right);
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
if (Math.abs(leftDeep - rightDeep) < 2) {
return root;
} else if (leftDeep > rightDeep) {
//不平衡,左边深,需要右旋
var changeTreeDeep = getDeep(root.left.right);
var noChangeTreeDeep = getDeep(root.left.left);
if (changeTreeDeep > noChangeTreeDeep) {
root.left = leftRotate(root.left);
}
var newRoot = rightRotate(root);
newRoot.right = change(newRoot.right); //每次右旋了之后有可能再次右旋。再来一遍,右侧分支进行再次平衡,右侧分支的高度变了,所以要对自己再做一次平衡,
newRoot = change(newRoot); //对自己再做一次平衡
return newRoot;
} else {
//不平衡,右边深,需要左旋
var changeTreeDeep = getDeep(root.right.left);
var noChangeTreeDeep = getDeep(root.right.right);
if (changeTreeDeep > noChangeTreeDeep) {
root.right = rightRotate(root.right);
}
var newRoot = leftRotate(root);
newRoot.left = change(newRoot.left);
newRoot = change(newRoot);
return newRoot;
}
return root;
}
function addNode(root, num) {
if (root == null) return;
if (root.value == num) return;
if (root.value < num) {
//目标值比当前节点大
if (root.right == null) root.right = new Node(num);
//如果右侧为空,则创建节点
else addNode(root.right, num); //如果右侧不为空,则向右侧进行递归
} else {
//目标值比当前节点小
if (root.left == null) root.left = new Node(num);
else addNode(root.left, num);
}
}
function buildSearchTree(arr) {
if (arr == null || arr.length == 0) return null;
var root = new Node(arr[0]);
for (var i = 1; i < arr.length; i++) {
addNode(root, arr[i]);
}
return root;
}
var num2 = 0;
function searchByTree(root, target) {
if (root == null) return false;
num2 += 1;
if (root.value == target) return true;
if (root.value > target) return searchByTree(root.left, target);
else return searchByTree(root.right, target);
}
var arr = [];
for (var i = 0; i < 10000; i++) {
arr.push(Math.floor(Math.random() * 10000));
}
var root = buildSearchTree(arr);
console.log(searchByTree(root, 1000));
console.log(num2);
var newRoot = change(root);
num2 = 0;
console.log(searchByTree(newRoot, 1000));
console.log(num2);
console.log(isBalance(newRoot));
// console.log(isBalance(node8));
//
// var newRoot = change(node8);
//
// console.log(isBalance(newRoot));
// console.log(newRoot);
进行了一系列操作后,性能还未达到极致,需要再次探索。
234 树的由来
树的深度优先搜索
function Node(value) {
this.value = value;
this.childs = [];
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
a.childs.push(c);
a.childs.push(f);
a.childs.push(b);
b.childs.push(d);
b.childs.push(e);
function deepSearch(root, target) {
if (root == null) return false;
if (root.value == target) return true;
var result = false;
for (var i = 0; i < root.childs.length; i++) {
result |= deepSearch(root.childs[i], target);
}
return result ? true : false;
}
console.log(deepSearch(a, "n"));
树的广度优先搜索
function Node(value) {
this.value = value;
this.childs = [];
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
var f = new Node("f");
a.childs.push(c);
a.childs.push(f);
a.childs.push(b);
b.childs.push(d);
b.childs.push(e);
function bfs(roots, target) {
if (roots == null || roots.length == 0) return false;
var childs = [];
for (var i = 0; i < roots.length; i++) {
if (roots[i].value == target) {
return true;
} else {
childs = childs.concat(roots[i].childs);
}
}
return bfs(childs, target);
}
console.log(bfs([a], "n"));
图的深度优先
不要形成环,防止死循环—-找过的点就不找了
function Node(value) {
this.value = value;
this.neighbor = [];
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
a.neighbor.push(b);
a.neighbor.push(c);
b.neighbor.push(a);
b.neighbor.push(c);
b.neighbor.push(d);
c.neighbor.push(a);
c.neighbor.push(b);
c.neighbor.push(d);
d.neighbor.push(b);
d.neighbor.push(c);
d.neighbor.push(e);
e.neighbor.push(d);
function deepSearch(node, target, path) {
if (node == null) return false;
if (path.indexOf(node) > -1) return false; //这个点找过了
if (node.value == target) return true;
path.push(node);
var result = false;
for (var i = 0; i < node.neighbor.length; i++) {
result |= deepSearch(node.neighbor[i], target, path); //一真为真
}
return result ? true : false;
}
console.log(deepSearch(b, "d", [])); //b节点开始,找d是不是在里面,返回找过的路径
图的广度优先
function Node(value) {
this.value = value;
this.neighbor = [];
}
var a = new Node("a");
var b = new Node("b");
var c = new Node("c");
var d = new Node("d");
var e = new Node("e");
a.neighbor.push(b);
a.neighbor.push(c);
b.neighbor.push(a);
b.neighbor.push(c);
b.neighbor.push(d);
c.neighbor.push(a);
c.neighbor.push(b);
c.neighbor.push(d);
d.neighbor.push(b);
d.neighbor.push(c);
d.neighbor.push(e);
e.neighbor.push(d);
function bfs(nodes, target, path) {
if (nodes == null || nodes.length == 0) return false;
var nextNodes = [];
for (var i = 0; i < nodes.length; i++) {
if (path.indexOf(nodes[i]) > -1) continue; //存在的话看下一个
path.push(nodes[i]); //代表填过了 不存在就push进去
if (nodes[i].value == target) return true;
else nextNodes = nextNodes.concat(nodes[i].neighbor); //把它的邻居全添加到下一步要走的路里
}
return bfs(nextNodes, target, path);
}
console.log(bfs([c], "n", []));
动态规划-斐波那契数列
// 动态规划,笔试遇到动态规划是后面的大题
// 0, 1, 1, 2, 3, 5, 8, 13, 21 ……
// 给出第n位,问第n位值为几?
function fibo(n) {
if (n <= 0) return -1;
if (n == 1) return 0;
if (n == 2) return 1;
var a = 0;
var b = 1;
var c = 0;
for (var i = 3; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return c;
}
// f(n) = f(n-1) + f(n-2);
function fibo2(n) {
if (n <= 0) return -1;
if (n == 1) return 0;
if (n == 2) return 1;
return fibo2(n - 1) + fibo2(n - 2);
}
console.log(fibo2(7));
动态规划-青蛙跳台阶
// 一个青蛙,一次只能跳一级台阶,或者跳两级台阶。
// 问:这个青蛙跳上n级台阶有多少种跳法。
// 如果这只青蛙,跳上了第n级台阶,那么最后一次跳跃之前,他一定在n-1级台阶或n-2级台阶上。
// 那么跳上n级台阶有多少种情况就变成了两个子问题
// 跳上n-1级台阶的跳法 加上 跳上n-2级台阶的跳法。
// 按照此逻辑递推,跳上n-1级台阶可以拆解为两个子问题
// 既:跳上n-2级台阶的跳法 加上 跳上n-3级台阶的跳法
// f(n) = f(n-1) + f(n-2)
function jump(n) {
if (n <= 0) return -1; //0级
if (n == 1) return 1; //一级 一种
if (n == 2) return 2; //两级 两种
return jump(n - 1) + jump(n - 2);
}
牛客网多练
变态青蛙跳台阶
//
// 这只青蛙,一次可以跳1级台阶、2级台阶、或n级台阶。
// 问:这只青蛙,跳上n级台阶有多少种方法?
// f(n) = f(n-1) + f(n-2) + f(n-3) + …… + f(2) + f(1) + f(0)
function jump(n) {
if (n <= 0) return -1;
if (n == 1) return 1;
if (n == 2) return 2;
var result = 0;
for (var i = 1; i < n; i++) {
result += jump(n - i);
}
return result + 1; // +1代表从0级台阶直接跳上去的情况
}
/*
* 1,1,1,1,
* 1,1,2
* 1,2,1,
* 2,1,1,
* 1,3,
* 3,1,
* 2,2,
* 4
* */
console.log(jump(4));
深入算法
《算法导论》