树
树是一种非线性的数据结构,以分层的方式存储数据。
树被用来存储具有层级关系的数据。比如文件系统中的文件
树还被用来存储有序列表。
树的定义
树由一组以边连接的节点组成。
公司的组织结构图就是一个树的例子
每个方框都是一个节点,节点代表了该组织中的各个职位。
连接方框的线叫做边,边描述了各职位间的关系。
关于树的描述
一棵树最上面的节点称为【根节点】
如果一个节点下面连接多个节点,那么该节点称为【父节点】,它下面的节点称为【子节点】
一个节点可以有0个、1个或多个子节点。
没有任何子节点的节点称为【叶子节点】
描述:
1、沿着一组特定的边,可以从一个节点走到另一个与它不直接相连的节点。从一个节点到另一个节点的这一组边称为【路径】(图中虚线部分)以某种特定顺序访问树中所有的节点称为【树的遍历】。
2、树可以分为几个【层次】,根节点是第0层,它的子节点是第一层,子节点的子节点是第二层,以此类推。
3、树中的任何一层的节点可以都看作是【子树的根】,该子树包含【根节点的子节点】,【子节点的子节点】等。
4、定义树的层次就是树的【深度】。
5、每个节点都有一个与之相关的值,该值有时被称为【键】
二叉树
二叉树是一种特殊的树。
它的子节点数不超过两个。
一个父节点的两个子节点分别称为【左节点】和【右节点】
左子节点比父节点小,右子节点比父节点大
即使某节点只有一个子节点,也是区分左右的。
在二叉树上查找,为二叉树添加或删除元素都非常快。
二叉查找树
二叉查找树是一种特殊的二叉树,相对较小的值保存在左节点中,较大的值保存在右节点中。
实现二叉查找树
二叉查找树由节点组成,所以要先定一个一个Node构造函数
// Node
function Node (data, left, right) {
this.data = data;
this.count = 1;
this.left = left;
this.right = right;
this.show = show;
}
// 返回保存在节点中的数据
function show () {
return this.data;
}
创建一个类,表示二叉查找树(BST)
// 首先让类只包含一个数据成员:
// 一个表示二叉查找树的根节点的Node对象
// 该类的构造函数将根节点初始化为null,以此创建一个空节点
// BST类
function BST () {
this.root = null;
this.insert = insert; // 插入方法
this.inOrder = inOrder; // 遍历方法
this.preOrder = preOrder;
this.inOrder = inOrder;
this.postOrder = postOrder;
this.getMin = getMin;
this.getMax = getMax;
this.find = find;
this.remove = remove;
this.removeNode = removeNode;
this.update = update;
}
插入方法 用来向树中插入新节点。
- 首先检查BST中是否有根节点,如果没有,那么是棵新树,该节点就是根节点,这个方法到此结束
- 若带插入节点不是根节点,那么需要遍历BST,找到插入的适当位置。
function insert (data) {
let n = new Node(data, null, null);
// 若没有根节点,这证明是新树
if (this.root === null) {
this.root = n;
} else {
// 假设根节点为当前节点
let current = this.root
let parent;
while (true) {
parent = current;
// 判断当前带插入节点是否小于当前节点
// 若小于并且当前节点的左节点为null,则插入并退出循环
// 否则进入下一次循环
if (data < current.data) {
current = current.left;
if (current === null) {
parent.left = n;
break;
}
} else {
// 判断当前带插入节点是否大于当前节点
// 若大于并且当前节点的右节点为null,则插入并退出循环
// 否则进入下一次循环
current = current.right;
if (current === null) {
parent.right = n;
break;
}
}
}
}
}
connst bst = new BST();
bst.insert(23);
bst.insert(45);
bst.insert(16);
bst.insert(37);
bst.insert(3);
bst.insert(99);
bst.insert(22);
遍历二叉查找树
遍历BST有三种方式:
- 先序遍历:先访问根节点,然后以同样的方式访问左子树和右子树
- 中序遍历:按照节点上的键值,以升序访问BST上的所有节点
- 后序遍历:先访问叶子节点,从左子树到右子树,再到根节点
先序遍历
function preOrder (node) {
if (!node) return [];
const resArr = [];
resArr.push(node.show());
resArr.push(...preOrder(node.left))
resArr.push(...preOrder(node.right))
return resArr;
}
const preNums = preOrder(bst.root);
// 结果:[ 23, 16, 3, 22, 45, 37, 99 ]
中序遍历
function inOrder (node) {
if (!node) return []
const resArr = []
resArr.push(...inOrder(node.left))
resArr.push(node.show());
resArr.push(...inOrder(node.right))
return resArr
}
const inNums = inOrder(bst.root);
// 结果:[ 3, 16, 22, 23, 37, 45, 99 ]
后序遍历
function postOrder (node) {
if (!node) return [];
const resArr = [];
resArr.push(...postOrder(node.left));
resArr.push(...postOrder(node.right));
resArr.push(node.show());
return resArr;
}
const postNums = postOrder(bst.root);
// 结果:[ 3, 22, 16, 37, 99, 45, 23 ]
在二叉查找树上查找
查找最小值 由于二叉查找树的左节点永远小于当前节点,所以只需要一直遍历左子树,直到找到最后一个节点为止
function getMin (node) {
let current = node || this.root;
while (current.left !== null) {
current = current.left;
}
return current.data;
}
const min = node ? current : nums.getMin();
// 结果: 3
查找最大值 由于二叉查找树的右节点永远大于当前节点,所以只需要一直遍历右子树,直到找到最后一个节点为止
function getMax (node) {
let current = node || this.root;
while (current.right !== null) {
current = current.right;
}
return current.data
}
const max = node ? current || nums.getMax();
// 结果: 99
查找给定值 查找给定值时,需要和当前节点进行比较,若不是当前节点则可以确定是向左遍历还是向右遍历
function find (val) {
let current = this.root;
while (current !== null) {
if (current.data === val) {
return current;
} else if (val < current.data) {
current = current.left
} else {
current = current.right;
}
}
return null;
}
const res = find(45);
// 结果
//Node {
// data: 45,
// left:
// Node { data: 37, left: null, right: null, show: [Function: show] },
// right:
// Node { data: 99, left: null, right: null, show: [Function: show] },
// show: [Function: show] }
从二叉树上删除节点
- 从二叉树上删除节点的操作最复杂,复杂程度取决于删除那个节点。为了管理删除操作的复杂度,方法上使用递归操作。
- 第一步是判断当前节点是否包含待删除的数据,若包含,则删除。若不包含,则比较当前节点上的数据和带删除的数据,来决定向左还是向右移动
- 若待删除的解释安是叶子节点,那么只需要将从父节点指向它的指针指向null即可。
- 若待删除节点只包含一个节点,那么原本指向它的节点就要做调整,使其指向其它的子节点
- 若待删除的节点包含两个子节点,做法有两种:要么查找待删除节点左子树上的最大值,要么查找其右子树上的最小值。
// 移除节点 只是简单的接收带删除数据,调用removeNode删除它
function remove (data) {
let root = removeNode(this.root, data);
}
// 删除
function removeNode (node, data) {
if (node === null) return null;
// 待删除节点就是当前节点
if (data === node.data) {
// 没有子节点的节点
if (!node.left && !node.right) {
return null;
}
// 没有左子节点的节点
if (!node.left) {
return node.right;
}
// 没有右子节点的节点
if (!node.right) {
return node.left;
}
// 有两个子节点的节点
// 找到右子树上的最小值
let tempNode = getMin(node.right);
console.log('tempNode', tempNode);
node.data = tempNode.data;
node.right = removeNode(node.right, tempNode.data);
return node;
}
else if (data < node.data) {
node.left = removeNode(node.left, data);
return node
}
else {
node.right = removeNode(node.right, data);
return node;
}
}
console.log(nums.preOrder(nums.root));
// 删除前打印: [ 23, 16, 3, 22, 45, 37, 99 ]
nums.remove(16);
console.log('remove', nums.preOrder(nums.root))
// 删除后打印: [ 23, 22, 3, 45, 37, 99 ]
二叉树的应用
BST的一个用途是记录一组数据集中出现的次数。
比如可以使用BST记录考试成绩的分布。
给定一组考试成绩,可以写一段程序将它们加入一个BST,如果某成绩尚未在BST中出现,就将其加入BST,若已经出现,就将出现的次数+1。
首先修改node对象定义,为其增加记录成绩出现次数的成员
function Node (data, left, right) {
this.data = data;
this.count = 1;
this.left = left;
this.right = right;
this.show = show;
}
当向BST插入一条成绩(Node对象)时,将出现的频次设为1 此时BST的insert()方法还能正常工作,但是当次数增加时,就需要一个新方法更新BST中的节点。
function update (data) {
let grade = this.find(data);
grade.count++;
return grade;
}
测试程序 首先定义一个可以增加一些随机产生成绩及显示它们的函数
function genArray (len) {
let arr = [];
for (let i = 0; i < len; i++){
arr[i] = Math.floor(Math.random() * Number(len + 1));
}
return arr;
}
定义BST类
let grades = genArray(100);
// prArray(grades);
let gradedistro = new BST();
for (let i = 0; i < grades.length; i++){
let g = grades[i];
let grade = gradedistro.find(g);
if (!grade) {
gradedistro.insert(g);
} else {
gradedistro.update(g);
}
}
查看结果
注:由于时随机数,不能保证每次输入同一个数字都有结果出现。别问我是怎么知道的!!!!
console.log('45', gradedistro.find(45).count);
// 结果: 45 3