树的基本概念,二叉树的种类及原理……
一 基本概念
树(Tree),是由 n(n>=1)个有限结点组成一个具有层次关系的集合。因为它根朝上,而叶朝下,看起来像一棵倒挂的树,所以把它叫做“树”。
树的特点:
- 每个结点有零个或多个子结点;
- 没有父结点的结点称为根结点;
- 每一个非根结点有且只有一个父结点;
- 除了根结点外,每个子结点可以分为多个不相交的子树。
树的相关概念
| 名词 | 说明 |
|---|---|
| 兄弟节点 | 拥有共同父节点的节点互称为兄弟节点 |
| 度 | 节点的分支数目 |
| 结点层数 | 从根结点开始算,根结点是第一层,依次往下 |
| 树的深度 | 树中结点的最大层数 |
| 节点深度 | 对任意节点x,其深度为根节点到x节点的路径长度。根节点深度为0,第二层节点深度为1 |
| 森林 | m 颗互不相交的树构成的集合就是森林 |
二 二叉树
二叉树的特点:
- 每个节点最多只有两个子节点,可以没有或者只有一个。
- 左子树和右子树是有顺序的,次序不能任意颠倒
二叉树的计算特性:
- 在二叉树的第i层上最多有2i-1 个节点(i>=1)。
- 二叉树中如果深度为k,那么最多有2k-1个节点(k>=1)。
n0=n2+1,n0 表示度数为 0 的节点数,n2 表示度数为 2 的节点数。- 在完全二叉树中,具有n个节点的完全二叉树的深度为 [log2n] + 1,其中 [log2n] 是向下取整。
若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点有如下特性:
- 若 i = 1,则该结点是二叉树的根,无双亲, 否则,编号为 [i/2] 的结点为其双亲结点;
- 若 2i > n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子结点;
- 若 2i+1>n,则该结点无右孩子结点, 否则,编号为2i+1 的结点为其右孩子结点。
二叉树的结构简单,存储效率较高。
1. 斜树
2. 完全二叉树
除了最下面一层节点外,其他各层的节点达到最大个数,只有最下层最右侧节点可以缺少子节点,而且最下层按照从左到右的顺序连续存在。
3. 满二叉树
除最下层节点外,其他节点都有两个子节点。
满二叉树一定是完全二叉树,但反过来不一定成立。
4. 搜索二叉树
其特点是:对树中的每一个节点而言,如果存在左子树,则左子树的值不能大于节点的值;如果存在右子树,右子树的值不能小于节点的值。
5. 平衡二叉树
平衡二叉树的特点是,根节点的左右子树深度之差的绝对值 <= 1;左右子树也是平衡二叉树;节点的左子树深度减去右子树的深度,值只能是 -1,1,0其中的一项。
三 二叉树的存储结构
1. 顺序存储
二叉树的顺序存储,是使用数组存储二叉树中的结点,并且结点的存储位置,就是数组的下标索引。
如下图:
当二叉树为完全二叉树时,结点数刚好填满数组。如果不是完全二叉树,顺序存储就会出现空间浪费的情况。因此,顺序存储一般适用于完全二叉树。
链表存储(二叉链表)
二叉树的每个结点最多有两个孩子,所以可以将结点数据结构定义为一个数据和两个指针的链表。
四 二叉树的遍历
二叉树的遍历思路是,从二叉树的根结点出发,按照某种次序依次访问二叉树中的所有结点,使得每个结点被访问一次,且仅被访问一次。
二叉树的访问次序分为四种:前序遍历 / 中序遍历 / 后序遍历 / 层序遍历。
1. 前序遍历
前序遍历,从二叉树的根结点出发,当第一次到达结点时输出结点数据,按照先左再右的方向访问。也就是按根节点-左孩子-右孩子的顺序。
如上图,输出顺序为:ABDHIEJCFG。
步骤分析:
从根节点 A 触发,第一次到达 A,输出 A;
向左访问,第一次访问 B, 输出 B;
向左访问,输出 D,输出 H;
H 无子节点,输出 H 后返回到 D;第二次访问 D,故不输出 D,进而访问 I,输出 I;
返回 D,返回 B,访问 E,输出 E;
输出 J……
C F G 同理。
2. 中序遍历
中序遍历,从二叉树的根结点出发,当第二次到达结点时输出结点数据,按照先左再右的方向访问。也就是按左孩子-根节点-右孩子的顺序。
如上图,输出顺序为:HDIBJEAFCG。
步骤分析:
第一次到达 A,不输出 A; 继续向左访问,第一次访问结点 B,不输出 B;继续到达 D,H;
到达 H,H 左子树为空,则返回到 H,此时第二次访问 H,故输出 H;
H 右子树为空,则返回至 D,此时第二次到达 D,故输出 D;
由 D 返回至 B,第二次到达 B,故输出 B;
其他同理。
3. 后序遍历
后序遍历,从二叉树的根结点出发,当第三次到达结点时输出结点数据,按照先左再右的方向访问。也就是按左孩子-右孩子-根节点的顺序。
如上图,后序遍历输出为:HIDJEBFGCA。
步骤分析:
第一次到达 A,不输出 A; 继续向左访问,第一次访问结点 B,不输出 B;继续到达 D,H;
到达 H,H 左子树为空,则返回到 H,此时第二次访问 H,不输出 H;
H 右子树为空,则返回至 H,此时第三次到达 H,故输出 H;
由 H 返回至 D,第二次到达 D,不输出 D;
继续访问至 I,I 左右子树均为空,故第三次访问 I 时,输出 I;
返回至 D,此时第三次到达 D,输出 D;
其他同理。
4. 层序遍历
层次遍历,顾名思义,就是按照树的层次自上而下的遍历。 如上图,层次遍历输出为:ABCDEFGHIJ
五 搜索二叉树的代码实现
// 节点类
class Node {
constructor(data, left, right) {
// 节点值
this.data = data;
// 左孩子
this.left = left;
// 有孩子
this.right = right;
}
// 获取节点值
getNode() {
return this.data;
}
}
// 二叉树类
class BST {
constructor() {
this.root = null;
}
// insert
insert(data) {
// 创建节点
let node = new Node(data, null, null);
// 如果跟节点为空,则作为跟节点
if (this.root === null) {
this.root = node;
} else {
let current = this.root;
let parent;
// 循环查找子节点,查到某个子节点为空时,将 node 赋值到此节点上
while (true) {
parent = current;
if (data < current.data) {
current = current.left;
// 没有左子节点
if (current === null) {
parent.left = node;
break;
}
} else {
current = current.right;
// 没有右子节点
if(current === null) {
parent.right = node;
break;
}
}
}
}
}
// 前序遍历: 根节点-左孩子-右孩子
preOrder (node) {
if (node !== null) {
console.log(node.getNode()); // 打印
this.preOrder(node.left);
this.preOrder(node.right);
}
}
// 中序遍历: 左孩子-根节点-右孩子
inOrder (node) {
if (node !== null) {
this.inOrder(node.left);
console.log(node.getNode());
this.inOrder(node.right);
}
}
// 后序遍历: 左孩子-右孩子-根节点
afterOrder (node) {
if (node !== null) {
this.afterOrder(node.left);
this.afterOrder(node.right);
console.log(node.getNode());
}
}
// 获取最小值 沿着根节点一直向左孩子查找,最后一个左孩子就是最小值
getMin () {
let current = this.root;
while (current.left !== null) {
current = current.left;
}
return current.data;
}
// 获取最大值 沿着根节点一直向右孩子查找,最后一个右孩子就是最大值
getMax () {
let current = this.root;
while (current.right !== null) {
current = current.right;
}
return current.data;
}
// 查找某个值
find (data) {
let current = this.root;
while (current !== null) {
// 如果当前节点的值等于要查找的元素,返回此节点
if (data === current.data) {
return current;
}
// 如果大于节点值 就顺着右孩子继续查找
if (data < current.data) {
current = current.left;
} else if (data > current.data) {
// 否则,顺着左孩子进行查找
current = current.right;
}
}
// 查找不到,返回空
return null;
};
// 删除元素
remove (data) {
this.root = this._removeNode(this.root, data); //将根节点转换
}
// 找到最小值
_getSmallest (node) {
while(node.left!=null){
node=node.left;
}
return node;
}
// 删除元素并且重新构建二叉树
_removeNode (node, data) {
if (node === null) {
return null;
}
if (data === node.data) {
// 如果没有子节点
if (node.right === null && node.left === null) {
return null;
}
// 如果没有左子节点
if (node.left === null) {
return node.right;
}
// 如果没有右子节点
if (node.right === null) {
return node.left;
}
// 如果有两个节点 找到最小的右节点
let tempNode = this._getSmallest(node.right);
node.data = tempNode.data;
node.right = this._removeNode(node.right, tempNode.data);
return node;
} else if (data < node.data){
node.left = this._removeNode(node.left, data);
return node;
} else {
node.right = this._removeNode(node.right, data);
return node;
}
}
}
六 其他树结构
霍夫曼树 / AVL树 / 红黑树 / 伸展树 / 替罪羊树 / B-tree / B+树 ……
参考:yancy__ 、 天山老霸王 、 MrHorse1992