二叉树和二叉查找树
描述:
- 树是计算机科学中经常用到的一种数据结构。
- 树是一种非线性的数据结构,以分层的方式存储数据。
- 树被用来存储具有层级关系的数据,比如文件系统中的文件。
选择树而不是那些基本的数据结构,是因为:
- 在二叉树上进行查找非常快(而在链表上查找则不是这样)。
- 为二叉树添加或删除元素也非常快(而对数组执行添加或删除操作则不是这样)。
一、树的定义
相关术语:
- 树的结点(node):包含一个数据元素及若干指向子树的分支;
- 孩子结点(child node):结点的子树的根称为该结点的孩子;
- 父结点:B 结点是 A 结点的孩子,则 A 结点是 B 结点的双亲;
- 兄弟结点:同一双亲的孩子结点; 堂兄结点:同一层上结点;
- 祖先结点: 从根到该结点的所经分支上的所有结点
- 子孙结点:以某结点为根的子树中任一结点都称为该结点的子孙
- 结点层:根结点的层定义为 1;根的孩子为第二层结点,依此类推;
- 树的深度:树中最大的结点层
- 结点的度:结点子树的个数
- 树的度: 树中最大的结点度。
- 叶子结点:也叫终端结点,是度为 0 的结点;
- 分枝结点:度不为 0 的结点;
- 有序树:子树有序的树,如:家族树;
- 无序树:不考虑子树的顺序;
而二叉树
是一种特殊的树,它的子节点个数不超过两个。二叉树具有一些特殊的计算性质, 使得在它们之上的一些操作异常高效。
二、叉树和二叉查找树
二叉树每个节点的子节点不允许超过两个。通过将子节点的个数限定为 2,可以写出高效的程序在树中插入、查找和删除数据。
一个父节点的两个子节点分别称为左节点
和右节点
。
二叉查找树——确定子节点非常重要。
二叉查找树是一种 特殊的二叉树,相对较小的值保存在左节点中,较大的值保存在右节点中。
2.1 创建二叉查找树
2.1.1 定义的第一个对象就是表示一个节点的Node
function Node(data, left, right) {
this.data = data; //当前值
this.left = left; // 左节点
this.right = right; // 右节点
this.show = show;
}
function show() {
return this.data;
}
- 设根节点为当前节点。
- 如果待插入节点保存的数据小于当前节点,则设新的当前节点为原节点的左节点;反之,执行第 4 步。
- 如果当前节点的左节点为 null,就将新的节点插入这个位置,退出循环;反之,继续执行下一次循环。
- 设新的当前节点为原节点的右节点。
- 如果当前节点的右节点为 null,就将新的节点插入这个位置,退出循环;反之,继续执行下一次循环。
2.1.2 定义包含完整树结构、插入的BST类
function Node(data, left, right) {
this.data = data;
this.left = left;
this.right = right;
this.show = show;
}
function show() {
return this.data;
}
function BST() {
this.root = null;
this.insert = insert;
this.inOrder = inOrder;
}
function insert (data) {
var n = new Node(data, null, null)
if (this.root == null) {
this.root = n
} else {
var current = this.root
var parent // 定义当前节点为父节点
while (true) {
parent = current
if (data < current.data) {
current = current.left; //如果当前节点的左节点为空,再插入
if (current == null) {
parent.left = n
break;
}
} else {
current = current.right; //如果当前节点的右节点为空,再插入
if (current == null) {
parent.right = n
break;
}
}
}
}
}
2.2 遍历二叉查找树
现在 BST
类已经初步成型,但是操作上还只能插入节点,我们需要有能力遍历 BST
的树。
- **中序遍历:**按照节点上的键值,以升序访问
BST
上的所有节点。先访问左子树
,再访问根节点
,最后访问右子树
。 - **先序遍历:**先访问
根节点
,然后以同样方式访问左子树
和右子树
。 - **后序遍历:**先访问叶子节点,从
左子树
到右子树
,再到根节点
。
中序遍历的递归思路:
function inOrder(node) {
if (!(node == null)) {
inOrder(node.left);
node.show()
inOrder(node.right);
}
}
中序遍历的访问路径
先序遍历的递归思路:
function preOrder(node) {
if(!(nide == null)){
node.show()
preOrder(node.left)
preOrder(node.right)
}
}
先序遍历的访问路径
后序遍历的递归思路:
function preOrder(node) {
if(!(nide == null)){
node.show()
preOrder(node.left)
preOrder(node.right)
}
}
后序遍历的访问路径
三种遍历结果对比:
var nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);
类型 | 结果 |
---|---|
中序:inOrder(nums.root); | 3 16 22 23 37 45 99 |
先序:preOrder(nums.root); | 23 16 3 22 45 37 99 |
后序:postOrder(nums.root); | 3 22 16 37 99 45 23 |
2.3 查询二叉树上的节点
对BST
通常有下列三种类型的查找:
- (1) 查找给定值;
- (2) 查找最小值;
- (3) 查找最大值:
2.3.1 查找最小值和最大值
查找 BST
上的最小值和最大值非常简单:
- 因为
较小的值
总是在左子节点上,所以只需要遍历左子树,直到找到最后一个节点。 - 因为
较大的值
总是在右子节点上,所以只需要遍历右子树,直到找到最后一个节点。
/**
* @description: 查找最小值
* @param {type}
* @return:
*/
function getMin() {
var current = this.root;
while (current.left != null) {
current = current.left
}
return current.data
}
/**
* @description: 查找最大值
* @param {type}
* @return:
*/
function getMax() {
var current = this.root;
while (current.right != null) {
current = current.right
}
return current.data
}
2.3.2 查找某个值是否在树上
在 BST
上查找给定值,需要比较该值和当前节点上的值的大小。通过比较,就能确定如果给定值不在当前节点时,该向左遍历还是向右遍历。
/**
* @description: 查找指定值
* @param {type}
* @return:
*/
function find(data) {
var current = this.root;
while(current != null) {
if(current.data = data) return current;
else if(data < current.data){
current = current.left
}
else{
current = current.right
}
}
return null
}
2.4 删除二叉树上的节点
从 BST
上删除节点的操作最复杂,毕竟你删除的节点包含下面几种特点:
- 没有子节点
- 只有一个子节点
- 包含两个子节点的节点
function remove(data) {
root = removeNode(this.root, data);
}
function removeNode(node, data) {
if (node == null) {
return null;
}
if (data == node.data) {
// 没有子节点的节点
if (node.left == null && node.right == null) return null;
// 没有左子节点的节点
if (node.left == null) return node.right;
// 没有右子节点的节点
if (node.right == null) return node.left;
// 有两个子节点的节点
var tempNode = getMin(node.right);
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;
}
二叉树的基本知识就总结到这里,这之后就是不断加深对它的理解,知道可以随意的运用这种数据结构去解决合适的问题,so,刷题去吧~