基础

115 阅读4分钟

栈与队列:

  1. 中缀表达式变后缀表达式

两个数组,一个stack存操作符,一个output存后缀表达式结果

遇到数字,输出到output 遇到操作符,如果优先级大于栈顶,直接压入,如果小于等于栈顶,依次弹栈到output,直到操作符大于栈顶 左括号优先级最高,遇到右括号时,弹栈直到左括号弹出

  1. 后缀表达式计算

一个数组,存放操作数 遇到操作数,压入栈 遇到操作符,将栈顶两个元素弹出,并且将计算结果压入栈

  1. 循环队列

  2. 两个栈实现的队列


//一个输入栈,一个输出栈
/**
 * Initialize your data structure here.
 */
var MyQueue = function() {
    //一个输入栈,一个输出栈
    this.inStack = [];
    this.outStack = [];
};

/**
 * Push element x to the back of queue. 
 * @param {number} x
 * @return {void}
 */
MyQueue.prototype.push = function(x) {
    this.inStack.push(x);
};

/**
 * Removes the element from in front of queue and returns that element.
 * @return {number}
 */
MyQueue.prototype.pop = function() {
    if (!this.outStack.length) {
         while (this.inStack.length) {
            this.outStack.push(this.inStack.pop())
        }
    }
    return this.outStack.pop();
};

/**
 * Get the front element.
 * @return {number}
 */
MyQueue.prototype.peek = function() {
    if (!this.outStack.length) {
         while (this.inStack.length) {
            this.outStack.push(this.inStack.pop())
        }
    }
    return this.outStack[this.outStack.length - 1];
};

/**
 * Returns whether the queue is empty.
 * @return {boolean}
 */
MyQueue.prototype.empty = function() {
    return !this.inStack.length && !this.outStack.length;
};

 

链表:

  1. 链表逆序
//链表逆序:依次把1~n移到第0个前面

function reverseLink(head) {
   if (!head || !head.next) return;
    let pre = head,
        cur = head.next;
     
    while (cur) {
        pre.next = cur.next;
        cur.next = head;
        head = cur;
        cur = pre.next;
    }
}

  1. 链表环入口

两指针同时从head出发,p1一次走1步,p2一次走2步,相遇时走的次数为环的k 第二次p2先走k步,p1在head. 两指针同时出发一次走一步,相遇的位置为环的入口

图: 0. 基本概念

连通图:图中任意结点i都有路径可以到达任意结点j 强连通图:有向连通图即是强连通图 连通分量:非连通图的极大连通子图

连通图的边的数量至少是结点数量-1 强连通图的边的数量至少是结点数量

  1. 连通图的DFS

邻接矩阵表示的图


function graphDFS(g) {
  let n = g.length,
     stack = [],
     visited = new Array(n),
     ans = [],
     count = 1;
  
  stack.push(0);
  visited[0] = true;
  ans.push(0);
  while (stack.length) {
    let node = stack[stack.length-1];
    if (count == n) continue;
    let i = 1;
    for (; i < n; i++) {
        if (!visited[i] && g[node][i] == 1) {
        stack.push(i);
        visited[i] = true;
        //压栈时访问node
        ans.push(node);
        count++;
        break;
        }
    }
    //该节点没有未访问过的后续节点,弹出此结点
    if (i == n) {
        stack.pop();
    }
  }
}

  1. 连通图的BFS

function graphBFS(g) {
    let n = g.length,
       queue = [],
       ans = [],
       visited = new Array(n + 1),
       count = 0;
    
    queue.push(0);
    visited[0] = true;
    while (queue.length) {
        let node = queue.shift();
        ans.push(node);
        count++;
        if (count == n) continue;
        for (let i = 1; i < n; i++) {
            if (!visited[i] && g[node][i] == 1) {
                queue.push(i);
                visited[i] = true;
            }
        }
    }
}

  1. 非连通图DFS(通用,适用于连通)

