前端必会算法(原理及代码实现)
推荐书籍:算法导论
一维数据结构:线性数据结构(数组,链表),线性的数据结构强调存储与顺序
线性数据结构之数组
1.存储在物理空间上是连续的,这个与操作系统的内存分配有关
2.底层的数据长度是不可变化的。当长度发生变化时,数组需要扩容,这个会消耗内存(过程中涉及新数据的创建和数组数据的复制,注意,数组的创建会多扩充几位,防止经常扩容,如8位数据扩充为16位数组);在js语言中会发现数组长度是可以变化的,是因为js引擎内部做了优化,帮用户做了扩容。
3.数组的变量,指向了数组第一个元素的位置
4.数组a[1]中的方括号表示存储地址的偏移。操作系统小知识:通过偏移查询数据性能最好。
优点:
1.查询性能好,可以指定查询某个位置
缺点:
1.因为空间必须是连续的,所以如果数组比较大,当系统的空间碎片较多的时候,容易存储不下。 空间碎片较时,连续的内存空间可能就没有了,导致明明空间足够,而大数组却存储不下;
2.因为数组的长度是固定的,所以数组的内容难以被添加和删除。 ---添加的代价(可能扩容和复制);删除的代价(数据往前移动)
线性数据结构之链表(默认单链表,还有一些如双链表等)
链表特性:
1.空间上不是连续的。
2、每存放一个值,都有开销一个引用空间。
3.传递一个链表时,必须传递链表的根节点。(每一个节点都认为自己是根节点。)
优点:
1.只要内存足够大,就能存的下,不用担心空间碎片的问题
2.链表的添加和删除非常容易
缺点:
1.查询速度慢(指的查询某个位置)
2.链表每一个节点都需要创建一个指向next的引用,浪费空间,但是当节点内的数据越多的时候,这部分多开销的内存影响越少
定义链表:function Node(value){this.value=value;this.next=null}
定义双向链表链表:function Node(value){this.value=value;this.next=null}
线性数据结构的遍历
数组遍历略
链表遍历(循环遍历和递归遍历)
function bianLink(root){
var temp = root;
while(true){
if(temp != null){
console.log(temp.value);
}else{
break;
}
temp = temp.next;
}
}
递归遍历,必须要有出口
function bianLink(root){
if(root == null) return;
console.log(root.value);
bianLink(root.next);
}
链表的逆置(算法中最简单的题目,算法基础)
突破口:找到倒数第二个节点(并非找到最后一个节点,因为每一个节点都认为自己是根节点,无法找到倒数第二个节点了,因此不能进行倒置)
function nizhi(root){
if(root.next == null){
return root; //函数的出口
}else{
return nizhi(root.next);
}
}
虽然找到了最后一个,但是没有用,找不到倒数第二个节点了,因此要找到倒数第二个节点root.next.next
function nizhi(root){ //参数为初始节点
if(root.next.next == null){ //表示当前的节点是倒数第二个节点
root.next.next = root; //让最后一个节点指向自己(倒数第二个节点)
return root.next; //返回最后一个节点
}else{ //如果不是倒数第二个
let result = nizhi(root.next); //先拿到当前结果,进行逆置,最后放回结果
root.next.next = root; //将自己的下一个节点指向自己
root.next = null; //将自己的next指向null,放在来回连接;主要是将第一个的节点next指向null
return result;
}
}
例子:
let newRoot = nizhi(node1);
bianlink(newRoot);
七种常见的数组排序方法
数组排序
冒泡排序(数组排序)
1利用原生数组中的sort方法(冒泡原理)
arr.sort((a,b)=>{return a-b}) //升序排序
//性能一般,复杂度为n*(n-1)
var arr=[1,5,7,9,16,2,4];
//冒泡排序,每一趟找出最大的,总共比较次数为arr.length-1次,每次的比较次数为arr.length-1次,依次递减
var temp;
for(var i=0;i<arr.length-1;i++){
for(var j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
选择排序(假定某个位置的值是最小值)
//性能一般
var arr=[1,23,5,8,11,78,45];
var temp;
for(var i=0;i<arr.length-1;i++){
for(var j=i+1;j<arr.length;j++){
if(arr[i]>arr[j]){ //默认选择第一项
temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
}
快速排序(一拆为二) ---简单快排
快排的算法思路:一分为二,递归
//会创建很多的数组
function quickSort(arr){
if(arr.length <= 1){ //算法严谨性,性能优化,小于1也是可以的,递归出口
return arr;
}
var left = [];
var right = [];
var midIndex = parseInt(arr.length / 2); //数组一分为二,默认中间的为leader,大家和它比
var mid = arr[midIndex];
for(var i = 0 ; i < arr.length ; i++){
if(i == midIndex) continue;
if( arr[i] < mid){
left.push(arr[i])
}else{
right.push(arr[i]);
}
}
return quickSort(left).concat([mid],quickSort(right)); //递归调用
}
快速排序(一拆为二) ---标准快排(begin,end通常都是指左闭右开区间)
快排的算法思路:一分为二,递归
一、默认比较值为数组最后一位,左右扫描,找到左边第一个大于k的,找到右边第一个小于k的,两者交换
function quicksort(arr,low, high) {
if (low >= high) { //出口
return;
}
int k = arr[high];
int left = low;
int right = high-1;
while (left < right) {
while (arr[left] < k && left < right) {
++left;
}
while (arr[right] >= k && left < right) {
--right;
}
swap(arr,left, right); //交换
}
if (arr[left] < k) {
++left;
}
swap(arr,left, high);
quicksort(arr, low, left-1);
quicksort(arr, left+1, high);
}
辅助函数:swap
function swap(arr,a,b){
var temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
注意:
1. left左边(不包括left)为<key的数字,right右边(不包括right)为>=key的数字
2. 当退出循环时,left = right, 此时无法确定arr[left]和k谁大,需进行判定。如果arr[left] < k, 则partition位置为left+1, 否则为left
3. 为了避免partition位置位于左边界或右边界,即mid == low || mid == high, 在函数入口处需要对low和high进行校验
二、左中扫描
int partition(int arr[], int low, int high) {
if (low >= high) {
return;
}
int k = arr[high];
int left = low-1;
int mid = low;
for (; mid <= high-1; ++mid) {
if (arr[mid] <= k) {
left++;
std::swap(arr[left], arr[mid]);
}
}
std::swap(arr[left+1], arr[mid]);
return left+1;
}
void quicksort(int arr[], int low, int high) {
if (low >= high) {
return;
}
int mid = partition(arr, low, high);
partition(arr, low, mid-1);
partition(arr, mid+1, high);
}
注意:
1. left左边(包括low)为<=k的数字, 所以partition位置为left+1
其他同上
其他较不常用算法
1.插入排序法:将要排序的数组分成两部分,每次从后面的部分取出索引最小的元素插入到前一部分的适当位置
var arr=[45,1,32,21,56,87,43,12,34,45];
for(var i=0;i<arr.length;i++){
var n=i;
while(arr[n]>arr[n+1] && n>=0){
var temp=arr[n];
arr[n]=arr[n+1];
arr[n+1]=temp;
n--;
}
}
-
希尔排序(性能最好的排序)
function xier(arr){ var interval = parseInt(arr.length / 2); //分组间隔设置 while(interval > 0){ for(var i = 0 ; i < arr.length ; i ++){ var n = i; while(arr[n] < arr[n - interval] && n > 0){ var temp = arr[n]; arr[n] = arr[n - interval]; arr[n - interval] = temp; n = n - interval; } } interval = parseInt(interval / 2); } return arr; } xier([12,9,38,44,7,98,35,59,49,88,38]);
栈和队列
数组封装一个栈结构
function Stack(){
this.arr = [];
this.push = function(value){
this.arr.push(value);
}
this.pop= function(){
return this.arr.pop(); 改为队列结构,this.arr.shift()
}
}
var stack = new Stack()
var queue = new Queue()
二维数据结构
二维数组,树形结构
二维数组略
树形结构
树形结构有一个根节点,树形结构没有回路,是有向无环图,树是图的一种
关于树的概念:
1.有一个根节点
2.叶子节点:下边没有其他节点了
3.节点:既不是根节点,又不是叶子节点
4.树的度:这棵树有最多分叉的节点有多少个分叉,这棵树的度就为多少
5.树的深度:树最深有几层,树的深度就为多少。
6.子节点:某个节点下的节点
7.父节点:上级节点
二叉树
树的度最多为2的树形结构
二叉树之满二叉树
概念:(1)所有的叶子节点都在最底层
(2)每一个节点(非叶子节点)都有两个子节点
二叉树之完全二叉树
国内概念:(1)叶子节点都在最后一层或者倒数第二层
(2)如果有叶子节点,叶子节点都向左聚拢
国际概念:(1)叶子节点都在最后一层或者倒数第二层
(2)如果有叶子节点,就必然有两个叶子节点
二叉树中,子树的概念
概念:1.二叉树中,每一个节点或者叶子节点,都是一颗子树的根节点
2.左子树、右子树
二叉树的遍历(几乎校招每年的必考题)
考点:
-
给出二叉树,写成前序中序后序遍历
-
写成前序中序后序遍历的代码
-
给出前序中序还原二叉树,要求写出后序遍历(中序一定要有)
-
给出后序中序还原二叉树,要求写出前序遍历(中序一定要有)
-
代码实现前序中序还原二叉树
-
代码实现中序后序还原二叉树
传递二叉树要传递根节点。
前序遍历(先根次序遍历):先打印当前的(中间的),再打印左边的,最后打印右边的
中序遍历(中根次序遍历):先打印左边的,再打印当前的,最后打印右边的(中序遍历其实就是从左到右进行投影)
后序遍历(后根次序遍历):先打印左边的,再打印右边的,最后打印当前的
打印顺序:前序遍历根节点在最左边,中序遍历根节点在中间,后序遍历根节点在最右边
代码实现前中后序
节点创建
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(root);
中序遍历
function f1(root){
if(root == null) return;
f1(root.left);
console.log(root.value);
f1(root.right);
}
f1(root);
后序遍历
function f1(root){
if(root == null) return;
f1(root.left);
f1(root.right);
console.log(root.value);
}
f1(root);
根据前序中序还原二叉树
前序遍历:ACFGBDE
中序遍历:FCGADBE
得出后序遍历:FGCDEBA
根据后序中序还原二叉树
中序遍历:FCGADBE
后序遍历:FGCDEBA
得出前序遍历: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);//找到根节点在中序遍历中的位置
var qianLeft = qian.silce(1,1+index); //前序遍历的左子树
var qianRight = qian.silce(index+1,qian.length); //前序遍历的右子树
var zhongLeft = zhong.silce(0,index); //中序遍历的左子树
var zhongRight = zhong.silce(index+1,zhong.length);//中序遍历的右子树
root.left = f1(qianLeft,zhongLeft); //根据左子树的前序和中序还原左子树并赋值给root.left
root.right = f1(qianRight,zhongRight);//根据右子树的前序和中序还原右子树并赋值给root.right
return root;
}
var root = f1(qian,zhong);
代码实现中序后序还原二叉树
不能默认为就是满二叉树,要得出普通二叉树,必须知道中序以及前序(或者后序)
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]); //创建根节点|第二次就为节点
var index = zhong.indexOf(root.value);//找到根节点在中序遍历中的位置
var zhongLeft = zhong.silce(0,index); //中序遍历的左子树
var zhongRight = zhong.silce(index+1,zhong.length);//中序遍历的右子树
var houLeft = hou.silce(0,index); //后序遍历的左子树
var houRight = hou.silce(index,hou.length-1); //后序遍历的右子树
root.left = f1(zhongLeft,houLeft); //根据左子树的中序和后序还原左子树并赋值给root.left
root.right = f1(zhongRight,houRight);//根据右子树的中序和后序还原右子树并赋值给root.right
return root;
}
var root = f1(zhong,hou);
二叉树的深度搜索和广度搜索(重点)
深度优先搜索:更适合探索未知
广度优先搜索:更适合探索局域
代码实现二叉树的深度优先搜索
节点创建
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.value){
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++) { //rootList[i]为null的时候怎么处理
// console.log(rootList[i], `第${i}遍`);
if (rootList[i] != null && rootList[i].value == target) {
return true;
} else {
//rootList[i]的左右节点为null的时候怎么处理,若为空,当为空时不处理。即不push
if (rootList[i].left != null) {
childList.push(rootList[i].left);
}
if (rootList[i].right != null) {
childList.push(rootList[i].right);
}
}
}
// console.log(childList);
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) {
// root1和root2都为空算相等吗??
if (root1 == root2) { //两个树对应同一个地址
return true;
}
if ((root1 == null && root2 != null) || (root2 == null && root1 != null)) { //其中一个为空,另一个不为空
return false;
}
if (root1.value != root2.value) { //相同位置的值不相等
return false;
} else { //当前值相等
var leftBool = compareTree(root1.left, root2.left); //判断左子树是否相等
var rightBool = compareTree(root1.right, root2.right); //判断右子树是否相等
return leftBool && rightBool; //必须左右子树都相等才算相等
}
}
console.log(compareTree(a1, a2));
注意:遇到二叉树比较的问题时,必须要确定,左右两个子树在交换位置后,是否还算一颗二叉树。
如果是笔试,默认只有呼唤后不是同一棵树;如果是面试,可以问一下。
二叉树左右子树允许互换的比较
//左右子树可以交换的情况
function compareTree(root1, root2) {
// root1和root2都为空算相等吗??
if (root1 == root2) { //两个树对应同一个地址
return true;
}
if ((root1 == null && root2 != null) || (root2 == null && root1 != null)) { //其中一个为空,另一个不为空
return false;
}
if (root1.value != root2.value) { //相同位置的值不相等
return false;
} else { //当前值相等
return compareTree(root1.left, root2.left) && compareTree(root1.right, root2.right) || compareTree(root1.left, root2.right) && compareTree(root1.right, root2.left)
}
}
二叉树的diff算法
两棵树的比较:新增,修改,删除什么?
最小生成树(一个图生成最小生成树)
表示一个图,可以使用点集合和边集合
点集合:[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 | max | 7 | 0 |
比如:需要将所有的村庄联通,但需要花费最少的钱?
普利姆算法(加点法)
-
任选一个点作为起点
-
找到以当前选中点为起点路径最短的边
-
如果这个边的另一端没有被联通进来,那么就连结
-
如果这个边的另一端也早就被连进来了,则看倒数第二短的边
-
重复2-4步骤直到将所有的点都联通为止。
普利姆算法代码实现
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 getIndex(str) {
for (let i = 0; i < pointSet.length; i++) {
if (pointSet[i].value == str) return i;
}
return -1;
}
// 需要传入点的集合,边的集合,当前已经连接进入的集合
// 此方法,根据当前已经有的节点,来进行判断,获取距离最短的点
function getMinDistance(pointSet, distance, nowPointSet) {
var formNode = null; //线段的起点
var minDisNode = null; //线段的终点
var minDis = max;
// 根据目前已有的这些点为起点,判断连接其他的点的距离是多少
for (let i = 0; i < nowPointSet.length; i++) {
var nowPointIndex = getIndex(nowPointSet[i].value); //获取当前节点的序号
for (let j = 0; j < distance[nowPointIndex].length; j++) { //遍历这个序号对于的行
var thisNode = pointSet[j]; //thisNode表示distance中的点
if (nowPointSet.indexOf(thisNode) < 0 //首先这个点不能是已经接入的点
&&
distance[nowPointIndex][j] < minDis) { //其次点之间的距离得是目前的最短距离
formNode = nowPointSet[i]; //起点
minDisNode = thisNode; //终点
minDis = distance[nowPointIndex][j]; //跟新最短距离
}
}
}
formNode.neighbor.push(minDisNode); //起点的neighbor是终点
minDisNode.neighbor.push(formNode);
return minDisNode;
}
function prim(pointSet, distance, start) { //普利姆算法(加点法)
var nowPointSet = [];
nowPointSet.push(start);
// 获取最小代价的边
while (true) {
var minDisNode = getMinDistance(pointSet, distance, nowPointSet)
nowPointSet.push(minDisNode);
if (nowPointSet.length == pointSet.length) { //结束条件
break;
}
}
}
prim(pointSet, distance, pointSet[2])
console.log(pointSet)
克鲁斯卡尔算法(加边法)
-
选择最短的边进行连接
-
要保证边与边连结的两端至少有一个点是新的点
-
或者这个边是将两个部落进行连结的
-
重复1-3直到将所有的点都连结到一起
克鲁斯卡尔算法代码实现(难)
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) { //canLink方法判断是否可以连接的方法 :先判断两个点是否为新的点,如果不是,判断是否为两个部落中的点
var beginIn = null; //判断tempBegin是否在里面
var endIn = null; //判断tempEnd是否在里面
for (let i = 0; i < resultList.length; i++) {
if (resultList[i].indexOf(tempBegin) > -1) { //tempBegin在resultList里面
beginIn = resultList[i];
}
if (resultList[i].indexOf(tempEnd) > -1) { //tempEnd在resultList里面
endIn = resultList[i];
}
}
// 下面是不可以连接的情况
// begin和end在同一个部落 -----不可以连接
if (beginIn != null && endIn != null && beginIn == endIn) {
return false
} else {
// 下面都是可以连接的情况(仅仅是判断能否连接的话,可以合并判断)
// 两个点都是新的点(都不在任何部落)---产生新的部落,可以连接
// begin在A部落,end没有部落 ---- A部落扩张一个节点
// end在A部落,begin没有部落 ----- A部落扩张一个节点
// begin在A部落,end在B部落 ------将AB两个部落合并
return true
}
}
function link(resultList, tempBegin, tempEnd) {
var beginIn = null; //判断tempBegin是否在里面
var endIn = null; //判断tempEnd是否在里面
for (let i = 0; i < resultList.length; i++) {
if (resultList[i].indexOf(tempBegin) > -1) { //tempBegin在resultList里面
beginIn = resultList[i];
}
if (resultList[i].indexOf(tempEnd) > -1) { //tempEnd在resultList里面
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部落扩张一个节点
end.push(tempBegin);
} else if (beginIn != null && endIn != null && beginIn != endIn) { // begin在A部落,end在B部落 ------将AB两个部落合并
var allIn = beginIn.concat(endIn);
var needRemove1 = resultList.indexOf(endIn);
resultList.splice(needRemove1, 1);
var needRemove2 = resultList.indexOf(beginIn);
resultList.splice(needRemove2, 1);
resultList.push(allIn);
}
// 上一个函数判断可以连接
tempBegin.neighbor.push(tempEnd);
tempEnd.neighbor.push(tempBegin);
}
function kruskal(pointSet, distance) {
var resultList = []; //这里是二位数组,此数组代表的是有多少个“部落”
while (true) { //知道次数的用for,不知道次数的用while循环
var minDis = max; //定义最短距离,默认max
var begin = null; //定义起点
var end = null; //定义终点
for (let i = 0; i < distance.length; i++) {
for (let 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) //canLink方法判断是否可以连接
) {
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 (let i = 0; i < 10; i++) {
arr[i] = Math.floor(Math.random() * 10)
}
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
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) return null;
var root = new Node(arr[0]);
for (let i = 1; i < arr.length; i++) {
addNode(root, arr[i])
}
return root;
}
var root = buildSearchTree(arr); //得到二叉搜索树
console.log(root);
二叉搜索树的使用
// 二叉搜索树查找某一个数
function searchByTree(root, target) {
if (root == null) return false;
if(root.value == target ) return true;
if(root.value > target){
return searchByTree(root.left,target);
}else{
return searchByTree(root.right,target);
}
}
console.log(searchByTree(root,1000));
查找一个数,数组于平衡二叉树的性能比较(即比较次数)
// 比较普通数组和二叉搜索树,搜索一个数是否存在的性能
//比较次数差很多
//数的搜索性能是由树的深度来决定的,想要提升性能,可以使用平衡二叉搜索树(树的深度尽量浅)
var arr = [];
for (let i = 0; i < 10000; i++) {
arr[i] = Math.floor(Math.random() * 10000)
}
var num1 = 0;
function search(arr, target) {
for (let i = 0; i < arr.length; i++) {
num1 += 1;
if (arr[i] == target) return true;
}
return false;
}
console.log(search(arr, 1000));
console.log(num1);
function Node(value) {
this.value = value;
this.left = null;
this.right = null;
}
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);
}
}
}
var num2 = 0;
function buildSearchTree(arr) {
if (arr == null) return null;
var root = new Node(arr[0]);
for (let i = 1; i < arr.length; i++) {
addNode(root, arr[i])
}
return root;
}
var root = buildSearchTree(arr); //得到二叉搜索树
// 二叉搜索树查找某一个数
function searchByTree(root, target) {
num2++
if (root == null) return false;
if(root.value == target ) return true;
if(root.value > target){
return searchByTree(root.left,target);
}else{
return searchByTree(root.right,target);
}
}
console.log(searchByTree(root,1000));
console.log(num2);
平衡二叉树
-
根节点的左子树与右子树的高度差不能超过1
-
这棵二叉树的每个子树都符合第一条
代码实现判断平衡二叉树
root是构建好的平衡二叉树
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");
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(a))
二叉树的单旋
二叉树双旋
二叉树双旋的代码实现
左左双旋和右右双旋
234树的由来
红黑树
普通树
普通树(一个节点有多个子节点,所以应该是childs)
// 节点创建
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.chidls.push(c);
a.chidls.push(b);
a.chidls.push(f);
b.chidls.push(d);
b.chidls.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;
}
deepSearch(a,"c");
树的广度优先搜索
function bfs(roots,target) { //roots接受一个数组
if(rootss =null || roots.length == 0 ) return false;
var childs = [];
for(var i = 0;i<roots.length ;i++){
if(roots[i].value ==target){
return true;
}else{
chidls = childs.concat(roots[i].childs);
}
}
return bfs(chidls,target);
}
构建一个图
// 节点创建
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 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 (let i = 0; i < node.neighbor.length; i++) {
result |= deepSearch(node.neighbor[i], target, path);
}
return result ? true : false;
}
console.log(deepSearch(b, "c", []));
图的广度优先搜索
// 节点创建
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 (let i = 0; i < nodes.length; i++) {
if (path.indexOf(nodes[i]) > -1) {
continue;
}
path.push(nodes[i]);
if (nodes[i].value == target) {
return true;
}else{
nextNodes = nextNodes.concat(nodes[i].neighbor);
}
return bfs(nextNodes,target,path);
}
}
console.log(bfs([c], "b", []));
动态规划
动态规划,笔试遇到动态规划是后面的大题,校招必考题
通常给出如下题:-----斐波那契数列
动态规划之斐波那契数列
// 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;
for (let 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 == 0) return 1;
return fibo2(n-1) +fibo2(n-2);
}
console.log(fibo(4));
动态规划之青蛙跳台阶问题(笔试中最常见的题)
//一个青蛙,一次只能跳一级台阶,或者跳两级台阶
// 问:这个青蛙跳上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;
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 (let i = 1; i < n; i++) {
result += jump(n - i); //最少中一级台阶开始跳,避免死循环
}
return result + 1; // +1表示从0级台阶直接跳上去的情况。
}
// n为3时有哪些情况
// 1,1,1
// 1.2
// 2,1
// 2
// n为4时有哪些情况
// 1,1,1,1
// 1,1,2
// 1,2,1
// 2,1,1
// 1,3
// 3,1
// 2,2
// 4
console.log(jump(4))
如果算法导论都没有问题,看看《算法导论》

浙公网安备 33010602011771号