数据结构 - Javascirpt
准备工作
全程使用 VScode + Typescript
调试环境搭建
Vscode typescript 配置
.vscode/launch.json
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "ts launch",
"runtimeExecutable": "ts-node",
"runtimeArgs": [],
"args": [
// 当前文件
"${relativeFile}"
]
}
]
}
.tsconfig.json
...
// 需要打开
sourceMap: map
...
代码格式化
EditorConfig
+ Prettier
安装插件
.editorconfig
# EditorConfig is awesome: https://EditorConfig.org
root = true
# 作用于所有的 js、ts 文件
[*.{js,ts}]
# 间隔类型
indent_style = space
# 间隔大小
indent_size = 4
# 换行符
end_of_line = lf
# 字符集边编码
charset = utf-8
trim_trailing_whitespace = true
# 在行尾插入空行
insert_final_newline = true
# 单、双引号问题
quote_type = single
prettier.config.js
module.exports = {
semi: false,
useTabs: true,
indent_size: 4,
arrowParens: 'avoid',
trailingComma: 'none'
}
栈
后进先出
使用场景
- 所有用到后进先出特点的场景
- JavaScript 函数执行栈
实例
队列
与栈相似,但是队列是一头进,另一头出 (先进先出 )
使用场景
- Javascript 异步任务队列
实例
链表
元素之间无顺序可言,通过元素前后指针相连,连成一条链表
使用场景
遍历链表
树
树的类型
二叉树:完美二叉树、完全二叉树、完满二叉树
AVL树、红黑树、B树、B+树
树的遍历
准备了一颗以上的一棵普通的树
// 使用 Object 来实现树结构
type Tree = {
value: string
children: Tree[]
}
const tree: Tree = {
value: 'a',
children: [
{
value: 'b',
children: [
{
value: 'd',
children: []
},
{
value: 'e',
children: []
}
]
},
{
value: 'c',
children: [
{
value: 'f',
children: []
},
{
value: 'g',
children: []
}
]
}
]
}
深度遍历
一直往树的底部去遍历,尽可能深的搜索树的分支
按照上图的顺序就是 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7
// 使用上述的常量 tree
// 递归版
// 树的深度遍历
// Tree 为 type, tree 是数据结构
import { Tree, tree } from './tree'
// 对树的结构按顺序进行遍历
const dft = (tree: Tree) => {
console.log(tree.value)
tree.children.forEach(dft)
}
dft(tree)
// a -> b -> d -> e -> c -> f -> g
广度遍历
先访问离根节点最近的结点
按照上图的顺序就是 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7
// 使用上述的常量 tree
// 广度遍历
import { Tree, tree } from './tree'
const gft = (tree: Tree) => {
// 使用队列数据接口
// 先把头结点推入队列
// 弹出头结点,然后遍历子节点
// 子节点的头结点也推入队列中,以此类推
const q = [tree]
while (q.length) {
// 弹出队列首项目
const node = q.shift()
if (!node) return
console.log(node.value)
q.push(...node.children)
}
}
gft(tree)
// a -> b -> c -> d -> e -> f -> g
二叉树
树中每个节点最多只能有两个子节点
这也算是一棵二叉树
Object 表达式
type BTree = {
value: string
left?: BTree
right?: BTree
}
const Btree: BTree = {
value: 'a',
left: {
value: 'b',
left: {
value: 'd'
},
right: {
value: 'e'
}
},
right: {
value: 'c',
left: {
value: 'f'
},
right: {
value: 'g'
}
}
}
二叉树的遍历
二叉树遍历有三种遍历方法:先序遍历,中序遍历,后序遍历
先序遍历(递归)
遍历步骤
- 访问根节点
- 对根节点的左子树进行先序遍历
- 对根节点的右子树进行先序遍历
上面的二叉树先序遍历结果为:a -> b -> d -> e -> c -> f -> g
import { Btree, BTree } from './tree'
const preorder = (tree: BTree) => {
console.log(tree.value)
tree.left && preorder(tree.left)
tree.right && preorder(tree.right)
}
preorder(Btree)
中序遍历(递归)
遍历步骤
- 对根节点的左子树进行中序遍历
- 访问根节点
- 对根节点的右子树进行中序遍历
上面的二叉树先序遍历结果为:d -> b -> e -> a -> f -> c -> g
import { Btree, BTree } from './tree'
const inorder = (tree: BTree) => {
tree.left && inorder(tree.left)
console.log(tree.value)
tree.right && inorder(tree.right)
}
inorder(Btree)
后序遍历(递归)
遍历步骤
-
对根节点的左子树进行后序遍历
-
对根节点的右子树进行后序遍历
-
访问根节点
每个整体的子树的中序遍历顺序如上图所示
上面的二叉树先序遍历结果为:d -> e -> b -> f -> g -> c -> a
import { Btree, BTree } from './tree'
const postorder = (tree: BTree) => {
tree.left && postorder(tree.left)
tree.right && postorder(tree.right)
console.log(tree.value)
}
postorder(Btree)
先序遍历(非递归)
思路: 借助栈实现
const preorder = (tree: BTree) => {
const stack = [tree]
while (stack.length) {
const node = stack.pop()
if (!node) break
console.log(node.value)
// 借助栈 后进先出 的特性
if (node.right) stack.push(node.right)
if (node.left) stack.push(node.left)
}
}
中序遍历(非递归)
思路:
借助栈实现,每碰到一个节点就收集一次左节点并压入栈中,直到最后一个左节点。
然后开始出栈,再对右子节点进行收集一次
const inorder = (tree: BTree) => {
// 先搜集所有的左子结点(包含根结点)
const stack: BTree[] = []
let p: BTree | null = tree
while (stack.length || !!p) {
// 每次都收集一次左结点
while (p) {
stack.push(p)
p = p.left ? p.left : null
}
const node = stack.pop()
if (!node) break
console.log(node.value)
p = node.right ? node.right : null
}
}
后序遍历(非递归)
思路: 将先序遍历翻转
const postorder = (tree: BTree) => {
const stack = [tree]
const outputStack = []
while (stack.length) {
const node = stack.pop()
if (!node) break
outputStack.push(node)
if (node.left) stack.push(node.left)
if (node.right) stack.push(node.right)
}
while (outputStack.length) {
const node = outputStack.pop()
if (!node) break
console.log(node.value)
}
}
二叉树的最大深度(练习)
Leetcode.101
/**
* 二叉树的最大深度
* 使用树形结构的深度遍历
* 使用变量保存最大深度
*/
function maxDepth(root: TreeNode | null): number {
let max = 0
if (!root) return max
// 深度遍历二叉树
const dft = (root: TreeNode, level: number) => {
if (!root) return
if (!root.left && !root.right) {
max = Math.max(max, level)
}
root.left && dft(root.left, level + 1)
root.right && dft(root.right, level + 1)
}
dft(root, 1)
return max
}
二叉树的最小深度(练习)
Leetcode.111
/**
* 推荐使用广度遍历,而不使用深度遍历。
* 广度遍历可以减少遍历的次数,更快的发现叶子节点
* 遇到叶子节点则返回
*/
function minDepth(root: TreeNode | null): number {
if (!root) return 0
let min = 1
const queue: [TreeNode, number][] = [[root, min]]
while (queue.length) {
const [node, level] = queue.shift()!
if (!node.left && !node.right) return (min = level)
if (node.left) queue.push([node.left, level + 1])
if (node.right) queue.push([node.right, level + 1])
}
return min
}
二叉树的层序遍历(练习)
Leetcode.102
/**
* 01
* 使用广度遍历
* 找出对应的子节点和层级,按照层级往数组的推入
*/
function levelOrder(root: TreeNode | null): number[][] {
if (!root) return []
const q: [TreeNode, number][] = [[root, 0]]
const levelNodeList: [number][] = []
while (q.length) {
const [node, level] = q.shift()!
if (levelNodeList[level]) {
levelNodeList[level].push(node.val)
} else {
levelNodeList[level] = [node.val]
}
// 推入新节点
if (node.left) q.push([node.left, level + 1])
if (node.right) q.push([node.right, level + 1])
}
return levelNodeList
}
/**
* 02
* 使用广度遍历
* 区分新节点与老节点(两者都是同一层级的节点)
* 时间复杂度 (O)n : 全遍历树的所有节点
* 空间复杂度(O)n : 使用了递归
*/
function levelOrder(root: TreeNode | null): number[][] {
if (!root) return []
const q = [root]
const res: Array<number[]> = []
while (q.length) {
// 获取队列的长度
let len = q.length
res.push([])
while (len--) {
const node = q.shift()!
res[res.length - 1].push(node.val)
// 推入新节点
if (node.left) q.push(node.left)
if (node.right) q.push(node.right)
}
}
return res
}
路径总和(练习)
Leetcode.112
/**
* 采用深度遍历
* 遇到叶子节点则进行判断总和是否与期望值相等
* 时间复杂度 (O)n : 全遍历树的所有节点
* 空间复杂度(O)n : 使用了递归
*/
function hasPathSum(root: TreeNode | null, sum: number): boolean {
if (!root) return false
let res = false
const dft = (root: TreeNode, value: number) => {
if (!root.left && !root.right && value === sum) {
res = true
}
if (root.left) dft(root.left, root.left.val + value)
if (root.right) dft(root.right, root.right.val + value)
}
dft(root, root.val)
return res
}
遍历 JSON 对象 (练习)
const json = {
a: {
b: [1, 2, 3],
c: {
name: 'hello',
val: 'world'
}
},
d: {
color: ['red', 'green', 'blue']
}
}
// 凡科面试题 找出来做一下
// 拷贝 toString 方法
const checkType = Function.prototype.call.bind(Object.prototype.toString)
const typeAbout = (data: any) => {
return (checkType(data) as string).substring(8).replace(']', '')
}
const jsonDfs = (n: any, path: string) => {
console.log(n, path)
debugger
if (typeAbout(n) !== 'Object') return
Reflect.ownKeys(n).forEach(key => {
path += `.${String(key)}`
jsonDfs(Reflect.get(n, key), path)
})
}
jsonDfs(json, 'json')
后台管理的侧边栏菜单
Vue 可采用深度遍历 router 菜单