function dfsGraph(graph) {

    let n = graph.length,
        visited = new Array(n),
        ans = [];

    function dfsVisit(node) {
        visited[node] = true;
        ans.push(node);
        for (let i = 0; i < n; i++) {
            if (!visited[i] && graph[node][i]) {
                dfsVisit(i);
            }
        }
    }
    
    for (let i = 0; i < n;i++) {
        if (!visited[i]) {
            dfsVisit(i);
        }
    }
    
    return ans;
}
  1. 非连通图BFS(通用,适用于连通)

function graphBFS(graph) {
  let n = graph.length,
     visited = new Array(n),
     ans = [];
     
  function bfsVisit(node) {
    let queue = [node];
    while (queue.length) {
        let cur = queue.shift();
        ans.push(cur);
        for (let i = 0; i < n; i++) {
            if (!visited[i] && graph[cur][i]) {
                queue.push(i);
                visited[i] = true;
            }
        }
    }
  }
  
  for (let i = 0; i < n; i++) {
    if (!visited[i]) {
        bfsVisit(i);
    }
  }
  
  return ans;
}


  1. 拓扑排序

用队列实现 每次寻找入度为0的结点, 放入队列 图用邻接表表示


function topologicalSort(graph) {
    let n = graph.length,
        map = {},
        queue = [],
        ans = [];
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < graph[i].length; j++) {
            if (!map[j]) {
                map[j] = 1;
            } else {
                map[j]++;
            }
        }
    }
    
    for (let i = 0; i < n; i++) {
        if (!map[i]) {
            queue.push(i);
        }
    }
    
    while(queue.length) {
        let node = queue.shift();
        ans.push(node);
        graph[node].forEach(i => {
            map[i]--;
            if (map[i] == 0) {
                queue.push(i);
            }
        });
    }
    
    return ans;
}

  1. kruskal求最小生成树

n个结点的图,初始化为n棵树 将边按照weight升序排序 不断遍历边,如果边两个结点不在一个集合中,将边加入,否则忽略

  1. prim求最小生成树

所有节点分为A, B两个集合 初始化A集合中只有源结点0 每次选择A和B之间weight最小的一条边 类似dijkstra, 每次迭代,更新源到目标结点的最小距离

  1. 单源最短路径dijkstra算法

function dijkstra(graph) {
    let n = graph.length, 
        map = new Array(n),
        visited = new Array(n),
        count = 1;
    
    map[0] = 0;
    visited[0] = true;
    
    let cur = 0;
    while (count <= n) {
        //松弛邻接结点
        for (let i = 0; i < graph[cur].length; i++) {
            if (!visited[i]) {  
               //从未被触达过
                if (!map[i]) {
                    map[i] = map[cur] + graph[cur][i].weight;
                } else {
                    map[i] = Math.min(
                     map[i],
                     map[cur] + graph[cur][i].weight
                    );
                }
            }
        }
        
        //选出最近的结点
        let min = 99999, cur = -1;
        for (let i = 0; i < n; i++) {
            if (!visited[i] && map[i] && map[i] < min){               
               min = map[i];
               cur = i;
            }
        }
        
        visited[cur] = true;
        count++;
    }
    
    return map;
}
  1. 有向图欧拉路径/欧拉回路

欧拉路径:有向图中一条可以经过所有边并且不经过一条边2次的路径。路径回到起点则是欧拉回路。

欧拉路径:起点出度比入度大1,终点入度比出度大1,其他点入度等于出度

欧拉回路:所有点入度等于出度


//对图进行DFS
//每次把走过的边置为visited
//最终无边的结点,放入栈中

function eulerPath(graph) {
    let stack = [],
        visited = {};
    
    function dfs(node) {
        let i = 0;
        for (; i < graph[node].length;i++) {
            if (!visited[node + '_' + i]) { 
                visited[node + '_' + i] = true;
                dfs(i);
            }
        }
        //遍历结束
        stack.push(node);
    
    }
    
    dfs(0);
    return stack.reverse();

}


随机数:

  1. 生成[min, max]随机数

//Math.random()生成的范围是[0, 1)

function getRandom(min, max) {
    
    return Math.floor(Math.random() * (max - min + 1)) + min;

}

排序:

  1. 堆排序
