数据结构与算法

129 阅读6分钟

栈 Stack

  1. 后进先出
  2. 后进先出的数据结构 入 push () 出 pop()
  3. 需要后进先出的场景: 1、十进制转二进制 2、有效括号 3、函数调用堆栈

队列 queue

  1. 先进先出, 保证有序
  2. 后进先出的数据结构 入 push () 出 shift()
  3. 需要先进先出的场景 1、食堂打饭 2、js异步中的任务队列(js是单线程) 3、计算最近请求次数

事件循环与任务队列

代码刚执行时有一个主事件放到任务队列中,然后JS引擎会去任务队列中拿到这个事件去执行,执行到setTimeOut时发现这是一个异步任务,就交给WebAPI来执行这个异步任务,它继续执行后面的代码。setTimeout 0秒后执行完,接着把执行完后的回调函数放到任务队列中,但是任务队列中还有一个事件没执行完,即主事件。主事件执行完了才轮到回调函数执行。

环形链表

  1. point: 快慢指针

原型链

  1. 如果A沿着原型链能找到B.prototype, 那么A instanceof B为true.
  2. 如果在A对象上没有找到X属性,那么会沿着原型链找X属性。
  3. question:
    1. instanceOf的原理,并用代码实现 如果A沿着原型链可以找到B的原型对象,那么A instanceOf B为true, 反之为false
    const instanceOf = (A, B) => {
        let p = A;
        while(p){
            if(p === B.prototype){
                return true;
            }
            p = p.__proto__;
        }
        return false;
    }
    

链表总结

  1. 链表里的元素存储不是连续的,之间通过next连接。
  2. Javascript中没有链表,但可以用Object模拟链表
  3. 链表常用操作: 修改next, 遍历链表
  4. JS中的原型链也是一个链表
  5. 使用链表指针可以获取JSON的节点值

集合 (无序且唯一)

  1. 数组集合互转
    1. 集合转数组 a.
    const arr = [...set];
    
    b.
    const arr = Array.from(set);
    
    1. 数组转集合
    const set = new Set(arr);
    
  2. 求数组交集
    [...new Set(arr)].filter(x => arr2.includes(x));
    

字典

  1. has(), set(), get(), delete()

深度优先遍历

  1. 算法(递归): 1、访问根节点 2、对根节点的children挨个进行深度优先遍历
  2. 代码:
    function dfs(root){
        console.log(root.val);
        root.children.forEach(dfs);
    };
    

广度优先遍历

  1. 算法: 1、新建一个队列,把根节点入队 2、把队头出队并访问 3、把队头的children挨个入队 4、重复第二、三步,直到队列为空
  2. 代码:
    function bfs(root){
        const q = [root];
        while(q.length > 0){
            const h = q.shift();
            console.log(h.val);
            h.children.forEach(x => {
                q.push(x);
            });
        }
    };
    

先序遍历算法

  1. 访问根节点
  2. 对根节点的左子树进行先序遍历
  3. 对根节点的右子树进行先序遍历
  4. 非递归版代码:
    const preorder = (root) => {
        if(!root) {return;}
        const stack = [root];
        while(stack.length){
            const n = stack.pop();
            console.log(n);
            if(n.right) stack.push(n.right);
            if(n.left) stack.push(n.left);
        }
    };
    

中序遍历算法

  1. 对根节点的左子树进行中序遍历
  2. 访问根节点
  3. 对根节点的右子树进行中序遍历
  4. 非递归版代码:
    const inorder = (root) => {
        if(!root) { return; }
        const stack = [];
        let p = root;
        while(stack.length || p){
            while(p) {
                stack.push(p);
                p = p.left;
            }
            const n = stack.pop();
            console.log(n.val);
            p = n.right;
        }
    }
    

