浅谈前端常用数据结构和算法(二)

二叉搜索树(二叉排序树)

首先这是一颗二叉树,其次有排序效果,左子树的节点都比当前节点小,右子树的节点都比当前节点大。

问题:有一万个数,写一个方法进行查找。查找给定的数,返回存在或不存在。
要求:尽可能性能好。

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.left == null) root.left = new Node(num);//如果左侧为空,则创建
        else addNode(root.left, num);//如果左侧不为空,则向左侧进行递归
    }else{
        if(root.right == null) root.right = new Node(num);
        else addNode(root.right, num);
    }
}

function buildSearchTree(arr){ //构建二叉搜索树
    if(arr == null || arr.length == 0) return null;
    let root = new Node(arr[0]);
    for(let i = 0; i < arr.length; i++){
        addNode(root, arr[i]);
    }
    return 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);
}

var arr = (x=>Array(x).join(',').split(',').map(()=>Math.floor(Math.random()*x)))(10000);
var root =buildSearchTree(arr);
console.log(root);
console.log(searchByTree(root, 1000));
二叉平衡搜索树(平衡二叉树)
  1. 根节点左子树与右子树的高度差不能超过1
  2. 这颗二叉树的每个子树都符合第一条
function Node(value){
    this.value = value;
    this.left = null;
    this.right = null;
}

function getDeep(root){
    if(root == null) return 0;
    let leftDeep = getDeep(root.left),
        rightDeep = getDeep(root.right);
    return Math.max(leftDeep, rightDeep) + 1;
}

function isBalance(root){
    if(root == null) return true;
    let leftDeep = getDeep(root.left),
        rightDeep = getDeep(root.right);
    if(Math.abs(leftDeep - rightDeep) > 1) return false;
    else return isBalance(root.left) && isBalance(root.right);
}

var a = new Node('a'),
    b = new Node('b'),
    c = new Node('c'),
    d = new Node('d'),
    e = new Node('e'),
    f = new Node('f'),
    g = new Node('g'),
    h = new Node('h'),
    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;

console.log(isBalance(a));
二叉树的单旋

二叉树的单旋操作(左单旋、右单旋)

某一节点不平衡,如果左边浅,右边深,进行左单旋。

【注:以下名词尽作理解,非专有名词】
旋转节点: 不平衡的节点
新根: 旋转之后的根节点
变化分支: 父级节点发生变化的分支
不变分支: 父级节点不变的分支

左单旋时:
旋转节点:当前不平衡的节点
新根:右子树的根节点
变化分支:旋转节点的右子树的左子树
不变分支:旋转节点的右子树的右子树

左单旋步骤:

  1. 找到新根
  2. 找到变化分支
  3. 当前旋转节点的右节点为变化分支
  4. 新根的左节点为旋转节点
  5. 返回新的根节点
function Node(value){
    this.value = value;
    this.left = null;
    this.right = null;
}

function leftRotate(root){
    // 1. 找到新根
    let newRoot = root.right;
    // 2. 找到变化分支
    let changeTree = root.right.left;
    // 3. 当前旋转节点的右节点为变化分支
    root.right = changeTree;
    // 4. 新根的左节点为旋转节点
    newRoot.left = root;
    // 5. 返回新的根节点
    return newRoot;
}
function rightRotate(root){
    let newRoot = root.left;
    let changeTree = root.left.right;
    root.left = changeTree;
    newRoot.right = root;
    return newRoot;

}

function changeToBalance(root){
    if(isBalance(root)) return root;
    if(root.left != null) root.left = changeToBalance(root.left);
    if(root.right != null) root.right = changeToBalance(root.right);
    let leftDeep = getDeep(root.left),
        rightDeep = getDeep(root.right);
    if(Math.abs(leftDeep - rightDeep) < 2) return root;
    else if(leftDeep > rightDeep){ //不平衡,左边深,需要右旋
        //判断是否需要进行左旋
        let changeTreeDeep = getDeep(root.left.right),
            noChangeTreeDeep = getDeep(root.left.left);
        if(changeTreeDeep > noChangeTreeDeep) root.left = leftRoate(root.left);
        //是否右右双旋
        let newRoot = rightRotate(root);
        newRoot.right = changeToBalance(newRoot.right);
        newRoot = changeToBalance(newRoot);
        return newRoot;
    }else{ //不平衡,右边深,需要左旋
        //判断是否需要进行右旋
        let changeTreeDeep = getDeep(root.right.left),
            noChangeTreeDeep = getDeep(root.right.right);
        if(changeTreeDeep > noChangeTreeDeep) root.right = rightRotate(root.right);
        //是否左左双旋
        let newRoot = leftRotate(root);
        newRoot.left = changeToBalance(newRoot.left);
        newRoot = changeToBalance(newRoot);
        return newRoot;
    }
    return root;
}