//调整堆
//params: heap, i
//hint: i的左子树和右子树都已经是调整好的堆
function heapMaxify(heap, i) {
    while (i < heap.length) {
        let max = i,
            left = i * 2 + 1,
            right = i * 2 + 2;
        if (left < heap.length && heap[max] < heap[left]) {
            max = left;
        }
        if (right < heap.length && heap[max] < heap[right]) {
            max = right;
        }
        if (max == i) {
            return;
        } else {
            let tmp = heap[i];
            heap[i] = heap[max];
            heap[max] = tmp;
            i = max;
        }   
    }
}


//建堆
//把一个数组调整为一个最大堆
function buildHeap(heap) {
    let n = heap.length,
       i = Math.floor((n-2) / 2);
    
    while (i >= 0) {
        heapMaxify(heap, i);
        i--;
    }
}

//堆排序
//永远把堆的根(目前最大值)与堆尾元素交换(当前堆的堆尾)
//再调整新的根
//最终会得到升序排序结果

function heapSort(heap) {
    buildHeap(heap);
    let n = heap.length;
    for (let j = n - 1; j >= 1; j--) {
       let tmp = heap[j];
       heap[j] = heap[0];
       heap[0] = tmp;
       heapMaxify(heap.slice(0, j - 1), 0);
    }
}

  1. 堆中元素x的增加
//最大堆中某个元素i的值增加了
//从i出发向上寻找正确的位置,如果大于父元素,不断交换。
//不必担心换下来的元素会改变堆的性质。因为上层结点除了x之外,必然大于下层所有节点。

function heapIncrease(heap, i, x) {
    heap[i] = x;
    let parent =Math.floor((i - 1) / 2);
    while (parent >= 0 && heap[i] > heap[parent]) {
        let tmp = heap[parent];
        heap[parent] = heap[i];
        heap[i] = tmp;
        i = parent;
        parent =Math.floor((parent - 1) / 2);
    }

}

  1. 堆的插入(优先队列的插入)

//将元素放到堆的最后
//不断与父结点交换直到不大于父节点为止
//等价于将最后的-Infinity结点增加到x

function heapInsert(heap, x) {
    heap.push(x);
    let parent = Math.floor((heap.length - 2)/2),
        i = heap.length - 1;
    while (parent >= 0 && heap[i] > heap[parent]) {
        let tmp = heap[parent];
        heap[parent] = heap[i];
        heap[i] = tmp;
        i = parent;
        parent = Math.floor((parent - 1) / 2);
    }

}

树: 1、树的非递归前序遍历

tip: 入栈时先放入右子树,再放入左子树。这样左子树会先出栈。

 function preOrder(root) {
    if (!root) return [];
    let stack = [root], ans = [];
    while (stack.length) {
        let node = stack.pop();
        ans.push(node);
        if (node.right) {
            stack.push(node.right);
        }
        if (node.left) {
            stack.push(node.left);
        }   
    }
    
    return ans;
    
 }
  1. 树的非递归中序遍历

tip:

function inOrder(root) {
    let stack = [], ans = [],
        node = root;
    
    while (node || !stack.length) {
        if (node) {
            stack.push(node);
            node = node.left;
        } else {
            node = stack.pop();
            ans.push(node);
            node = node.right;
        }
    }
    
    return ans;
}
  1. 树的非递归后序遍历

tip: 后续遍历中每个pop出的结点,要么满足是根节点,要么是左右子结点分别作为前驱刚刚遍历完


function postOrder(root) { 
   if (!root) return;
    let stack = [root],
        ans = [];
        pre = null;
        cur = null;
        
    while (stack.length) {
        cur = stack[stack.length - 1];
        if (!cur.left && !cur.right
        || (!pre && (pre == cur.left || pre == cur.right))) {
            ans.push(stack.pop());
            pre = cur;
        } else {
            if (cur.right) {
                stack.push(cur.right);
            }
            if (cur.left) {
                stack.push(cur.left);
            }
        }
        
        
     return ans;
    
    }

}

  1. 树的关系式

1)高度为h的完全二叉树,结点个数为2 ** h - 1 2)结点个数为n的二叉树,树的高度为Math.floor(logN) + 1 3)数组表示的树,结点k的左子树2 * k + 1, 右子树 2 * k + 2, 父节点 Math.floor((k - 1) / 2)