系列文章目录
目录
前言
JavaScript算法,树、堆相关知识
9.树
- 一种分层数据的抽象模型
- 前端工作中常见的树包括:DOM树、级联选择、树形控件
- JS中没有树,可以用Object和Array构建树
- 树的常用操作:深度/广度优先遍历、先中后序遍历
深度优先遍历:尽可能深的搜索树的分支
- 访问根节点
- 对跟节点的children 挨个进行深度优先遍历
const tree = {
val: 'a',
children: [
{
val: 'b',
children: [
{
val: 'd',
children: [],
},
{
val: 'e',
children: [],
},
],
},
{
val: 'c',
children: [
{
val: 'f',
children: [],
},
{
val: 'g',
children: [],
},
],
}
],
}
const dfs = (root) => {
console.log(root.val);
root.children.forEach((child) => {dfs(child)})
}
dfs(tree);
广度优先遍历:先访问离根节点最近的节点
- 新建一个队列,把根节点入队
- 把队头出队并访问
- 把队头的children挨个入队
- 重复第二、三步,直到队列为空
const bfs = (root) => {
const q = [root];
while(q.length > 0){
const n = q.shift()
console.log(n.val);
n.children.forEach(child => {
q.push(child)
})
}
}
bfs(tree);
二叉树
- 树中每个节点最多只能有两个子节点
- JS中通常用Object模拟二叉树
const binaryTree = {
val: 1,
left:{
val: 2,
left: null,
right: null,
},
right:{
val: 3,
left: null,
right: null,
},
}
先序遍历
const bt = {
val:1,
left:{
val:2,
left:{
val:4,
left:null,
right:null,
},
right:{
val:5,
left:null,
right:null,
},
},
right:{
val:3,
left:{
val:6,
left:null,
right:null,
},
right:{
val:7,
left:null,
right:null,
}
},
}
// 先序遍历
const preorder = (root)=>{
if(!root){return ;}
console.log(root.val);
preorder(root.left);
preorder(root.right);
}
// 非递归版(栈后进先出,先存右侧再存左侧)
const preorder1 = (root)=>{
if(!root){return ;}
const stack = [root];
while(stack.length){
const n = stack.pop();
console.log(n.val);
if(n.right) stack.push(n.right);
if(n.left) stack.push(n.left);
}
}
中序遍历
// 中序遍历
const inorder = (root) => {
if(!root) return ;
inorder(root.left);
console.log(root.val);
inorder(root.right);
}
// 非递归版
const inorder1 = (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;
}
}
后序遍历
// 后序遍历
const postorder = (root) => {
if(!root) return ;
postorder(root.left);
postorder(root.right);
console.log(root.val);
}
// 非递归版(把后序遍历的顺序倒置,用先序遍历逻辑实现逆序访问,用栈的后进先出,把先序遍历倒过来访问)
const postorder = (root) => {
if(!root) return ;
const stack = [root];
const outputStack = [];
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);
}
}
// leetCode 104 二叉树的最大深度
// 输入 [3,9,20,null,null,15,7] 输出3
var maxDepth = function(root){
let res = 0;
const dfs = (n,l) => {
if(!n) return ;
if(!n.left && !n.right){
res = Math.max(res,l);
}
dfs(n.left, l+1);
dfs(n.right, l+1);
}
dfs(root,1);
return res;
}
// leetCode 111 二叉树的最小深度(广度优先遍历)
// 输入 [3,9,20,null,null,15,7] 输出2
var minDepth = function(root){
if(!root) return 0;
const q = [[root,1]];
while(q.length){
const [n,len] = q.shift();
if(!n.left && !n.right){
return len;
}
if(n.left) q.push([n.length, len+1]);
if(n.right) q.push([n.right, len+1]);
}
}
// leetCode 102 二叉树的层序遍历(广度优先遍历,记录层级)
// 输入[3,9,20,null,null,15,7] 输出[[3],[9,20],[15,7]]
var levelOrder = function(root){
if(!root) return [];
const q = [[root,0]];
const res = [];
while(q.length){
const [n,level] = q.shift();
if(!res[level]){
res.push([n.val]);
}else{
res[level].push(n.val);
}
if(n.left)q.push([n.left, level+1]);
if(n.right)q.push([n.right, level+1]);
}
return res;
}
var levelOrder1 = function(root){
if(!root) return [];
const q = [root];
const res = [];
while(q.length){
let len = q.length;
res.push([]);
while(len--){
const n = q.shift();
res[res.length - 1].push(n.val);
if(n.left)q.push(n.left);
if(n.right)q.push(n.right);
}
}
return res;
}
// leetCode 94 二叉树的中序遍历(左根右)
// 输入[1,null,2,3] 输出[1,3,2]
var inorderTraversal = function(root){
const res = [];
const rec = (n)=>{
if(!n)return;
rec(n.left);
res.push(n.val);
rec(n.right);
}
rec(root);
return res;
}
// 迭代版
var inorderTraversal1 = function(root){
const res = [];
const stack = [];
let p = root;
while(stack.length || p){
while(p){
stack.push(p);
p = p.left;
}
const n = stack.pop();
res.push(n.val);
p = n.right;
}
return res;
}
// leetCode 112 路径总和(深度优先遍历)
// 给定目标和,计算二叉树是否有满足目标和的路径
var hasPathSum = function(root,sum){
if(!root) return false;
let res = false;
const dfs = (n,s) => {
if(!n.left && !n.right && s === sum){
res = true;
}
if(n.left) dfs(n.left, s+n.left.val);
if(n.right) dfs(n.right, s+n.right.val);
}
dfs(root,root.val);
return res;
}
前端与树:遍历JSON的所有节点值
const json = {
a:{ b: { c: 1}},
d: [1,2],
};
const dfs = (n, path) => {
console.log(n,path);
Object.keys(n).forEach(k => {
dfs(n[k], path.concat(k));
});
}
dfs(json,[]);
10.堆
- 堆是一种特殊的完全二叉树(每层都填满,不填满仅缺少最后一层的右节点)
- 所有的节点都大于等于(最大堆)或小于等于(最小堆)它的子节点
// leetCode 215 数组中的第K个最大元素
// 构建一个最小堆,并依次把数组的值插入堆中;当堆的容量超过k,就删除堆顶
// 输入[3,2,1,5,6,4] k=2 输出5
// 最小堆类
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 (i-1) >> 1;
}
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[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;
}
}
var findKthLargest = function(nums,k){
const h = new MinHeap();
nums.forEach(n => {
h.insert(n);
if(h.size() > k){
h.pop();
}
});
return h.peek();
}
// leetCode 347 前k个高频元素
// 输入[1,1,1,2,2,3],k=2 输出[1,2]
//Map方法
var topKFrequent = function(nums,k){
const map = new Map();
nums.forEach(n => {
map.set(n,map.has(n) ? map.get(n)+1:1);
})
const list = Array.from(map).sort((a,b)=> b[1]-a[1]);
return list.slice(0,k).map(n=>n[0]);
}
// 最小堆
// MinHeap类,shiftUp/shiftDown,需要换成比较value
var topKFrequent1 = function(nums,k){
const map = new Map();
nums.forEach(n => {
map.set(n,map.has(n) ? map.get(n)+1:1);
})
const h = new MinHeap();
map.forEach((value,key)=>{
h.insert({value,key});
if(h.size() > k){
h.pop();
}
})
return h.heap.map(a => a.key);
}
总结
JavaScript算法