var node2 = new Node(2),
    node3 = new Node(3),
    node5 = new Node(5),
    node6 = new Node(6);
node2.right = node5;
node5.left = node3;
node5.right = node6;

console.log(isBalance(node2));
var newRoot = changeToBalance(node2);
console.log(isBalance(newRoot),newRoot);
二叉树双旋(右左双旋、左右双旋)

单旋时,变化分支不可以是唯一的最深分支

当要对某个节点进行左(右)单旋时,
如果变化分支是唯一的最深分支,则需要对新根进行右(左)单旋,然后再进行左(右)单旋,这样的旋转叫右左双旋(左右双旋)。

(左左双旋、右右双旋)

如果变化分支的高度比旋转节点的另一侧高度差距超过2,那么单旋后依旧不平衡。

234树的由来

希望有一棵树,最多有四个叉(度为4)。

影响二叉平衡排序树的性能的点在哪?

答:在于二叉平衡排序树只能有两个叉,导致在节点铺满时也有很多层。
希望可以一个节点存多个数,可以提升空间性能。

如果让查找效率尽可能高?

答:树的层级越少,查找效率高。

怎样让二叉平衡排序树的层数变得更少?

答:如果大于二叉,层数会更少。

234树子节点永远在在最后一层,永远是平衡的(每一个路径高度都相同)
结果:分支变多,层数变少,节点中存的数变多,节点变少,但复杂度上升。

因此希望:
简化为二叉树;
依旧保留多叉;
依旧单节点中存放多个值

红黑树

性质1:节点是红色或黑色。
性质2:根节点是黑色。
性质3:每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的节点)
性质4:从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点。

红黑树是普通的二叉平衡排序树,仅有的节点含义不同而已。

树的深度优先搜索
function Node(value){
    this.value = value;
    this.children = [];
}

function deepSearch(root, target){
    if(root == null) return false;
    if(root.value == target) return true;
    let flag = false;
    for(let i = 0; i < root.children.length; i++){
      flag |= deepSearch(root.children[i], target);
    }
    return  flag ? true : false;
}
var a = new Node('a'),
    b = new Node('b'),
    c = new Node('c'),
    d = new Node('d'),
    e = new Node('e'),
    f = new Node('f');
a.children.push(c, f, b);
b.children.push(d, e);
console.log(deepSearch(a,'c'));
树的广度优先搜索
function wideSearch(rootList, target){//bfs
    if(rootList == null || rootList.length == 0) return false;
    let children =[];//当前层所有节点的子节点,都在这个List中,这样传入下一层级的时候,就可以遍历整个层级的节点。
    for(let i = 0; i < rootList.length; i++){
        if(rootList[i] != null && rootList[i].value == target) return true;
        else{
            children.push(rootList[i].left);
            children.push(rootList[i].right);
        }
    }
    return wideSearch(children, target);
}
console.log(wideSearch([a],'c'));
图的深度优先搜索
function Node(value){
    this.value = value;
    this.neighbor = [];
}
var a = new Node('a'),
    b = new Node('b'),
    c = new Node('c'),
    d = new Node('d'),
    e = new Node('e');
a.neighbor.push(b, c);
b.neighbor.push(a, c, d);
c.neighbor.push(a, b, d);
d.neighbor.push(b, c, 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);
    let flag = false;
    for(let i = 0; i < node.neighbor.length; i++){
       flag |= deepSearch(node.neighbor[i], target, path);
    }
    return flag ? true : false;
}
console.log(deepSearch(d, 'a', []));
图的广度优先搜索

常用于爬虫

function bfs(nodes, target, path){
    if(nodes == null || nodes.length == 0) return false;
    let 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], 'a', []));

动态规划

笔试遇到一般是大题

斐波拉契数列
function fibo(n){
    if(n <= 0) return -1;
    if(n == 1) return 0;
    if(n == 2) return 1;
    return fibo(n - 1) + fibo(n - 2);
}
青蛙跳台阶
//一个青蛙一次只能跳一阶或两阶台阶,问:有多少种跳法?
//分析:如果青蛙跳上了第n阶台阶,则最后跳跃之前一定在n-1阶台阶或n-2阶台阶,
//即问题转变成了两个子问题,跳上n-1阶台阶的方法与跳上n-2阶台阶的方法之和。
//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);
}

//变态青蛙跳台阶
//青蛙一次可以跳一阶、二阶、......n阶台阶,跳上n阶台阶有多少种跳法?
// f(n) = f(n-1) + f(n-2) + ... + f(2) + f(1) + f(0)

function complexJump(n){
    if(n <= 0) return -1;
    if(n == 1) return 1;
    if(n == 2) return 2;
    let sum = 0;
    for(let i = 1; i < n; i++){
        sum += complexJump(n-1);
    }
    return sum + 1; // +1 表示从0阶台阶直接跳上去的情况
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值