一、队列
1.队列是先进先出的结构 。(JS引擎利用队列执行异步任务,计算最近的请求次数,树、图中广度优先遍历)
2.JS中没有队列,可以用Array实现队列的所有功能。
3.常用操作:push、shift、queue[0]
二、链表
1. 原型链: 本质是链表;原型链上的节点:各种原型对象;通过__proto__属性连接各种原型对象。对象、函数、数组 ,js变量中除了对象这个数据类型,其他的都是先指向自己的原型对象,再指向Object的原型对象。
(1)面试一:instanceof的原理用代码实现
知识点:如果A沿着原型链能找到B.prototype,那么A instanceOf B为true;
解法:遍历A的原型链,如果能找到B的原型,则返回true,否则返回false
const instanceOf = (A,B) => {
while(p){
if(p === B){
return true;
}
p = p.prototype;
}
return false;
}
(2)面试二 下面代码中的foo.a, foo.b, F.a, F.b是多少
知识点:如果A对象上没有找到x属性,那么会沿着原型链找x属性。
解法:明确foo和F变量的原型链,沿着原型链找a属性和b属性。
var foo = {};
F = () => {};
Object.prototype.a = 'value a'; //foo.a 为 value a, F.a为value a
Function.prototype.b = 'value b';//F.b为value b, foo.b为null,因为对象的原型链上没有函数的原型。
三、集合
-
集合:无序且唯一的数据结构, ES6中的集合名为Set; 集合的常用操作:去重、判断某元素是否在集合中、求交集
-
Set 使用Set对象:new add delete has size; 迭代Set: 多重迭代方法、set与Array互转、求差集/交集
let my = new Set();
my.add(1);
my.add(2);
my.add('some text');
my.add({a:1, b:2})
my.delete(2);
for(let item of my) console.log(item);
// set数据结构中key、value为同一个值。
for(let [key, value] of my.entries()) console.log(key, value);
//set 转换为 array
const myarr = [...my];
const myArr = Array.from(my);
//array 转为 set
const myset2 = new Set([1,2,3,4]);
//求交集
const intersection = new Set([...my].filter(x => myset2.has(x)));
//求差集
const difference = new Set([...my].filter(x => !myset2.has(x)));
字典
- 与集合类似,字典也是一种存储唯一值得数据结构,但它以键值对存储。
- ES6中有字典,叫Map
- 常用操作:增删改查
树
- 分层数据的抽象模型
- 前端工作中常见的包括:DOM、树、级联选择、树形控件
- JS中没有树,但可以用Object和Array来构建树
- 常用操作:深度/广度、先中后序遍历
1. 深度优先遍历
算法口诀:
- 访问根节点;
- 对根节点的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(dfs);
}
dfs(tree);
2. 广度优先遍历
算法口诀:
- 新建一个队列,把根节点入队;
- 把队头出队并访问;
- 把队头的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 bfs = (root) => {
const q = [root];
while(q.length !== 0){
const n = q.shift();
console.log(n.val);
n.children.forEach(element => {
q.push(element);
});
}
}
bfs(tree)
二叉树
- 树的每个节点最多只能有两个子节点
- 在JS中常用Object来模拟二叉树
- 所谓前中后序遍历,遍历的是二叉树的根节点
递归版
1. 先序遍历
算法口诀:
- 访问根节点;
- 对根节点的左子树进行先序遍历;
- 对根节点的右子树进行先序遍历
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);
}
preorder(bt)
2. 中序遍历
算法口诀:
- 对根结点的左子树进行中序遍历;
- 访问根节点;
- 对根节点的右子树进行中序遍历
const inorder = (root) => {
if(!root) return;
inorder(root.left);
console.log(root.val);
inorder(root.right);
}
3. 后序遍历
算法口诀:
- 对根节点的左子树进行后序遍历
- 对根节点的右子树进行后序遍历
- 访问根节点
const postorder = (root) => {
if(!root) return;
postorder(root.left);
postorder(root.right);
console.log(root.val)
}
非递归版(面试更偏向考察)
1. 先序遍历
因为栈结构后进先出,而访问节点顺序为先左后右,所以先把右节点push进去。
const preorder1 = (root) => {
if(!root) return;
const stack = [root];
while(stack.length){
const n = stack.pop();
console.log(n.val);
// 因为栈结构后进先出,而访问节点顺序为先左后右,所以先把右节点push进去。
if(n.right) stack.push(n.right.val);
if(n.left) stack.push(n.left.val);
}
}
2. 中序遍历
左根右,从当前根元素开始循环压入所有左子节点,当到叶子节点时,弹出当前叶子结点,此时栈顶为上一个左节点,若其也没有右子树,那么弹出该节点(即刚刚弹出的元素的父节点)
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;
}
}
3. 后序遍历
先序遍历输出顺序是根左右 后序需要的是左右根,所以将先序遍历的左右顺序调换,使得到根右左,再逆序输出(需要第二个栈)即可
//先序遍历输出顺序是根左右 后序需要的是左右根
//所以将先序遍历的左右顺序调换,使得到根右左,再逆序输出(需要第二个栈)即可
const postorder1 = (root) => {
if(!root) return;
const outputStack = [];
const stack = [root];
while(stack.length){
const n = root;
//逆序入第二个栈
outputStack.push(n);
//调换先序遍历的入栈顺序,栈后进先出,第一次变换要得到右左,所以先压左后压右
if(n.left) stack.push(n.left);
if(n.right) stack.push(n.right);
}
}