We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
重启数据结构与算法,一些代码案例使用 Deno 与 TypeScript 实现,相关代码都在这里
在计算机科学中,树(英语:tree)是一种抽象数据类型(ADT)或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的
在内存中连续存放
通过指针联系在一起
以二叉树为例,同时具备数组取值的高效率还具有链式存储的增删效率
应用场景:常见业务需求像决策树、目录树、索引树、甚至简单的流程树都可以用二叉树来实现。
在计算机科学中,二叉树(英语:Binary tree)是每个节点最多只有两个分支(即不存在分支度大于2的节点)的树结构。通常分支被称作“左子树”或“右子树”。二叉树的分支具有左右次序,不能随意颠倒。
一棵深度为k,且有{\displaystyle 2^{\begin{aligned}k\end{aligned}}-1}个节点的二叉树,称为满二叉树(Full Binary Tree)。这种树的特点是每一层上的节点数都是最大节点数。
在一颗二叉树中,若除最后一层外的其余层都是满的,并且最后一层要么是满的,要么在右边缺少连续若干节点,则此二叉树为完全二叉树(Complete Binary Tree)。具有n个节点的完全二叉树的深度为{\displaystyle log_{2}n+1}。深度为k的完全二叉树,至少有{\displaystyle 2^{\begin{aligned}k-1\end{aligned}}}个节点,至多有{\displaystyle 2^{\begin{aligned}k\end{aligned}}-1}个节点。
从上面找规律,找到父的输出位置,就可以知道是哪种方式遍历
class HeroNode { public no: number; public name: string; public left: HeroNode | null = null; public right: HeroNode | null = null; constructor(no: number, name: string) { this.no = no; this.name = name; } public toString(): string { return `no = ${this.no}, name = ${this.name} `; } /** 前序 */ public preOrder() { console.log(this.toString()); // 父 // left 不为 null 递归执行 this.left?.preOrder(); // right 不为 null 递归执行 this.right?.preOrder(); } /** 中序 */ public infixOrder() { this.left?.infixOrder(); console.log(this.toString()); this.right?.infixOrder(); } /** 后序 */ public postOrder() { this.left?.postOrder(); this.right?.postOrder(); console.log(this.toString()); } } class BinaryTree { public root: HeroNode | null = null; public preOrder() { this.root?.preOrder(); } public infixOrder() { this.root?.infixOrder(); } public postOrder() { this.root?.postOrder(); } } window.onload = function main() { const root = new HeroNode(1, '宋江'); const node2 = new HeroNode(2, '卢俊义'); const node3 = new HeroNode(3, '吴用'); const node4 = new HeroNode(4, '公孙胜'); const node5 = new HeroNode(5, '关胜'); // 这里先使用手动创建树 root.left = node2; root.right = node3; node3.left = node5; node3.right = node4; const binaryTree = new BinaryTree(); binaryTree.root = root; binaryTree.preOrder(); // 1, 2, 3, 5, 4 binaryTree.infixOrder(); // 2, 1, 5, 3, 4 binaryTree.postOrder(); // 2, 5, 4, 3, 1 }
二叉树查找的思路与遍历一致,只是多了判断是否一致并且返回
class HeroNode { public no: number; public name: string; public left: HeroNode | null = null; public right: HeroNode | null = null; constructor(no: number, name: string) { this.no = no; this.name = name; } public toString(): string { return `no = ${this.no}, name = ${this.name} `; } /** 前序 */ public preOrder() { console.log(this.toString()); // 父 // left 不为 null 递归执行 this.left?.preOrder(); // right 不为 null 递归执行 this.right?.preOrder(); } /** 中序 */ public infixOrder() { this.left?.infixOrder(); console.log(this.toString()); this.right?.infixOrder(); } /** 后序 */ public postOrder() { this.left?.postOrder(); this.right?.postOrder(); console.log(this.toString()); } /** 前序搜索 */ public preOrderSearch(no: number): HeroNode | null { // 对比当前,正确直接返回 if (this.no === no) return this; let ret: HeroNode | null = null; // 如果 left 不为 null,递归遍历,赋值结果 ret if (this.left) { ret = this.left?.preOrderSearch(no); } // 结果 ret 不为 null,说明找到,直接返回 ret if (ret) return ret; // 如果 right 不为 null,递归遍历,赋值结果 ret if (this.right) { ret = this.right?.preOrderSearch(no); } // 将结果返回 return ret; } infixOrderSearch(no: number): HeroNode | null { let ret: HeroNode | null = null; if (this.left) { ret = this.left.infixOrderSearch(no); } if (ret) return ret; if (this.no === no) return this; if (this.right) { ret = this.right.infixOrderSearch(no); } return ret; } postOrderSearch(no: number): HeroNode | null { let ret: HeroNode | null = null; if (this.left) { ret = this.left.infixOrderSearch(no); } if (ret) return ret; if (this.right) { ret = this.right.infixOrderSearch(no); } if (ret) return ret; if (this.no === no) return this; return ret; } } class BinaryTree { public root: HeroNode | null = null; public preOrder() { this.root?.preOrder(); } public infixOrder() { this.root?.infixOrder(); } public postOrder() { this.root?.postOrder(); } public preOrderSearch(no: number): HeroNode | null { if (!this.root) return null; return this.root.preOrderSearch(no); } public infixOrderSearch(no: number): HeroNode | null { if (!this.root) return null; return this.root.infixOrderSearch(no); } public postOrderSearch(no: number): HeroNode | null { if (!this.root) return null; return this.root.postOrderSearch(no); } } window.onload = function main() { const root = new HeroNode(1, '宋江'); const node2 = new HeroNode(2, '卢俊义'); const node3 = new HeroNode(3, '吴用'); const node4 = new HeroNode(4, '公孙胜'); const node5 = new HeroNode(5, '关胜'); // 这里先使用手动创建树 root.left = node2; root.right = node3; node3.left = node5; node3.right = node4; const binaryTree = new BinaryTree(); binaryTree.root = root; binaryTree.preOrder(); // 1, 2, 3, 5, 4 // binaryTree.infixOrder(); // 2, 1, 5, 3, 4 // binaryTree.postOrder(); // 2, 5, 4, 3, 1 console.log(binaryTree.postOrderSearch(5)?.toString()); // no = 5, name = 关胜 }
我们规定
// HeroNode public delNode(no: number) { if (this.left?.no === no) { this.left = null; return; } if (this.right?.no === no) { this.right = null; return; } this.left?.delNode(no); this.right?.delNode(no); } // BinaryTree public delNode(no: number): void { if (this.root === null) { console.log(`当前树为空树`); return; } if (this.root.no === no) { this.root = null; return; } this.root.delNode(no); }
二叉树可以用数组或链接串列来存储,若是满二叉树就能紧凑排列而不浪费空间。
如果某个节点的索引为 n,假设根节点为 0 的情况下:
2 * n + 1
2 * n + 2
(n - 1) / 2
class ArrBinaryTree { private arr: number[]; constructor(arr: number[]) { this.arr = arr; } /** * 先序遍历 * @param index arr的下标 */ proOrder(index: number = 0) { if (this.arr?.length === 0) { console.log('数组为空'); return; } console.log(`${this.arr[index]}`); // 左递归遍历 if ((index * 2 + 1) < this.arr.length) { this.proOrder(index * 2 + 1); } // 右递归遍历 if ((index * 2 + 2 < this.arr.length)) { this.proOrder(index * 2 + 2); } } /** * 中序遍历 * @param index */ infixOrder(index: number = 0) { if (this.arr?.length === 0) { console.log('数组为空'); return; } // 左递归遍历 if ((index * 2 + 1) < this.arr.length) { this.infixOrder(index * 2 + 1); } console.log(`${this.arr[index]}`); // 右递归遍历 if ((index * 2 + 2 < this.arr.length)) { this.infixOrder(index * 2 + 2); } } /** * 后序遍历 * @param index */ postOrder(index: number = 0) { if (this.arr?.length === 0) { console.log('数组为空'); return; } // 左递归遍历 if ((index * 2 + 1) < this.arr.length) { this.postOrder(index * 2 + 1); } // 右递归遍历 if ((index * 2 + 2 < this.arr.length)) { this.postOrder(index * 2 + 2); } console.log(`${this.arr[index]}`); } } window.onload = function main() { const arrBinaryTree = new ArrBinaryTree([1, 2, 3, 4, 5, 6, 7]); // arrBinaryTree.proOrder(); // 1, 2, 4, 5, 3, 6, 7 // arrBinaryTree.infixOrder(); // 4, 2, 5, 1, 6, 3, 7 arrBinaryTree.postOrder(); // 4, 5, 2, 6, 7, 3, 1 }
在计算机科学中,二叉树添加了直接指向节点的前驱和后继的指针的二叉树称为线索二叉树。 一个二叉树通过如下的方法“穿起来”:所有原本为空的右(孩子)指针改为指向该节点在中序序列中的后继,所有原本为空的左(孩子)指针改为指向该节点的中序序列的前驱。
在计算机科学中,二叉树添加了直接指向节点的前驱和后继的指针的二叉树称为线索二叉树。
一个二叉树通过如下的方法“穿起来”:所有原本为空的右(孩子)指针改为指向该节点在中序序列中的后继,所有原本为空的左(孩子)指针改为指向该节点的中序序列的前驱。
class HeroNode { public no: number; public name: string; public left: HeroNode | null = null; public right: HeroNode | null = null; /** 左节点类型,0:子树,1:前驱 */ public leftType: 0 | 1 = 0; /** 右节点类型,0:子树,1:后继 */ public rightType: 0 | 1 = 0; constructor(no: number, name: string) { this.no = no; this.name = name; } public toString(): string { return `no = ${this.no}, name = ${this.name} `; } /** 前序 */ public preOrder() { console.log(this.toString()); // 父 // left 不为 null 递归执行 this.left?.preOrder(); // right 不为 null 递归执行 this.right?.preOrder(); } /** 中序 */ public infixOrder() { this.left?.infixOrder(); console.log(this.toString()); this.right?.infixOrder(); } /** 后序 */ public postOrder() { this.left?.postOrder(); this.right?.postOrder(); console.log(this.toString()); } /** 前序搜索 */ public preOrderSearch(no: number): HeroNode | null { // 对比当前,正确直接返回 if (this.no === no) return this; let ret: HeroNode | null = null; // 如果 left 不为 null,递归遍历,赋值结果 ret if (this.left) { ret = this.left?.preOrderSearch(no); } // 结果 ret 不为 null,说明找到,直接返回 ret if (ret) return ret; // 如果 right 不为 null,递归遍历,赋值结果 ret if (this.right) { ret = this.right?.preOrderSearch(no); } // 将结果返回 return ret; } infixOrderSearch(no: number): HeroNode | null { let ret: HeroNode | null = null; if (this.left) { ret = this.left.infixOrderSearch(no); } if (ret) return ret; if (this.no === no) return this; if (this.right) { ret = this.right.infixOrderSearch(no); } return ret; } postOrderSearch(no: number): HeroNode | null { let ret: HeroNode | null = null; if (this.left) { ret = this.left.infixOrderSearch(no); } if (ret) return ret; if (this.right) { ret = this.right.infixOrderSearch(no); } if (ret) return ret; if (this.no === no) return this; return ret; } public delNode(no: number) { if (this.left?.no === no) { this.left = null; return; } if (this.right?.no === no) { this.right = null; return; } this.left?.delNode(no); this.right?.delNode(no); } } class ThreadedBinaryTree { public root: HeroNode | null = null; public pre: HeroNode | null = null; public preOrder() { this.root?.preOrder(); } public infixOrder() { this.root?.infixOrder(); } public postOrder() { this.root?.postOrder(); } public preOrderSearch(no: number): HeroNode | null { if (!this.root) return null; return this.root.preOrderSearch(no); } public infixOrderSearch(no: number): HeroNode | null { if (!this.root) return null; return this.root.infixOrderSearch(no); } public postOrderSearch(no: number): HeroNode | null { if (!this.root) return null; return this.root.postOrderSearch(no); } public delNode(no: number): void { if (this.root === null) { console.log(`当前树为空树`); return; } if (this.root.no === no) { this.root = null; return; } this.root.delNode(no); } /** 中序线索化二叉树 */ public threadedNodes(node: HeroNode | null) { if (node === null) return; // 中序遍历的顺序为 左 =》当前 =》右 // 1. 左递归 this.threadedNodes(node.left); // 2. 当前 // 2.1. 当前前驱 if (node.left === null) { node.left = this.pre; node.leftType = 1; } // 2.2. 当前后继 if (this.pre !== null && this.pre.right === null) { this.pre.right = node; this.pre.rightType = 1; } // 记录上一个值 this.pre = node; // 3. 右递归 this.threadedNodes(node.right); } } window.onload = function main() { const root = new HeroNode(1, 'aa'); const node2 = new HeroNode(3, 'bb'); const node3 = new HeroNode(6, 'cc'); const node4 = new HeroNode(8, 'dd'); const node5 = new HeroNode(10, 'ee'); const node6 = new HeroNode(14, 'ff'); // 这里先使用手动创建树 root.left = node2; root.right = node3; node2.left = node4; node2.right = node5; node3.left = node6; const tree = new ThreadedBinaryTree(); tree.root = root; tree.threadedNodes(root); console.log(node5.left?.toString()); // 3 console.log(node5.right?.toString()); // 1 };
在原来二叉树的代码中加了一些代码
class HeroNode { ... /** 左节点类型,0:子树,1:前驱 */ public leftType: 0 | 1 = 0; /** 右节点类型,0:子树,1:后继 */ public rightType: 0 | 1 = 0; ... } class ThreadedBinaryTree { ... /** 记录上一个节点值 */ public pre: HeroNode | null = null; ... /** 中序线索化二叉树 */ public threadedNodes(node: HeroNode | null) { if (node === null) return; // 中序遍历的顺序为 左 =》当前 =》右 // 1. 左递归 this.threadedNodes(node.left); // 2. 当前 // 2.1. 当前前驱 if (node.left === null) { node.left = this.pre; node.leftType = 1; } // 2.2. 当前后继 if (this.pre !== null && this.pre.right === null) { this.pre.right = node; this.pre.rightType = 1; } // 记录上一个值 this.pre = node; // 3. 右递归 this.threadedNodes(node.right); } }
ThreadedBinaryTree 中加入方法
... /** 中序遍历线索化二叉树 */ public threadedList() { let node = this.root; while (node !== null) { // 找最左最底 while (node!.leftType === 0) { node = node!.left; } console.log(node?.toString()); // 往右边找 while (node!.rightType === 1) { node = node!.right; console.log(node?.toString()); } node = node!.right; } }
这里也是前面八大排序中遗留的地方,当时没有讲到二叉树所以先搁置,现在补齐
需要注意的点:
2*n+1
2*n+2
左->右,底->上
思路:
function heapSort(arr: number[]) { for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) { adjustHeap(arr, i, arr.length); } // 依次将大值沉底 for (let j = arr.length - 1; j > 0; j--) { const temp = arr[j]; arr[j] = arr[0]; arr[0] = temp; adjustHeap(arr, 0, j); } } function adjustHeap(arr: number[], index: number, len: number) { let temp = arr[index]; for (let k = index * 2 + 1; k < len; k = k * 2 + 1) { // 判断左子节点与右子节点 if (k + 1 < len && arr[k] < arr[k + 1]) { k++; } // 子节点大于父节点,不满足大顶堆条件,交换 if (arr[k] > temp) { arr[index] = arr[k]; index = k; } else { break; } } arr[index] = temp; // 交换赋值 } window.onload = function main() { const arr = [4, 6, 8, 5, 9]; // 测试构建大顶堆 // adjustHeap(arr, 1, arr.length); // adjustHeap(arr, 0, arr.length); // console.log(arr); // [ 9, 6, 8, 5, 4 ] heapSort(arr); console.log(arr); // [ 4, 5, 6, 8, 9 ] }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
重启数据结构与算法9
树的基础部分
为什么需要树这种数据结构
1、数组存储方式的分析
在内存中连续存放
2、链式存储方式的分析
通过指针联系在一起
3、树型存储方式的分析
以二叉树为例,同时具备数组取值的高效率还具有链式存储的增删效率
应用场景:常见业务需求像决策树、目录树、索引树、甚至简单的流程树都可以用二叉树来实现。
常用术语
二叉树的概念
满二叉树
完全二叉树
前中后序遍历二叉树
从上面找规律,找到父的输出位置,就可以知道是哪种方式遍历
前中后序查找二叉树
二叉树查找的思路与遍历一致,只是多了判断是否一致并且返回
二叉树的删除
我们规定
顺序存储二叉树
如果某个节点的索引为 n,假设根节点为 0 的情况下:
2 * n + 1
2 * n + 2
(n - 1) / 2
线索化二叉树
在原来二叉树的代码中加了一些代码
遍历线索化二叉树
ThreadedBinaryTree 中加入方法
线索化二叉树的实用场景(堆排序)
这里也是前面八大排序中遗留的地方,当时没有讲到二叉树所以先搁置,现在补齐
需要注意的点:
2*n+1
2*n+2
左->右,底->上
思路:
下期预告
The text was updated successfully, but these errors were encountered: