算法与数据结构(五):树之二叉树
博主会对算法与数据结构会不断进行更新,敬请期待,如有什么建议,欢迎联系。
树是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家谱、单位的组织架构、等等。树是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
树的相关术语:
结点的度:
一个结点含有的子树的个数称为该结点的度;
叶结点:
度为0的结点称为叶结点,也可以叫做终端结点
分支结点:
度不为0的结点称为分支结点,也可以叫做非终端结点
结点的层次:
从根结点开始,根结点的层次为1,根的直接后继层次为2,以此类推
结点的层序编号:将树中的结点,按照从上层到下层,同层从左到右的次序排成一个线性序列,把他们编成连续的自然数。
树的度:树中所有结点的度的最大值
树的高度(深度):树中结点的最大层次
森林:m(m>=0)个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根结点,森林就变成一棵树
孩子结点:
一个结点的直接后继结点称为该结点的孩子结点
双亲结点(父结点):
一个结点的直接前驱称为该结点的双亲结点
兄弟结点:
同一双亲结点的孩子结点间互称兄弟结点
二叉树的实现细节:
- 二叉树是树结构中特殊的一种树,二叉树的每个节点最多有2个子节点,是数据结构中使用最为普遍的一种数据结构
- 其中二叉树的遍历,博主实现了前序遍历,中序遍历,后序遍历和层序遍历
- 实现了树最大深度的计算,此处使用了队列方面的知识,具体实现思路感觉是比较重要的一种算法思想,值得深思。
二叉树的实现代码如下:
package com.victor.tree;
import com.victor.linear.Queue;
/**
* @description: 二叉树
* @author: victor
*/
public class BinaryTree<K extends Comparable<K>, V> {
//根节点
private Node root;
//树中元素的个数
private int N;
/**
* 内部类Node节点
*/
private class Node {
public K key;
public V value;
public Node left;
public Node right;
public Node(K key, V value, Node left, Node right) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
}
/**
* 获取树种元素的个数
*
* @return 元素的个数
*/
public int size() {
return N;
}
/**
* 向树种添加元素
*/
public void put(K key, V value) {
root = put(root, key, value);
}
/**
* 向指定的树中添加元素
*
* @param x 指定的子树
* @param key 键
* @param value 值
* @return 插入后的新树
*/
private Node put(Node x, K key, V value) {
//根节点为null时
if (x == null) {
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的值,替换当前节点
x.value = value;
}
//返回子树
return x;
}
/**
* 通过key获取value
*
* @param key 键
* @return 值
*/
public V get(K key) {
return get(root, key);
}
/**
* 查找子树x,通过key获取value
*
* @param x 子树
* @param key 键
* @return 值
*/
private V get(Node x, K key) {
//如果子树为null,则返回null
if (x == null) {
return null;
}
//key与根节点的key进行对比
int cmp = key.compareTo(x.key);
if (cmp > 0) {
//key的值大于根节点key的值,获得右边子树key的值
return get(x.right, key);
} else if (cmp < 0) {
//key的值小于根节点key的值,获得左边子树key的值
return get(x.left, key);
} else {
//key的值等于根节点key的值,直接返回value
return x.value;
}
}
/**
* 通过key删除
*
* @param key 键
*/
public void delete(K key) {
delete(root, key);
}
/**
* 删除子树x中的键值对
*
* @param x 子树x
* @param key 键
* @return 删除后的新树
*/
private Node delete(Node x, K key) {
//子树为null时返回null
if (x == null) {
return null;
}
//key与根节点的key进行对比
int cmp = key.compareTo(x.key);
if (cmp > 0) {
//key的值大于根节点key的值,将删除key-value后的子树变成x的右子树
x.right = delete(x.right, key);
} else if (cmp < 0) {
//key的值小于根节点key的值,将删除key-value后的子树变成x的左子树
x.left = delete(x.left, key);
} else {
N--; //先让树的值--,这样不用每个if都要写了
//key的值等于根节点key的值,删除当前子树的根节点
//子树根节点没有左子树时
if (x.left == null) {
return x.right;
}
//子树根节点没有右子树时
if (x.right == null) {
return x.left;
}
//找到右子树最小的值(或者找到左子树最大的值)
Node minNode = x.right;
Node fatherMinNode = x; //最小值的父节点
while (minNode.left != null) {
fatherMinNode = minNode;
minNode = minNode.left;
}
//让fatherMinNode的左节点为null
fatherMinNode.left = null;
//让x节点的左子树为minNode的左子树
minNode.left = x.left;
//让x节点的右子树为minNode的右子树
minNode.right = x.right;
//将x子树变成minNode子树
x = minNode;
}
return x;
}
/**
* <p>
* 查询树中最小的key
* </p>
* 2*
*
* @return 键
*/
public K min() { //获取最小的key,还可以用递归
Node minKeyNode = root;
while (minKeyNode.left != null) {
minKeyNode = minKeyNode.left;
}
return minKeyNode.key;
}
/**
* 查询树中最大的key
*
* @return 键
*/
public K max() {
Node maxKeyNode = root;
while (maxKeyNode.right != null) {
maxKeyNode = maxKeyNode.right;
}
return maxKeyNode.key;
}
/**
* 前序遍历
*
* @return 包含key的queue
*/
public Queue<K> preErgodic() {
Queue<K> keys = new Queue<>();
preErgodic(root, keys);
return keys;
}
/**
* 获取指定x树所有的键,放到queue中
*
* @param x x树
* @param keys queue
*/
private void preErgodic(Node x, Queue<K> keys) {
//如果x为null则直接返回
if (x == null) return;
//把x节点放到keys中
keys.enqueue(x.key);
//遍历左子树
if (x.left != null) preErgodic(x.left, keys);
//遍历右子树
if (x.right != null) preErgodic(x.right, keys);
}
/**
* 中序遍历
*
* @return 包含key的queue
*/
public Queue<K> midErgodic() {
Queue<K> keys = new Queue<>();
midErgodic(root, keys);
return keys;
}
/**
* 获取指定x树所有的键,放到queue中
*
* @param x x树
* @param keys queue
*/
private void midErgodic(Node x, Queue<K> keys) {
//如果x为null则直接返回
if (x == null) return;
//遍历左子树
if (x.left != null) midErgodic(x.left, keys);
//把x节点放到keys中
keys.enqueue(x.key);
//遍历右子树
if (x.right != null) midErgodic(x.right, keys);
}
/**
* 后序遍历
*
* @return 包含key的queue
*/
public Queue<K> afterErgodic() {
Queue<K> keys = new Queue<>();
afterErgodic(root, keys);
return keys;
}
/**
* 获取指定x树所有的键,放到queue中
*
* @param x x树
* @param keys queue
*/
private void afterErgodic(Node x, Queue<K> keys) {
//如果x为null则直接返回
if (x == null) return;
//遍历左子树
if (x.left != null) afterErgodic(x.left, keys);
//遍历右子树
if (x.right != null) afterErgodic(x.right, keys);
//把x节点放到keys中
keys.enqueue(x.key);
}
/**
* 层序遍历
*
* @return 包含key的queue
*/
public Queue<K> layerErgodic() {
//声明两个队列,一个储存key,一个储存Node
Queue<Node> nodes = new Queue<>();
Queue<K> keys = new Queue<>();
nodes.enqueue(root); //先将根节点放入其中
while (!nodes.isEmpty()) {
Node node = nodes.dequeue(); //弹出一个Node,然后根据弹出的Node将弹出的node的左节点,有节点放入队列中
keys.enqueue(node.key); //将弹出的Node的key放入keys队列中
if (node.left != null) nodes.enqueue(node.left); //将左节点放入队列中
if (node.right != null) nodes.enqueue(node.right); //将右节点放入队列中
}
return keys; //返回keys
}
/**
* 获取树的最大深度
*
* @return 最大深度
*/
public int maxDepth() {
return maxDepth(root);
}
/**
* 子树x的最大深度
*
* @param x 子树
* @return 子树x的最大深度
*/
private int maxDepth(Node x) {
if (x == null) return 0; //递归调用的出口
int max = 0; //当前子树最大深度
int maxLeft = 0; //当前子树左子树的最大深度
int maxRight = 0; //当前子树右子树的最大深度
if (x.left != null) maxLeft = maxDepth(x.left); //获得左子树的最大深度
if (x.right != null) maxRight = maxDepth(x.right); //获得右子树的最大深度
max = Math.max(maxLeft, maxRight) + 1; //当前子树的最大深度
return max;
}
}