后序遍历算法

  1. 对根节点的左子树进行后序遍历
  2. 对根节点的右子树进行后序遍历
  3. 访问根节点
  4. 非递归版代码:
    const postorder = (root) => {
        if(!root) {return;}
        const outputStack = [];
        const stack = [root];
        while(stack.length) {
            const n = stack.pop();
            outputStack.push(n);
            if(n.left) stack.push(n.left);
            if(n.right) stack.push(n.right);
        }
        while(outputStack.length){
            const n = outputStack.pop();
            console.log(n.val);
        }
    };
    

遍历JSON的所有节点值

const json = {
    a: {b: { c : 1}},
    d: [1, 2],
}

// concat用于连接多个数组
// Object.keys(obj)会返回一个由传入对象的可枚举属性组成的数组,
数组名的排列顺序和使用for...in...循环遍历该对象返回的顺序一致。

const dfs = (n) => {
    console.log(n);
    Object.keys(n).forEach(k => {
        dfs(n[k], path.concat(k));
    });
};

dfs(json, []);

图的深度优先遍历

  1. 访问根节点
  2. 对根节点的没访问过的相邻节点挨个进行深度优先遍历
  3. 代码实现:
    const visited = new Set();
    const dfs = (n) => {
        console.log(n);
        visited.add(n);
        graph[n].forEach(c => {
            if(!visited.has(c)){
                dfs(c);
            }
        });
    };
    

图的广度优先遍历

  1. 新建一个队列,把根节点入队
  2. 把队头出队并访问
  3. 把队头的没访问过的相邻节点入队
  4. 重复第二、三步,直到队列为空
  5. 代码实现:
    const visited = new Set();
    visited.add(2);
    const q = [2];
    while(q.length){
        const n = q.shift();
        console.log(n);
        graph[n].forEach(c => {
            if(!visited.has(c)){
                q.push(c);
                visited.add(c);
            }
        });
    }
    

  1. 代码实现:
    class MinHeap {
        constructor(){
            this.heap = [];
        }
        swap(i1, i2){
            const temp = this.heap[i1];
            this.heap[i1] = this.heap[i2];
            this.heap[i2] = temp;
        }
        getParentIndex(i){
            return Math.floor((i-1)/2);
        }
        getLeftIndex(i){
            return (i*2 + 1);
        }
        getRightIndex(i){
            return (i*2 + 2);
        }
        shiftUp(index){
            if(index == 0) { return; }
            const parentIndex = this.getParentIndex(index);
            if(this.heap[this.parentIndex] > this.heap[index]){
                this.swap(parentIndex, index);
                this.shiftUp(parentIndex);
            }
        }
        shiftDown(index){
            const leftIndex = this.getLeftIndex(index);
            const rightIndex = this.getRightIndex(index);
            if(this.heap[leftIndex] < this.heap[index]){
                this.swap(leftIndex, index);
                this.shiftDown(leftIndex);
            }
            if(this.heap[rightIndex] < this.heap[index]){
                this.swap(rightIndex, index);
                this.shiftDown(rightIndex);
            }
        }
        insert(value){
            this.heap.push(value);
            this.shiftUp(this.heap.length-1);
        }
        pop(){
            this.heap[0] = this.heap.pop();
            this.shiftDown(0);
        }
        peek(){
            return this.heap[0];
        }
        size(){
            return this.heap.length;
        }
    }
    

选择排序

  1. 代码实现:
    Array.prototype.selectionSort = function(){
        for(let i = 0; i< this.length - 1; i++){
            let indexMin = i;
            for(let j = i; j < this.length; j++){
                if(this[j] < this[indexMin]){
                    indexMin = j;
                }
            }
            if(!indexMin == i){
                let temp = this[i];
                this[i] = this[indexMin];
                this[indexMin] = temp;
            }
        }
    }
    

