数据结构与算法
这是9月看了一些数据结构相关的课程记录的算法笔记,语言均采用js,部分内容还没有写完,会不时修改
1.栈
1.1操作方法
在js中用数组来模拟一个栈
可以用push和pop来模拟栈的先进后出(LIFO)特性
1.2使用场景
①十进制转二进制
十进制转二进制实际上是通过不断地除2取余数完成的,最后除的余数作为第一位(先进后出)
②括号匹配
左括号入栈右括号出栈,以及可以设置不同的类型匹配
③函数调用堆栈
函数层级调用中,先调用的函数是最后结束的(先进后出),必须等到子层级中的函数调用完,外层的才能结束
1.3相关题目
2.队列
2.1操作方法
2.2使用场景
2.3相关题目
20.有效的括号
3.链表
链表中的元素存储不是连续的,而且对于一个元素来说只能访问下一个元素而不能访问上一个元素
就像是间谍,采用单线沟通,除了上级没有人能证明你是卧底
3.1操作方法
使用object来模拟
val存储当前节点的值,next存储下一个节点的引用
从头部节点(第一个节点)开始算为一个完整的链表
3.2使用场景
①原型链
原型链的节点是各种原型对象(Funtion.prototype, Object.prototype等)
原型链通过 __proto__来连接各种原型对象(相当于next)
比如一个function的原型链 : function -> Function.prototpye -> Object.protoytpe -> null
可以这样检验
const func = () => {};
func.__proto__.__proto__ === Object.prototype // true
如果A沿着原型链可以找到B.prototype 那么 A instanceof B 为true
这样我们就可以手写instanceof方法
const instanceof = (A, B) => {
//指定指针
let point = A;
//遍历链表(原型链)
while(point) {
if(point === B.prototype) {
return true;
}
point = point.__proto__
}
return false;
}
如果在A对象上找不到x属性,就会沿着原型链寻找x属性
const arr = [];
Object.prototpye.x = 'x'
console.log(arr.x) //'x'
②使用链表获取json节点值
const json = {
a: {b: {c: 1}},
d: {e: 2}
};
let path = ['a', 'b', 'c'];
let p = json;
path.forEach(k => {
p = p[k];
})
console.log(p) //p = 1
3.3相关题目
349.两个数组的交集
4.集合
集合是一种无序且唯一的数据结构
4.1操作方法
ES6自带了集合的数据结构Set,可以直接使用
用has判断集合是否有某一元素,同时可以通过keys和values进行迭代访问所有值(在set中,keys和values是一样的)
用set添加元素(多次添加重复元素不会报错但是会后续会无效,这里对于属性值相同的但是内存不同的对象不判定为重复)
用size访问集合的尺寸(有几个元素就有多大)
set和array互相转换方法:
//set -> array
const arr1 = Array.from(set1);
const arr1 = [...set1];
//array -> set
const set1 = new Set(arr1);
4.2使用场景
①去重
const arr = [1,1,2,2,3];
//去重用数组转集合,利用集合唯一性
const arr2 = [...new Ser(arr)];
②求交集
const set1 = new Set([1,2,3,5]);
const set2 = new Set([1,2,3,4]);
//求交集需要集合转数组filter
const intersection = new Set([...set1].filter(item => set2.has(item)));
顺便提一下求差集的方法(就是加一个非)
const intersection = new Set([...set1].filter(item => !set2.has(item)));
5.字典
字典是一种存储唯一值的数据结构,用键值对形式存储
5.1操作方法
ES6中自带了字典数据结构Map
const m = new Map();
//增&查
m.set('key1', 'value1');
m.get('key1') //'value1'
//删
m.delete('key1')
m.get('key1') // undefined
m.clear() //删除所有
//改
m.set('key1', 'value2')
5.2使用场景
5.3相关题目
349.两个数组的交集,这题用map比用set要好一些
20.有效的括号,这题用map简洁很多(在规则匹配上)
1.两数之和,用map记录数据
3.无重复字符的最长字符串,用双指针滑动窗口,用map记录值和下标(很重要)
76.最小覆盖子串,同样是双指针滑动窗口,不过需要动态的匹配和移动,不愧是困难
6.树
树是一种分层数据的抽象模型
6.1操作方法
js中没有树,但是可以用OBject和Array来构建树
const tree = {
value: '中国',
label: 'china',
children: [
{
value: '福建',
label: 'Fujian',
children: [
{
value: '泉州',
label: 'Quanzhou',
}
]
}
]
}
树的常用操作:深度/广度优先遍历,二叉树先中后序遍历
①
②深度/广度优先遍历
深度优先遍历就是尽可能深的搜索树的分支
深度优先遍历的算法:1.访问根节点 2.对根节点的children挨个进行深度优先遍历 3.把当前节点作为根节点,重复以上过程
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);
};
广度优先遍历则是先访问离根节点最近的节点
广度优先遍历的算法:1.新建队列并把根节点入队 2.把对头出队并访问 3.把对头的children挨个入队 4.重复2,3步 直到队列为空
const bfs = (root) => {
const queue = [root];
while(queue) {
const n = queue.shift();
console.log(n.val);
n.children.forEach((child) => {queue.push(child)});
}
};
②二叉树
二叉树是指书中每个节点都只能有两个子节点
在js中用Object来模拟二叉树
const binaryTree = {
val: 1,
left: {
val: 2,
left: 3,
right: 4
},
right: {
val: 5,
left: 6,
right: 7
}
}
先序遍历(根左右): 1.访问根节点 2.对根节点的左子树进行先序遍历 3. 对根节点的右子树进行先序遍历
const preorder = (root) => {
if(!root) { return; }
console.log(root.val);
preorder(root.left);
preorder(root.right);
}
中序遍历(左根右): 1.对根节点的左子树进行中序遍历 2.访问根节点 3.对根节点的右子树进行中序遍历
const inorder =(root) => {
if(!root) { return; }
inorder(root.left);
console.log(root.val);
inorder(root.right);
}
后序遍历(左右根):1.对根节点的左子树进行中序遍历 2.对根节点的右子树进行中序遍历 3.访问根节点
const postorder =(root) => {
if(!root) { return; }
postorder(root.left);
postorder(root.right);
console.log(root.val);
}
当然,以上三种遍历都可以采用非递归的方法实现
整体思路就是用stack去模拟函数调用的堆栈顺序从而实现
const preorder = (root) => {
if(!root) { return; }
//console.log(root.val);
const stack = [root];
while(stack.length) {
const n = stack.pop();
console.log(n.val);
//preorder(root.left);
//preorder(root.right);
//栈先进后出,所以right在left前入栈
if(n.right) {stack.push(n.right)};
if(n.left) {stack.push(n.left)};
}
};
中序遍历和后序遍历也是一样的思路,后序遍历就是先序遍历的算法的逆序(入栈顺序相反)
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;
}
};
const preorder = (root) => {
if(!root) { return; }
//console.log(root.val);
const stack = [root];
//配合入栈实现倒序
const outputstack = [];
while(stack.length) {
const n = stack.pop();
outputstack.push(n.val);
// 同样倒序
if(n.left) {stack.push(n.left)};
if(n.right) {stack.push(n.right)};
}
while(outputstack.length) {
const n = outputstack.pop();
outputstack.push(n.val);
}
};
6.2使用场景
①DOM树
②级联选择
③树形控件(目录结构)
6.3相关题目
104.二叉树的最大深度(深度优先遍历)
111.二叉树的最小深度(广度优先遍历)