栈与队列:
- 中缀表达式变后缀表达式
两个数组,一个stack存操作符,一个output存后缀表达式结果
遇到数字,输出到output 遇到操作符,如果优先级大于栈顶,直接压入,如果小于等于栈顶,依次弹栈到output,直到操作符大于栈顶 左括号优先级最高,遇到右括号时,弹栈直到左括号弹出
- 后缀表达式计算
一个数组,存放操作数 遇到操作数,压入栈 遇到操作符,将栈顶两个元素弹出,并且将计算结果压入栈
-
循环队列
-
两个栈实现的队列
//一个输入栈,一个输出栈
/**
* 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~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;
}
}
- 链表环入口
两指针同时从head出发,p1一次走1步,p2一次走2步,相遇时走的次数为环的k 第二次p2先走k步,p1在head. 两指针同时出发一次走一步,相遇的位置为环的入口
图: 0. 基本概念
连通图:图中任意结点i都有路径可以到达任意结点j 强连通图:有向连通图即是强连通图 连通分量:非连通图的极大连通子图
连通图的边的数量至少是结点数量-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();
}
}
}
- 连通图的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;
}
}
}
}
- 非连通图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;
}
- 非连通图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;
}
- 拓扑排序
用队列实现 每次寻找入度为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;
}
- kruskal求最小生成树
n个结点的图,初始化为n棵树 将边按照weight升序排序 不断遍历边,如果边两个结点不在一个集合中,将边加入,否则忽略
- prim求最小生成树
所有节点分为A, B两个集合 初始化A集合中只有源结点0 每次选择A和B之间weight最小的一条边 类似dijkstra, 每次迭代,更新源到目标结点的最小距离
- 单源最短路径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;
}
- 有向图欧拉路径/欧拉回路
欧拉路径:有向图中一条可以经过所有边并且不经过一条边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();
}
随机数:
- 生成[min, max]随机数
//Math.random()生成的范围是[0, 1)
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
排序:
- 堆排序
//调整堆
//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);
}
}
- 堆中元素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);
}
}
- 堆的插入(优先队列的插入)
//将元素放到堆的最后
//不断与父结点交换直到不大于父节点为止
//等价于将最后的-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;
}
- 树的非递归中序遍历
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;
}
- 树的非递归后序遍历
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)高度为h的完全二叉树,结点个数为2 ** h - 1 2)结点个数为n的二叉树,树的高度为Math.floor(logN) + 1 3)数组表示的树,结点k的左子树2 * k + 1, 右子树 2 * k + 2, 父节点 Math.floor((k - 1) / 2)