树是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家 谱、单位的组织架构、等等。 树是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就 是说它是根朝上,而叶朝下的。
树具有以下特点:
1.每个结点有零个或多个子结点;
2.没有父结点的结点为根结点;
3.每一个非根结点只有一个父结点;
4.每个结点及其后代结点整体上可以看做是一棵树,称为当前结点的父结点的一个子树;
-- 树的相关术语:
结点的度: 一个结点含有的子树的个数称为该结点的度;
叶结点: 度为0的结点称为叶结点,也可以叫做终端结点
分支结点: 度不为0的结点称为分支结点,也可以叫做非终端结点
结点的层次: 从根结点开始,根结点的层次为1,根的直接后继层次为2,以此类推
结点的层序编号:将树中的结点,按照从上层到下层,同层从左到右的次序排成一个线性序列,把他们编成连续的自然数。
树的度: 树中所有结点的度的最大值
树的高度(深度): 树中结点的最大层次
森林: m(m>=0)个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根 结点,森林就变成一棵树
孩子结点: 一个结点的直接后继结点称为该结点的孩子结点
双亲结点(父结点): 一个结点的直接前驱称为该结点的双亲结点
兄弟结点: 同一双亲结点的孩子结点间互称兄弟结点
-- 二叉树的基本定义:
完全二叉树: 叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树
-- 二叉树结点类API设计:
-- 代码 :
/**
* 二叉树
*/
public class BinaryTree<Key extends Comparable<Key>,Value> {
// 记录根结点
private Node root;
// 记录树中的元素个数
private int N;
// 构造方法
public BinaryTree() {
root = null;
N = 0;
}
// 获取树中的元素个数
public int size() {
return N;
}
// 向树中添加key-value
public void put(Key key,Value value) {
root = put(root,key,value);
}
// 向指定树x中添加key-value,并返回添加元素后新的树
private Node put(Node x,Key key,Value value) {
if(x == null) { // 当树中还没有任何结点
// 个数+1
N++;
// 构建新结点并返回
return new Node(key,value,null,null);
}
// 记录key和当前结点key的比较结果
int cmp = key.compareTo(x.key);
if(cmp > 0) {
// 如果新节点的key大于当前结点的key,继续找当前结点的右孩子
x.right = put(x.right,key,value);
}else if(cmp < 0) {
// 如果新节点的key小于当前结点的key,继续找当前结点的左孩子
x.left = put(x.left,key,value);
}else {
// 新结点的key等于当前结点的key,把当前结点value进行替换
x.value = value;
}
return x;
}
// 查询树中指定key对应的value
public Value get(Key key) {
return get(root,key);
}
// 从指定的树x中,查询key对应的value
public Value get(Node x,Key key) {
if(x == null) {
return null;
}
// 记录key和当前结点key的比较结果
int cmp = key.compareTo(x.key);
if(cmp > 0) {
// 如果当前要查询的key大于当前结点的key,则继续查找当前结点的右子结点
return get(x.right,key);
}else if(cmp < 0) {
// 如果当前要查询的key小于当前结点的key,则继续查找当前结点的左子节点
return get(x.left,key);
}else {
// 如果要查询的key等于当前结点的key,则返回当前结点的value
return x.value;
}
}
// 删除树中key对应的value
public void delete(Key key) {
root = delete(root,key);
}
// 删除指定树x中的key对应的value,并返回删除后的新树
public Node delete(Node x,Key key) {
if(x == null) {
return null;
}
// 记录key和当前结点key的比较结果
int cmp = key.compareTo(x.key);
if(cmp > 0) {
// 如果新节点的key大于当前结点的key,继续找当前结点的右子结点
x.right = delete(x.right,key);
}else if(cmp < 0) {
// 如果新节点的key小于当前结点的key,继续找当前结点的左子结点
x.left = delete(x.left,key);
}else {
// 如果新结点的key等于当前结点的key,当前结点就是要删除的结点
// 如果当前结点的右子树不存在,则直接返回当前结点的左子树
if(x.right == null) {
// 个数-1
N--;
return x.left;
}
// 如果当前结点的左子树不存在,则直接返回当前结点的右子树
if(x.left == null) {
// 个数-1
N--;
return x.right;
}
// 如果当前结点的左右子树都存在
// 找到右子树中最小的结点
Node minNode = x.right;
while (minNode.left != null) {
minNode = minNode.left;
}
// 删除右子树中最小的结点
Node n = x.right;
while (n.left != null) {
if(n.left.left == null) {
n.left = null;
}else {
n = n.left;
}
}
// 让被删除结点的左子树成为minNode的左子树
minNode.left = x.left;
// 让被删除结点的右子树成为minNode的右子树
minNode.right = x.right;
// 让被删除结点指向最小结点minNode
x = minNode;
// 个数-1
N--;
}
return x;
}
// 找出整个树中最小的键
public Key min() {
return min(root).key;
}
// 找出指定树x中最小的键所在的结点
private Node min(Node x) {
if(x.left != null) {
// 如果当前结点的左子结点不为空,则继续找当前结点的左子结点
return min(x.left);
}else {
return x;
}
}
// 找出整个树中最大的键
public Key max() {
return max(root).key;
}
// 找出指定树x中最大的键所在的结点
private Node max(Node x) {
if(x.right != null) {
// 如果当前结点的右子结点不为空,则继续找当前结点的右子结点
return max(x.right);
}else {
return x;
}
}
// 使用前序遍历,获取整个树中所有的键
public Queue<Key> preErgodic() {
Queue<Key> keys = new Queue<>();
preErgodic(root,keys);
return keys;
}
// 前序遍历顺序:先访问根结点,然后再访问左子树,最后访问右子树
// 使用前序遍历,把指定树x中的所有键放入keys队列中
private void preErgodic(Node x,Queue<Key> keys) {
if(x == null) {
return;
}
// 把当前结点的key放入队列中
keys.enqueue(x.key);
// 找到当前结点的左子树,如果不为空,递归遍历左子树
if(x.left != null) {
preErgodic(x.left,keys);
}
// 找到当前结点的右子树,如果不为空,递归遍历右子树
if(x.right != null) {
preErgodic(x.right,keys);
}
}
// 使用中序遍历,获取整个树中的所有键
public Queue<Key> midErgodic() {
Queue<Key> keys = new Queue<>();
midErgodic(root,keys);
return keys;
}
// 中序遍历顺序:先访问左子树,中间访问根节点,最后访问右子树
// 使用中序遍历,把指定树x中的所有键放入keys队列中
public void midErgodic(Node x,Queue<Key> keys) {
if(x == null) {
return;
}
// 找到当前结点的左子树,如果不为空,递归遍历左子树
if(x.left != null) {
midErgodic(x.left,keys);
}
// 把当前结点的key放入队列中
keys.enqueue(x.key);
// 找到当前结点的右子树,如果不为空,递归遍历右子树
if(x.right != null) {
midErgodic(x.right,keys);
}
}
// 使用后序遍历,获取整个树中的所有键
public Queue<Key> afterErgodic() {
Queue<Key> keys = new Queue<>();
afterErgodic(root,keys);
return keys;
}
// 后序遍历顺序:先访问左子树,再访问右子树,最后访问根节点
// 使用后序遍历,把指定树x中的所有键放入到keys队列中
private void afterErgodic(Node x,Queue<Key> keys) {
if(x == null) {
return;
}
// 找到当前结点的左子树,如果不为空,递归遍历左子树
if(x.left != null) {
afterErgodic(x.left,keys);
}
// 找到当前结点的右子树,如果不为空,递归遍历右子树
if(x.right != null) {
afterErgodic(x.right,keys);
}
// 把当前结点的key放入队列中
keys.enqueue(x.key);
}
//使用层序遍历得到树中所有的键
public Queue<Key> layerErgodic() {
Queue<Key> keys = new Queue<>();
Queue<Node> nodes = new Queue<>();
nodes.enqueue(root);
while (!nodes.isEmpty()) {
Node x = nodes.dequeue();
keys.enqueue(x.key);
if (x.left != null) {
nodes.enqueue(x.left);
}
if (x.right != null) {
nodes.enqueue(x.right);
}
}
return keys;
}
//计算整个树的最大深度
public int maxDepth() {
return maxDepth(root);
}
private int maxDepth(Node x) {
if(x == null) {
return 0;
}else {
int leftHeight = maxDepth(x.left);
int rightHeight = maxDepth(x.right);
return Math.max(leftHeight, rightHeight) + 1;
}
}
// 结点类
private class Node {
// 存储Key
private Key key;
// 存储Value
private Value value;
// 记录子结点
private Node left;
// 记录右孩子
private Node right;
// 构造方法
public Node(Key key, Value value, Node left, Node right) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
}
}
@ 以上内容属于个人笔记