插入排序

  1. 算法思路:

    1、从第二个数开始往前比

    2、比它大就往后排

    3、以此类推到最后一个数

  2. 代码实现:

    Array.prototype.insertSort = function(){
        for(let i = 0; i< this.length; i++){
            let temp = this[i];
            let j = i;
            while(j > 0){
                if(this[j - 1] > temp){
                    this[j] = this[j-1];
                } else {
                    break;
                }
                j--;
            }
            this[j] = temp;
        }
    }
    

归并排序

  1. 算法思路:

    1、把数组分成两半,再递归的对子数组进行‘分’操作,直到分成一个个单独的数。

    2、把两个数合并为有序数组,再对有序数组进行合并,直到全部子数组合并为一个完整数组。

  2. 代码实现:

    function merge(arr){
        if(arr.length ==1) return arr;
        let mid = Math.floor(arr.length/2);
        let left = arr.slice(0, mid);
        let right = arr.slice(mid, arr.length);
        let res = [];
        let leftArr = merge(left);
        let rightArr = merge(right);
        while(leftArr.length || rightArr.length){
            if(leftArr.length && rightArr.length){
                if(leftArr[0] < rightArr[0]){
                    res.push(leftArr.shift());
                } else if (leftArr[0] > rightArr[0]){
                    res.push(rightArr.shift());
                }
            } else if(leftArr.length){
                res.push(leftArr.shift());
            }else if(rightArr.length){
                res.push(rightArr.shift());
            }
        }
        return res;
    }
    

快速排序

  1. 算法思路

    1、分区:从数组中任意选择一个基准,所有比基准小的元素放在基准的前面,比基准大的元素放在基准的后面。

    2、递归:递归地对基准前后的子数组进行分区

  2. 代码实现:

    Array.prototype.quickSort = function(){
        const rec = (arr) => {
            if(arr.length === 1) return res;
            let left = [];
            let right = [];
            let mid = arr[0];
            for(let i =1; i< arr.length; i++){
                if(arr[i] > mid){
                    right.push(arr[i]);
                }else {
                    left.push(arr[i]);
                }
            }
            return [...rec(left), mid, ...rec(right)];
        };
        const res = rec(this);
        res.forEach((n,i) => {this[i] = n});
    };
    

二分搜索

  1. 算法思路

    1、从数组的中间元素开始,如果中间元素正好是目标值,则搜索结束。

    2、如果目标值大于或者小于中间元素,则在大于或小于中间元素的那一半数组中搜索。

  2. 代码实现:

    Array.prototype.binarySearch = function(item){
        let low = 0;
        let high = this.length - 1;
        
        while(low <= high){
            let mid = Math.floor((low + high) /2);
            let elem = this[mid];
            if(elem < item){
                low = mid + 1;
            } else if( elem > item){
                high = mid - 1;
            } else{
                return mid;
            }
        }
        return -1;
    };
    

分治

  1. 将一个问题分成多个和原问题相似的小问题,递归解决小问题,再将结果合并以解决原来的问题。
  2. 应用场景: 归并排序、快速排序、二分搜索、翻转二叉树...

动态规划

  1. 将一个问题分解为相互重叠的子问题,通过反复求解子问题,来解决原来的问题。
  2. 与分治的区别:看他们的子问题是否是独立的。如果重叠,则为动态规划;如果独立,则为分治。
  3. 应用场景:斐波那契数列
  4. 步骤: 1、定义子问题 2、反复执行

贪心算法

  1. 期盼通过每个阶段的局部最优选择,从而达到全局的最优
  2. 结果并不一定是最优

回溯算法

  1. 回溯算法是一种渐进式寻找并构建问题解决方式的策略
  2. 回溯算法会先从一个可能的动作开始解决问题,如果不行,就回溯并选择另一个动作,直到将问题解决。
  3. 全排列:时间复杂度:o(n!) 空间复杂度:因为使用了递归,本质是堆栈,为线性增长,所以为o(n)
  4. 子集:时间复杂度:o(2^n), 每个元素两种情况(存在或不存在) 空间复杂度:o(n)